Variables in Select Statement - c#

I have the following function:
public List<TopMediansModel> QueryForMedians(string state, string median)
{
ApplicationDbContext db = new ApplicationDbContext();
return (from q in db.StateSuburbLocation
where (q.State == state)
orderby q.GetType().GetProperty(median) descending
select new TopMediansModel
{
Median = median,
MedianValue = q.GetType().GetProperty(median),
MedianSuburb = q.Suburb
}).Take(10).ToList();
}
Is it possible to have the orderby and MedianValue 'properties' as variables?
I've tried using the GetType().GetProperty() Methods, and am no doubt doing so incorrectly.

You can use something like Dynamic Linq
return db.StateSuburbLocation
.Where(l => l.State == state)
.OrderByDescending(median)
.Select(l => new TopMediansModel {
Median = median,
MedianValue = q.GetType().GetProperty(median),
MedianSuburb = q.Suburb
}).Take(10).ToList();
Or you can build query expression manually
public static IQueryable<T> OrderByDescending<T>(
this IQueryable<T> source, string propertyName)
{
var parameter = Expression.Parameter(typeof(T), "p");
var property = Expression.PropertyOrField(parameter, propertyName);
var keySelector = Expression.Lambda<Func<T, object>>(property, parameter);
return source.OrderByDescending(keySelector);
}
Usage is same

Related

How do I write a dynamic predicate builder for a where statement in linq / EF Core?

I'm working with an IQueryable<SomeRandomObject> that is pulled using an EF Core 3.1 data context.
I'm pretty sure I can dynamically build a predicate for .Where() so that I can pass a string in for what column, and what value.
Of course this doesn't work, but some pseudo-code might be:
IQueryable myQueryable = stuffFromContext;
var columnName = "memberid";
var searchValue = "1234";
var results = myQueryable.Where(x=> someMagicColumnFunction(columnName, searchvalue))
I've only done research at this point, and predicate building is not my area of expertise.
Can someone help me create a function that I can pass in the parameters my IQueryable, a string representing the column name, and a string for the search (full equality for now, no 'like').
I'd love to see how this is done. I can't find a solid example anywhere on how to do something small like this. Most of the examples are everything and the kitchen sink!
Assuming the type of the rows in myQueryable are TQueryable then you can create a myQueryable specific function to generate the lambda:
Expression<Func<TQueryable, bool>> EqualsFilter<TCol>(string columnName, TCol searchValue) {
// build x => x.{columnName} == searchValue
// (TQueryable x)
var xParam = Expression.Parameter(typeof(TQueryable), "x");
// x.{columnName}
var colExpr = Expression.Property(xParam, columnName);
// {searchValue}
var constExpr = Expression.Constant(searchValue);
// x.{columnName} == {searchValue}
var lambdaBody = Expression.MakeBinary(ExpressionType.Equal, colExpr, constExpr);
// (TQueryable x) => x.{columnName} == {searchValue}
var lambda = Expression.Lambda<Func<TQueryable, bool>>(lambdaBody, xParam);
return lambda;
}
Once you have the method, you can use it like:
var myQueryable = stuffFromContext;
var columnName = "memberid";
var searchValue = "1234";
var results = myQueryable.Where(EqualsFilter(columnName, searchvalue));
However, if myQueryable has a complex or anonymous type (because of a Select or Join) you need to replace the Where as C# can only infer types from parameters, so you need the myQueryable parameter to get the entity type you are filtering. Using a generic version of EqualsFilter as a helper method, you have:
public static class IQueryableExt {
static Expression<Func<T, bool>> EqualsFilter<T, TCol>(string columnName, TCol searchValue) {
// build x => x.{columnName} == searchValue
// (T x)
var xParam = Expression.Parameter(typeof(Accounts), "x");
// x.{columnName}
var colExpr = Expression.Property(xParam, columnName);
// {searchValue}
var constExpr = Expression.Constant(searchValue);
// x.{columnName} == {searchValue}
var lambdaBody = Expression.MakeBinary(ExpressionType.Equal, colExpr, constExpr);
// (T x) => x.{columnName} == {searchValue}
var lambda = Expression.Lambda<Func<T, bool>>(lambdaBody, xParam);
return lambda;
}
public static IQueryable<T> WhereColumnEquals<T, TCol>(this IQueryable<T> src, string columnName, TCol searchValue)
=> src.Where(EqualsFilter<T, TCol>(columnName, searchValue));
}
Which you can now use like:
var myQueryable = stuffFromContext;
var columnName = "memberid";
var searchValue = "1234";
var results = myQueryable.WhereColumnEquals(columnName, searchvalue);

How to build dynamic query with Where and OR using Expression

I hope somebody can guide and help me with this. We have an inherited project that uses ExpressionHelper class. Basically, this Expression Helper will return an IQueryable that build a dynamic query base on the search term that the user provided.
For example, I have the below code where I pass 2 search terms.
IQueryable<UserEntity> modifiedQuery = _uow.UserRepository.GetAll();;
var searchTerms = new List<SearchTerm>
{
new SearchTerm { Name = "FirstName", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "FirstName", Operator = "eq", Value = "John" }
};
foreach (var searchTerm in searchTerms)
{
var propertyInfo = ExpressionHelper
.GetPropertyInfo<TEntity>(searchTerm.EntityName ?? searchTerm.Name);
var obj = ExpressionHelper.Parameter<TEntity>();
var left = ExpressionHelper.GetPropertyExpression(obj, propertyInfo);
var right = searchTerm.ExpressionProvider.GetValue(searchTerm.Value);
var comparisonExpression = searchTerm.ExpressionProvider
.GetComparison(left, searchTerm.Operator, right);
// x => x.Property == "Value"
var lambdaExpression = ExpressionHelper
.GetLambda<TEntity, bool>(obj, comparisonExpression);
// query = query.Where...
modifiedQuery = ExpressionHelper.CallWhere(modifiedQuery, lambdaExpression);
}
With the code above and using the below ExpressionHelper class, this generate the below SQL query when I check using SQLProfiler. Please notice the AND in the query. What I actually what is OR.
Constructed QUERY in SQL Profiler
SELECT
[Extent1].[FirstName] AS [FirstName],
FROM [dbo].[tblUser] AS [Extent1]
WHERE ([Extent1].[Conatact1] = N'Bob') AND ([Extent1].[Contact2] = N'John')
ExpressionHelper.cs
public static class ExpressionHelper
{
private static readonly MethodInfo LambdaMethod = typeof(Expression)
.GetMethods()
.First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2);
private static MethodInfo[] QueryableMethods = typeof(Queryable)
.GetMethods()
.ToArray();
private static MethodInfo GetLambdaFuncBuilder(Type source, Type dest)
{
var predicateType = typeof(Func<,>).MakeGenericType(source, dest);
return LambdaMethod.MakeGenericMethod(predicateType);
}
public static PropertyInfo GetPropertyInfo<T>(string name)
=> typeof(T).GetProperties()
.Single(p => p.Name == name);
public static ParameterExpression Parameter<T>()
=> Expression.Parameter(typeof(T));
public static MemberExpression GetPropertyExpression(ParameterExpression obj, PropertyInfo property)
=> Expression.Property(obj, property);
public static LambdaExpression GetLambda<TSource, TDest>(ParameterExpression obj, Expression arg)
=> GetLambda(typeof(TSource), typeof(TDest), obj, arg);
public static LambdaExpression GetLambda(Type source, Type dest, ParameterExpression obj, Expression arg)
{
var lambdaBuilder = GetLambdaFuncBuilder(source, dest);
return (LambdaExpression)lambdaBuilder.Invoke(null, new object[] { arg, new[] { obj } });
}
public static IQueryable<T> CallWhere<T>(IQueryable<T> query, LambdaExpression predicate)
{
var whereMethodBuilder = QueryableMethods
.First(x => x.Name == "Where" && x.GetParameters().Length == 2)
.MakeGenericMethod(new[] { typeof(T) });
return (IQueryable<T>)whereMethodBuilder
.Invoke(null, new object[] { query, predicate });
}
public static IQueryable<TEntity> CallOrderByOrThenBy<TEntity>(
IQueryable<TEntity> modifiedQuery,
bool useThenBy,
bool descending,
Type propertyType,
LambdaExpression keySelector)
{
var methodName = "OrderBy";
if (useThenBy) methodName = "ThenBy";
if (descending) methodName += "Descending";
var method = QueryableMethods
.First(x => x.Name == methodName && x.GetParameters().Length == 2)
.MakeGenericMethod(new[] { typeof(TEntity), propertyType });
return (IQueryable<TEntity>)method.Invoke(null, new object[] { modifiedQuery, keySelector });
}
}
I have hard time understanding on how the query was created and how do I change it to become OR in the created query.
Hope someone can guide me and point to the right direction. Thank you!
Add to SearchTerm a new property (C# 6.0 syntax here):
// This is quite wrong. We should have an enum here, but Operator is
// done as a string, so I'm maintaining the "style"
// Supported LogicalConnector: and, or
public string LogicalConnector { get; set; } = "and";
}
Then:
private static IQueryable<TEntity> BuildQuery<TEntity>(IQueryable<TEntity> modifiedQuery, List<SearchTerm> searchTerms)
{
Expression comparisonExpressions = null;
var obj = ExpressionHelper.Parameter<TEntity>();
foreach (var searchTerm in searchTerms)
{
var propertyInfo = ExpressionHelper
.GetPropertyInfo<TEntity>(searchTerm.EntityName ?? searchTerm.Name);
var left = ExpressionHelper.GetPropertyExpression(obj, propertyInfo);
var right = searchTerm.ExpressionProvider.GetValue(searchTerm.Value);
var comparisonExpression = searchTerm.ExpressionProvider.GetComparison(left, searchTerm.Operator, right);
if (comparisonExpressions == null)
{
comparisonExpressions = comparisonExpression;
}
else if (searchTerm.LogicalConnector == "and")
{
comparisonExpressions = Expression.AndAlso(comparisonExpressions, comparisonExpression);
}
else if (searchTerm.LogicalConnector == "or")
{
comparisonExpressions = Expression.OrElse(comparisonExpressions, comparisonExpression);
}
else
{
throw new NotSupportedException(searchTerm.LogicalConnector);
}
}
if (comparisonExpressions != null)
{
// x => x.Property == "Value"
var lambdaExpression = ExpressionHelper.GetLambda<TEntity, bool>(obj, comparisonExpressions);
// query = query.Where...
modifiedQuery = ExpressionHelper.CallWhere(modifiedQuery, lambdaExpression);
}
return modifiedQuery;
}
Use it like:
var searchTerms = new List<SearchTerm>
{
new SearchTerm { Name = "PrimaryContact", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "SecondaryContact", Operator = "eq", Value = "Bob" },
new SearchTerm { Name = "PrimaryContact", Operator = "eq", Value = "John", LogicalConnector = "or", }
};
IQueryable<UserEntity> query = BuildQuery<UserEntity>(modifiedQuery, searchTerms);
Note that there is no way in this code to explicitly set brackets, that will be implicitly set as:
(((A opB b) opC C) opD D)
Where A, B, C, D are the SearchTerm[0], SearchTerm[1], SearchTerm[2], SearchTerm[3] and opB, opC, opD are the operators defined in SearchTerm[1].LogicalConnector, SearchTerm[2].LogicalConnector, SearchTerm[3].LogicalConnector.
While putting brackets is easy, choosing how to "describe" them is complex, unless you change significantly your SearchTerm collection (it couldn't be a "linear" array but it would need to be a tree).
P.S. I was wrong, you don't need an ExpressionVisitor. You need an ExpressionVisitor when you are trying to "merge" multiple LambdaExpressions that have distinct ParameterExpression. In this code we are able to have a single var obj = ExpressionHelper.Parameter<TEntity>() for all the query, so no problems merging the conditions. To make it clear: if you want to "merge" x1 => x1.Foo == "Foo1" with x2 => x2.Foo == "Foo2" then you need an ExpressionVisitor that replaces x2 with x1, otherwise you would get a wrong query like x1 => x1.Foo == "Foo1" || x2.Foo == "Foo2". In the code given we have only x1 (that is var obj = ExpressionHelper.Parameter<TEntity>()), so no problem.

How do i append a simple expression onto IQueryable

I have the following method:
public List<Customer> SearchTest(string city, int skip, int take)
{
EcomContext db = new EcomContext();
var results = db.Customers.Where(n => n.City == city).OrdeyBy(n => n.Name).Skip(skip).Take(10);
results = AddDeleteCheck<Customer>(results);
return results.ToList()
}
And this reusable method:
private IQueryable<T> AddArchivedCheck<T>(IQueryable<T> data)
{
var parameter = Expression.Parameter(typeof(T));
var e1 = Expression.Equal(Expression.Property(parameter, "Archived"), Expression.Constant(false));
var e2 = data.Expression;
var e3 = Expression.Lambda<Func<T, bool>>(Expression.AndAlso(e1, e2), parameter);
return data.Where(e3);
}
I want to be able to call this method from a number of different functions so i have made it generic. It should take the expression from the IQueryable object and add a check onto this (Archived == false).
I am getting this error:
The binary operator AndAlso is not defined for the types 'System.Boolean' and 'System.Linq.IQueryable` [Ecom.Customer]
It is easier than what you wrote:
private static IQueryable<T> AddArchivedCheck<T>(IQueryable<T> data)
{
var parameter = Expression.Parameter(typeof(T));
var e1 = Expression.Equal(Expression.Property(parameter, "Archived"), Expression.Constant(false));
var lambda = Expression.Lambda<Func<T, bool>>(e1, parameter);
return data.Where(lambda);
}
Remember that in Linq:
var result = query.Where(condition1).Where(condition2);
is equivalent to:
var result = query.Where(condition1 && condition2);

Joining multiple where clauses in LINQ as OR instead of AND

Is there anyway to join LINQ where clauses as OR ?
var ints = new [] { 1, 3, 5, 7 };
var query = from i in ints select i;
query = query.Where (q => q == 3);
query = query..Where (q => q == 7);
What I want is the ability to dynamically add where clauses but make them use OR instead of AND
If you want to stay with your strong-typing Linq queries you should look into LinqKit and predicate building. I have used this for something similar and found it to work well with And / Or stacking of filters.
Check out the C#4.0/3.0 in a Nutshell excerpt for more in depth info. Here is a snip from my code:
//Setup the initial predicate obj then stack on others:
basePredicate = basePredicate.And(p => false);
var predicate1 = PredicateBuilder.True<Person>();
foreach (SearchParms parm in parms)
{
switch (parm.field)
{
case "firstname":
predicate1 = predicate1.And(p => p.FirstName.Trim().ToLower().Contains(sValue));
break;
//etc...
}
}
//Run a switch based on your and/or parm value to determine stacking:
if (Parm.isAnd) {
basePredicate = basePredicate.And(predicate1);
} else {
basePredicate = basePredicate.Or(predicate1);
}
How about something like this?
var query = from i in ints where CheckConditions(i) select i;
public bool CheckConditions(int i)
{
var conditions = WhereConditions; //an IEnumerable<Func<int, bool>> of dynamically added conditions
foreach (var condition in conditions)
{
if (condition(i)) return true;
}
return false;
}
You can probably expand this to be a bit cleverer but that's sort of how I'd do it.
EDIT: Sorry the first example was an AND, have changed it now to be an OR. So the first time it encounters a passing condition it returns true.
Using ExpressionVisitor to help to build the expression base on two expressions with OR/AND relationship. This answer is from Jeffery Zhao's blog.
internal class ParameterReplacer : ExpressionVisitor
{
public ParameterReplacer(ParameterExpression paramExpr)
{
this.ParameterExpression = paramExpr;
}
public ParameterExpression ParameterExpression { get; private set; }
public Expression Replace(Expression expr)
{
return this.Visit(expr);
}
protected override Expression VisitParameter(ParameterExpression p)
{
return this.ParameterExpression;
}
}
public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another)
{
var candidateExpr = Expression.Parameter(typeof(T), "candidate");
var parameterReplacer = new ParameterReplacer(candidateExpr);
var left = parameterReplacer.Replace(one.Body);
var right = parameterReplacer.Replace(another.Body);
var body = Expression.And(left, right);
return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}
public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> one, Expression<Func<T, bool>> another)
{
var candidateExpr = Expression.Parameter(typeof(T), "candidate");
var parameterReplacer = new ParameterReplacer(candidateExpr);
var left = parameterReplacer.Replace(one.Body);
var right = parameterReplacer.Replace(another.Body);
var body = Expression.Or(left, right);
return Expression.Lambda<Func<T, bool>>(body, candidateExpr);
}
You can using Union method:
var ints = new [] { 1, 3, 5, 7 };
var query = ints.Where(q => q == 3);
query = query.Union(ints.Where(q => q == 7));
Are you talking about specifying more than one condition in the lambda?
query = query.Where(q => q == 3 ||
q == 7);
try this
var ints = new [] { 1, 3, 5, 7 };
var query = ints.select(X=>X).where(X=>X==3||X==7);
I am trying to do something similar. Here's what I came up with:
//various test cases
bool useTestCase1 = true;
bool useTestCase2 = true;
bool useTestCase3 = false;
query = query.Where(q =>
(q == 3 && useTestCase1 ) ||
(q == 7 && useTestCase2 ) ||
(q == 10 && useTestCase3 )
);

LINQ Expression to return Property value?

I'm trying to create a generic function to help me select thousands of records using LINQ to SQL from a local list. SQL Server (2005 at least) limits queries to 2100 parameters and I'd like to select more records than that.
Here would be a good example usage:
var some_product_numbers = new int[] { 1,2,3 ... 9999 };
Products.SelectByParameterList(some_product_numbers, p => p.ProductNumber);
Here is my (non-working) implementation:
public static IEnumerable<T> SelectByParameterList<T, PropertyType>(Table<T> items,
IEnumerable<PropertyType> parameterList, Expression<Func<T, PropertyType>> property) where T : class
{
var groups = parameterList
.Select((Parameter, index) =>
new
{
GroupID = index / 2000, //2000 parameters per request
Parameter
}
)
.GroupBy(x => x.GroupID)
.AsEnumerable();
var results = groups
.Select(g => new { Group = g, Parameters = g.Select(x => x.Parameter) } )
.SelectMany(g =>
/* THIS PART FAILS MISERABLY */
items.Where(item => g.Parameters.Contains(property.Compile()(item)))
);
return results;
}
I have seen plenty of examples of building predicates using expressions. In this case I only want to execute the delegate to return the value of the current ProductNumber. Or rather, I want to translate this into the SQL query (it works fine in non-generic form).
I know that compiling the Expression just takes me back to square one (passing in the delegate as Func) but I'm unsure of how to pass a parameter to an "uncompiled" expression.
Thanks for your help!
**** EDIT:** Let me clarify further:
Here is a working example of what I want to generalize:
var local_refill_ids = Refills.Select(r => r.Id).Take(20).ToArray();
var groups = local_refill_ids
.Select((Parameter, index) =>
new
{
GroupID = index / 5, //5 parameters per request
Parameter
}
)
.GroupBy(x => x.GroupID)
.AsEnumerable();
var results = groups
.Select(g => new { Group = g, Parameters = g.Select(x => x.Parameter) } )
.SelectMany(g =>
Refills.Where(r => g.Parameters.Contains(r.Id))
)
.ToArray()
;
Results in this SQL code:
SELECT [t0].[Id], ... [t0].[Version]
FROM [Refill] AS [t0]
WHERE [t0].[Id] IN (#p0, #p1, #p2, #p3, #p4)
... That query 4 more times (20 / 5 = 4)
I've come up with a way to chunk the query into pieces - i.e. you give it 4000 values, so it might do 4 requests of 1000 each; with full Northwind example. Note that this might not work on Entity Framework, due to Expression.Invoke - but is fine on LINQ to SQL:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
namespace ConsoleApplication5 {
/// SAMPLE USAGE
class Program {
static void Main(string[] args) {
// get some ids to play with...
string[] ids;
using(var ctx = new DataClasses1DataContext()) {
ids = ctx.Customers.Select(x => x.CustomerID)
.Take(100).ToArray();
}
// now do our fun select - using a deliberately small
// batch size to prove it...
using (var ctx = new DataClasses1DataContext()) {
ctx.Log = Console.Out;
foreach(var cust in ctx.Customers
.InRange(x => x.CustomerID, 5, ids)) {
Console.WriteLine(cust.CompanyName);
}
}
}
}
/// THIS IS THE INTERESTING BIT
public static class QueryableChunked {
public static IEnumerable<T> InRange<T, TValue>(
this IQueryable<T> source,
Expression<Func<T, TValue>> selector,
int blockSize,
IEnumerable<TValue> values) {
MethodInfo method = null;
foreach(MethodInfo tmp in typeof(Enumerable).GetMethods(
BindingFlags.Public | BindingFlags.Static)) {
if(tmp.Name == "Contains" && tmp.IsGenericMethodDefinition
&& tmp.GetParameters().Length == 2) {
method = tmp.MakeGenericMethod(typeof (TValue));
break;
}
}
if(method==null) throw new InvalidOperationException(
"Unable to locate Contains");
foreach(TValue[] block in values.GetBlocks(blockSize)) {
var row = Expression.Parameter(typeof (T), "row");
var member = Expression.Invoke(selector, row);
var keys = Expression.Constant(block, typeof (TValue[]));
var predicate = Expression.Call(method, keys, member);
var lambda = Expression.Lambda<Func<T,bool>>(
predicate, row);
foreach(T record in source.Where(lambda)) {
yield return record;
}
}
}
public static IEnumerable<T[]> GetBlocks<T>(
this IEnumerable<T> source, int blockSize) {
List<T> list = new List<T>(blockSize);
foreach(T item in source) {
list.Add(item);
if(list.Count == blockSize) {
yield return list.ToArray();
list.Clear();
}
}
if(list.Count > 0) {
yield return list.ToArray();
}
}
}
}
Easiest way to do this: Use LINQKit (Free, non-restrictive license)
Working version of code:
public static IEnumerable<T> SelectByParameterList<T, PropertyType>(this Table<T> items, IEnumerable<PropertyType> parameterList, Expression<Func<T, PropertyType>> propertySelector, int blockSize) where T : class
{
var groups = parameterList
.Select((Parameter, index) =>
new
{
GroupID = index / blockSize, //# of parameters per request
Parameter
}
)
.GroupBy(x => x.GroupID)
.AsEnumerable();
var selector = LinqKit.Linq.Expr(propertySelector);
var results = groups
.Select(g => new { Group = g, Parameters = g.Select(x => x.Parameter) } )
.SelectMany(g =>
/* AsExpandable() extension method requires LinqKit DLL */
items.AsExpandable().Where(item => g.Parameters.Contains(selector.Invoke(item)))
);
return results;
}
Example usage:
Guid[] local_refill_ids = Refills.Select(r => r.Id).Take(20).ToArray();
IEnumerable<Refill> results = Refills.SelectByParameterList(local_refill_ids, r => r.Id, 10); //runs 2 SQL queries with 10 parameters each
Thanks again for all your help!
LINQ-to-SQL still works via standard SQL parameters, so writing a fancy expression isn't going to help. There are 3 common options here:
pack the ids into (for example) csv/tsv; pass down as a varchar(max) and use a udf to split it (at the server) into a table variable; join to the table variable
use a table-valued-parameter in SQL Server 2008
have a table on the server that you could push the ids into (perhaps via SqlBulkCopy) (perhaps with a "session guid" or similar); join to this table
The first is the simplest; getting a "split csv udf" is trivial (just search for it). Drag the udf onto the data-context and consume from there.
Pass IQuerable to the Contains function instead of list or array. please see the below example
var df_handsets = db.DataFeed_Handsets.Where(m => m.LaunchDate != null).
Select(m => m.Name);
var Make = (from m in db.MobilePhones
where (m.IsDeleted != true || m.IsDeleted == null)
&& df_handsets.Contains(m.Name)
orderby m.Make
select new { Value = m.Make, Text = m.Make }).Distinct();
when you pass list or array it is passed in form of parameters and its exceed the counts when the list items count is greater than 2100.
You can create your own QueryProvider
public class QueryProvider : IQueryProvider
{
// Translates LINQ query to SQL.
private readonly Func<IQueryable, DbCommand> _translator;
// Executes the translated SQL and retrieves results.
private readonly Func<Type, string, object[], IEnumerable> _executor;
public QueryProvider(
Func<IQueryable, DbCommand> translator,
Func<Type, string, object[], IEnumerable> executor)
{
this._translator = translator;
this._executor = executor;
}
#region IQueryProvider Members
public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
{
return new Queryable<TElement>(this, expression);
}
public IQueryable CreateQuery(Expression expression)
{
throw new NotImplementedException();
}
public TResult Execute<TResult>(Expression expression)
{
bool isCollection = typeof(TResult).IsGenericType &&
typeof(TResult).GetGenericTypeDefinition() == typeof(IEnumerable<>);
var itemType = isCollection
// TResult is an IEnumerable`1 collection.
? typeof(TResult).GetGenericArguments().Single()
// TResult is not an IEnumerable`1 collection, but a single item.
: typeof(TResult);
var queryable = Activator.CreateInstance(
typeof(Queryable<>).MakeGenericType(itemType), this, expression) as IQueryable;
IEnumerable queryResult;
// Translates LINQ query to SQL.
using (var command = this._translator(queryable))
{
var parameters = command.Parameters.OfType<DbParameter>()
.Select(parameter => parameter)
.ToList();
var query = command.CommandText;
var newParameters = GetNewParameterList(ref query, parameters);
queryResult = _executor(itemType,query,newParameters);
}
return isCollection
? (TResult)queryResult // Returns an IEnumerable`1 collection.
: queryResult.OfType<TResult>()
.SingleOrDefault(); // Returns a single item.
}
public object Execute(Expression expression)
{
throw new NotImplementedException();
}
#endregion
private static object[] GetNewParameterList(ref string query, List<DbParameter> parameters)
{
var newParameters = new List<DbParameter>(parameters);
foreach (var dbParameter in parameters.Where(p => p.DbType == System.Data.DbType.Int32))
{
var name = dbParameter.ParameterName;
var value = dbParameter.Value != null ? dbParameter.Value.ToString() : "NULL";
var pattern = String.Format("{0}[^0-9]", dbParameter.ParameterName);
query = Regex.Replace(query, pattern, match => value + match.Value.Replace(name, ""));
newParameters.Remove(dbParameter);
}
for (var i = 0; i < newParameters.Count; i++)
{
var parameter = newParameters[i];
var oldName = parameter.ParameterName;
var pattern = String.Format("{0}[^0-9]", oldName);
var newName = "#p" + i;
query = Regex.Replace(query, pattern, match => newName + match.Value.Replace(oldName, ""));
}
return newParameters.Select(x => x.Value).ToArray();
}
}
static void Main(string[] args)
{
using (var dc=new DataContext())
{
var provider = new QueryProvider(dc.GetCommand, dc.ExecuteQuery);
var serviceIds = Enumerable.Range(1, 2200).ToArray();
var tasks = new Queryable<Task>(provider, dc.Tasks).Where(x => serviceIds.Contains(x.ServiceId) && x.CreatorId==37 && x.Creator.Name=="12312").ToArray();
}
}

Categories

Resources