LINQ to Entities does not recognize the method 'Boolean Contains[Int32] - c#

I have the following extension methods in which I am using to do a Contains on LINQ-To-Entities:
public static class Extensions
{
public static IQueryable<TEntity> WhereIn<TEntity, TValue>
(
this ObjectQuery<TEntity> query,
Expression<Func<TEntity, TValue>> selector,
IEnumerable<TValue> collection
)
{
if (selector == null) throw new ArgumentNullException("selector");
if (collection == null) throw new ArgumentNullException("collection");
if (!collection.Any())
return query.Where(t => false);
ParameterExpression p = selector.Parameters.Single();
IEnumerable<Expression> equals = collection.Select(value =>
(Expression)Expression.Equal(selector.Body,
Expression.Constant(value, typeof(TValue))));
Expression body = equals.Aggregate((accumulate, equal) =>
Expression.Or(accumulate, equal));
return query.Where(Expression.Lambda<Func<TEntity, bool>>(body, p));
}
//Optional - to allow static collection:
public static IQueryable<TEntity> WhereIn<TEntity, TValue>
(
this ObjectQuery<TEntity> query,
Expression<Func<TEntity, TValue>> selector,
params TValue[] collection
)
{
return WhereIn(query, selector, (IEnumerable<TValue>)collection);
}
}
When I call the extenion method to check if a list of ids is in a particular table, it works and I get back the List of ids, like this:
List<int> Ids = _context.Persons
.WhereIn(x => x.PersonId, PersonIds)
.Select(x => x.HeaderId).ToList();
When I execute the next statement, it complains that LINQ-To-Entities does not recogonize Contains(int32), but I thought I am not going against the entity anymore, but a collection of ints.
predicate = predicate.And(x=> Ids.Contains(x.HeaderId));
If I have a comma separated string such as "1,2,3", then the following works:
predicate = predicate.And(x=>x.Ids.Contains(x.HeaderId));
I am trying to take the List returned and create comma separated list of strings, the problem here is that now when I do predicate = predicate.And(x=>sb.Contains(x.HeaderId.ToString());, it complains that it does not like ToString().
I also tried doing:
predicate = predicate.And(x=>Extensions.WhereIn(Ids, x.id));, but it can't resolve WhereIn. It says I must add `<>`, but I am not sure what to add here and how implement it.

Where is nothing wrong with your WhereIn, and you are correct: when you use Ids, you are not going against the entity anymore, but a collection of ints.
Problem is when you're using .And on predicate: LINQ-To-Entities tries to convert everything inside those brackets into Entities methods, and there is no corresponding Contains method.
Solution:
Instead of
predicate = predicate.And(x=> Ids.Contains(x.HeaderId));
use
predicate = predicate.And(Contains<XClassName, int>(x.HeaderId));
where Contains defined as follows:
private static Expression<Func<TElement, bool>> Contains<TElement, TValue>(Expression<Func<TElement, TValue>> valueSelector, List<TValue> values)
{
if (null == valueSelector) { throw new ArgumentNullException("valueSelector"); }
if (null == values) { throw new ArgumentNullException("values"); }
if (!values.Any())
return e => false;
var equals = values.Select(value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));
return Expression.Lambda<Func<TElement, bool>>(#equals.Aggregate(Expression.Or), valueSelector.Parameters.Single());
}
and XClassName is the name of the class of your x

You cant use array like that, you need to previsit this lambda in order to expand it to primitives. Alternatively you can change underlying provider so it knows how to generate IN statement , as it doesnt by default.
Didnt find post where one guys actually implement it, will updated once I did.
Basically when you use your extension method it is like
x=>arr.Contains(x)
So if you try to execute such lambda agains your entityset etc it will throw you exception saying that parameters can only be primitives.
The reason is that underlying provider doesnt know how to convert .Contains method for array as function parameter into sql query. And in order to solve that you have two options
teach it how to use T[] as parameter and use Contains with this parameter
update your extension method in order to generate new lamda which will use 'allowed' building blocks, ie expressions using primitive types like int, string, guid etc.
Check this article
http://msdn.microsoft.com/en-us/library/bb882521(v=vs.90).aspx

Replace your:
List<int> Ids = _context.Persons
.WhereIn(x => x.PersonId, PersonIds)
.Select(x => x.HeaderId).ToList();
with
var Ids = _context.Persons
.WhereIn(x => x.PersonId, PersonIds)
.Select(x => x.HeaderId).ToList();
and then try.

Related

Build expression tree for LINQ using List<T>.Contains method

Problem
I'm working on refactoring some LINQ queries for several reports in our web application, and I'm attempting to move some duplicate query predicates into their own IQueryable exension methods so we can reuse them for these reports, and reports in the future. As you can probably infer, I've already refactored the predicate for groups, but the predicate for codes is giving me problems. This is an example of one of the report methods I have so far:
DAL method:
public List<Entities.QueryView> GetQueryView(Filter filter)
{
using (var context = CreateObjectContext())
{
return (from o in context.QueryViews
where (!filter.FromDate.HasValue || o.RepairDate >= EntityFunctions.TruncateTime(filter.FromDate))
&& (!filter.ToDate.HasValue || o.RepairDate <= EntityFunctions.TruncateTime(filter.ToDate))
select o)
.WithCode(filter)
.InGroup(filter)
.ToList();
}
}
IQueryable Extension:
public static IQueryable<T> WithCode<T>(this IQueryable<T> query, Filter filter)
{
List<string> codes = DAL.GetCodesByCategory(filter.CodeCategories);
if (codes.Count > 0)
return query.Where(Predicates.FilterByCode<T>(codes));
return query;
}
Predicate:
public static Expression<Func<T, List<string>, bool>> FilterByCode<T>(List<string> codes)
{
// Method info for List<string>.Contains(code).
var methodInfo = typeof(List<string>).GetMethod("Contains", new Type[] { typeof(string) });
// List of codes to call .Contains() against.
var instance = Expression.Variable(typeof(List<string>), "codes");
var param = Expression.Parameter(typeof(T), "j");
var left = Expression.Property(param, "Code");
var expr = Expression.Call(instance, methodInfo, Expression.Property(param, "Code"));
// j => codes.Contains(j.Code)
return Expression.Lambda<Func<T, List<string>, bool>>(expr, new ParameterExpression[] { param, instance });
}
The problem I'm having is that Queryable.Where doesn't accept a type of Expression<Func<T, List<string>, bool>. The only way I can think of creating this predicate dynamically is to use two parameters, which is the part that is really stumping me.
What I'm not comprehending is the following method works. I can pass the exact lambda expression I am trying to create dynamically, and it correctly filters my data.
public List<Entities.QueryView> GetQueryView(Filter filter)
{
// Get the codes here.
List<string> codes = DAL.GetCodesByCategory(filter.CodeCategories);
using (var context = CreateObjectContext())
{
return (from o in context.QueryViews
where (!filter.FromDate.HasValue || o.RepairDate >= EntityFunctions.TruncateTime(filter.FromDate))
&& (!filter.ToDate.HasValue || o.RepairDate <= EntityFunctions.TruncateTime(filter.ToDate))
select o)
.Where(p => codes.Contains(p.Code)) // This works fine.
//.WithCode(filter)
.InGroup(filter)
.ToList();
}
}
Questions
Can I implement my own Queryable.Where overload? If so, is it even feasible?
If an overload isn't feasible, is there a way to dynamically construct the predicate p => codes.Contains(p.Code) without using two parameters?
Is there an easier way to do this? I feel like I'm missing something.
You can create your own extension method, name it Where, accept an IQueryable<T>, return an IQueryable<T>, and otherwise make it emulate the form of LINQ methods. It wouldn't be a LINQ method, but it would look like one. I would discourage you from writing such a method simply because it would likely confuse others; even if you want to make a new extension method, use a name not used in LINQ to avoid confusion. In short, do what you're doing now, create new extensions without actually naming them Where. If you really wanted to name one Where though nothing's stopping you.
Sure, just use a lambda:
public static Expression<Func<T, bool>> FilterByCode<T>(List<string> codes)
where T : ICoded //some interface with a `Code` field
{
return p => codes.Contains(p.Code);
}
If you really cannot have your entities implement an interface (hint: you almost certainly can), then the code would look identical to the code that you have, but using the list that you pass in as a constant rather than a new parameter:
public static Expression<Func<T, bool>> FilterByCode<T>(List<string> codes)
{
var methodInfo = typeof(List<string>).GetMethod("Contains",
new Type[] { typeof(string) });
var list = Expression.Constant(codes);
var param = Expression.Parameter(typeof(T), "j");
var value = Expression.Property(param, "Code");
var body = Expression.Call(list, methodInfo, value);
// j => codes.Contains(j.Code)
return Expression.Lambda<Func<T, bool>>(body, param);
}
I would strongly encourage use of the former method; this method loses static type safety, and is more complex and as such harder to maintain.
Another note, the comment you have in your code: // j => codes.Contains(j.Code) isn't accurate. What that lambda actually looks like is: (j, codes) => codes.Contains(j.Code); which is actually noticeably different.
See the first half of #2.

Pass Func<> to Select

I'm starting with this:
query
.Take(20)
.Select(item => new
{
id = item.SomeField,
value = item.AnotherField
})
.AsEnumerable()
.ToDictionary(item => item.id, item => item.value);
Now, I want to reuse everything except SomeField and AnotherField.
public static Dictionary<int, string> ReusableMethod<T>(
this IQueryable<T> query,
Func<T, int> key,
Func<T, string> value)
{
return query
.Take(20)
.Select(item => new
{
id = key(item),
value = value(item)
})
.AsEnumerable()
.ToDictionary(item => item.id, item => item.value);
}
query.ReusableMethod(item => item.SomeField, item => item.AnotherField);
This works, but the DB query selects more data than required, so I guess that means ReusableMethod is using linq-to-objects.
Is it possible to do this while only selecting the required data? I'll add that Func<> is still part magic for me, so I might be missing something obvious.
Clarification to avoid confusion: the Take(20) is fine, the Select() isn't.
Wrap your funcs with Expression and remove the AsEnumerable call.
public static Dictionary<int, string> ReusableMethod<T>(
this IQueryable<T> query,
Expression<Func<T, int>> key,
Expression<Func<T, string>> value)
An alternative would be to just return the whole row then. No need for Expression in this case.
return query
.Take(20)
.ToDictionary(key, value);
Recently I had the same problem and here is what I did:
You have some DbEntity (generated by LINQ to EF,SQL), but you want to query only some fields (I did this to save network bandwidth). You have to create class derived from DbEntity, beacuse you cant create anonyous types in Expression trees and you can not create new instance of DbEntity in select statement. (No need to add any fields, properties etc.)
public class LocalEntity : DbEntity {}
You need to define a method to generate your select expression tree. It should look like this. This will generate expression tree similar to this: .Select(db => new LocalEntity() { Property1 = db.Property1, Proeprty2 = db.Property2})
protected Expression<Func<DbEntity, LocalEntity>> getSelectExpression()
{
ParameterExpression paramExpr = Expression.Parameter(typeof(DbEntity), "dbRecord");
var selectLambda = Expression.Lambda<Func<DbEntity, LocalEntity>>(
Expression.MemberInit(
Expression.New(typeof(LocalEntity)),
Expression.Bind(typeof(LocalEntity).GetProperty("DbEntityFieldName"), Expression.Property(paramExpr, "DbEntityFieldName"),
....
))
),
paramExpr);
return selectLambda;
}
Use it like this:
query.Select(getSelectExpression()).ToDictionary();
Consider this more as pseudo-code than C# code, as I had to simplify it a lot and I canĀ“t test it, but if oyu make it work, it will transfer from DB only fields you define in getSelectedExpression, not the whole row.

Building a Linq Query from a List<string> using dynamic linq

I am using dynamic linq to preocess some user requests. The way it was set up is that I would gather the data into Var data
var data = Project.Model.Adhoc.GetData().AsQueryable();
This is basically a select * from a view
Then from there I would loop through all of the options that I have to filter that the user selected
foreach (Filters filter in filters.OrderBy(x=>x.strOrderNumber))
{
along with some checks and permutations, I get down to this
data = data.Where(filter.strFilter + FormatOperator(filter.strOperator) + "#0", filter.strValue).
Select(x => x).ToList().AsQueryable();
This is working pretty well, however the datasource is starting to grow, so what I would like to do is something like this:
data = data.select(get all items that were selected) and then do my checks and permutations. This would allow me to only pull what is needed, not the entire datasource. What is the best way in C# using linq to accomplish this.
Ex.
datasource = {Name, Age, Race, Gender, Hair Color, Eye Color, height, weight, etc}
user selected = {Name, Age, Race, Gender}
Instead of querying against that whole datasource, I want to limit the datasource to only what is brought in by the user off the bat, and then I can filter based on that as teh datasource
Take a look at Dynamic Linq
You can use the DynamicQuery library against any LINQ data provider
(including LINQ to SQL, LINQ to Objects, LINQ to XML, LINQ to
Entities, LINQ to SharePoint, LINQ to TerraServer, etc). Instead of
using language operators or type-safe lambda extension methods to
construct your LINQ queries, the dynamic query library provides you
with string based extension methods that you can pass any string
expression into.
Remove the .ToList() call in the foreach loop.
data = data.Where() will build a query expression with ANDs. So after the loop you can finally invoke .ToList() to finally hit the database.
Update
And the .Select() isn't necessary.
data = data.Where(filter.strFilter + FormatOperator(filter.strOperator) + "#0", filter.strValue);
Update2
Oh, after reading your question again I get that you need to build the query using OR.
This is a little bit more difficult using the standard library. If you don't mind pulling in a extra dependency then it can (probably) be done using LinqKit
IQueryable<Product> SearchProducts (params string[] keywords)
{
var predicate = PredicateBuilder.False<Product>();
foreach (string keyword in keywords)
{
string temp = keyword;
predicate = predicate.Or (p => p.Description.Contains (temp));
}
return dataContext.Products.Where (predicate);
}
Although I am not sure how well that works together with Dynamic Linq.
Otherwise you'll have to handcraft the expression, which might end up looking similar to this:
public static class IQueryableExtensions
{
public static IQueryable<T> WhereIn<T, TValue>(
this IQueryable<T> source,
Expression<Func<T, TValue>> propertySelector,
IEnumerable<TValue> values)
{
return source.Where(GetWhereInExpression(propertySelector, values));
}
private static Expression<Func<T, bool>> GetWhereInExpression<T, TValue>(
Expression<Func<T, TValue>> propertySelector, IEnumerable<TValue> values)
{
if (!values.Any())
return c => false;
ParameterExpression p = propertySelector.Parameters.Single();
// You'll have to adjust this:
var equals = values.Select(value => (Expression)Expression.Equal(
propertySelector.Body, Expression.Constant(value, typeof(TValue))));
var body = equals.Aggregate<Expression>(
(accumulate, equal) => Expression.Or(accumulate, equal));
return Expression.Lambda<Func<T, bool>>(body, p);
}
}

Make a Search Method generic using LINQ

I have a method in my project that repeats over and over:
public PAC PAC_GetByCodiPac(string codiPac)
{
var sel = _gam.PAC.Where(pac => pac.CODI_PAC == codiPac);
if (sel.Count() > 0)
return sel.First();
return null;
}
The table PAC means (patient), so I have these methods for all the tables I have.
How can I make a generic method for this?
Thanks in advance.
Here is your generic method. Note, that as others pointed out FirstOrDefault is better than count and then first, so I'm using it here. But it's also possible to write the expression so that it mimics what your original code does. Please let me know if you need additional help with this.
public static T GetByCodi<T>(IQueryable<T> table, string codi, string fieldName) where T : class
{
// x
ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
Expression currentExpression = parameter;
Type currentType = typeof(T);
PropertyInfo property = currentType.GetProperty(fieldName);
// x.CODI_xxx
currentExpression = Expression.Property(currentExpression, property);
// x.CODI_xxx == codi
currentExpression = Expression.Equal(currentExpression, Expression.Constant(codi));
// x => x.CODI_xxx == codi
LambdaExpression lambdaExpression = Expression.Lambda(currentExpression, parameter);
return table.FirstOrDefault((Func<T, bool>)lambdaExpression.Compile());
}
You use it like this:
PAC xxx = GetByCodi<PAC>(_gam.PAC, codiPac, "CODI_PAC");
Edit 1:
I changed the code according to the comment so that you can pass arbitrary ID field name in.
I see that what you asked is a very straight forward where query even doesn't require to have have it on a separate method.
Also you can simply enhance your query link the following:
public PAC PAC_GetByCodiPac(string codiPac)
{
return _gam.PAC.FirstOrDefault(pac => pac.CODI_PAC == codiPac);
}
FirstOrDefault will return the first item on the array, if not it will return null.
If you want a generic method that lets you specify any table and any predicate for records from that table then you can't really get any better than the built-in Where<T>(...) and (as others have already pointed out) the FirstOrDefault<T>(...) extension methods.
Your code would then look like so:
var result = _gam.PAC.Where(pac => pac.CODI_PAC == codiPac).FirstOrDefault();
// OR
var result = _gam.PAC.FirstOrDefault(pac => pac.CODI_PAC == codiPac);
The best you could get then, writing your own generic method, would be this:
public T FirstOrDefault<T>(IQueryable<T> source,
Expression<Func<T, bool>> predicate)
{
return source.Where(predicate).FirstOrDefault();
// OR
// return source.FirstOrDefault(predicate);
}
And that is really just redundant. Especially when your calling code would be actually longer using the helper method:
var result = FirstOrDefault(_gam.PAC, pac => pac.CODI_PAC == codiPac);
// versus
var result = _gam.PAC.FirstOrDefault(pac => pac.CODI_PAC == codiPac);
And even worse, your code is no longer using a fluent, composable syntax. This just makes readability and maintenance more difficult.
If you stick with using the IQueryable<T> extension methods then you can do composition like this:
var result = _gam.PAC
.Where(pac => pac.CODI_PAC == codiPac)
.Where(pac => pac.SomeOtherProperty == someOtherValue)
.FirstOrDefault();
// OR
var result = (from pac in _gam.PAC
where pac.CODI_PAC == codiPac
where pac.SomeOtherProperty == someOtherValue
select pac).FirstOrDefault();
One very important thing to note here is that the predicate parameter in the IQueryable<T>.Where<T>(...) extension method is of type Expression<Func<T, bool>>. This allows the IQueryable<T> provider to construct the native SQL (or other native provider query) at the very last moment before returning a result.
Not using Expression<Func<T, bool>> means that your query would be the equivalent of this:
var result =
_gam.PAC
.ToArray()
.Where(pac => pac.CODI_PAC == codiPac)
.FirstOrDefault();
And that would mean the query will load every record from the "PAC" table into memory before selecting the first filtered result and throwing out the rest of the results.
The bottom-line is that by making a generic helper method you are rewriting existing framework code and you open yourself to performance and maintenance issues while also reducing code readability.
I hope this helps.
I'm not sure if you are asking for this, but this method could be in a static class and method and so you'd be able to call it from everywhere.
An easy solution will be:
//a generic method
private PAC PAC_GetPAC(Func<PAC, bool> predicate)
{
return _gam.PAC.Where(predicate).FirstOrDefault();
}
public PAC PAC_GetPACById(long id)
{
return PAC_GetPAC(p => p.ID == id);
}
public PAC PAC_GetByCodiPac(string codiPac)
{
return PAC_GetPAC(p => pac.CODI_PAC == codiPac);
}

How to Transform a LINQ Expression when you do not have one of the parameters when you define it

I'm trying to build more generic query functionality into my application. What I'd like to do is define objects which given an predicate expression can apply that to an iqueryable with a value that will be passed in later.
I believe the code below should demonstrate what I'm trying to do well enough to understand the problem. Please let me know if you'd like more details!
Thanks!
//in practice the value of this would be set in object constructor likely
private Expression<Func<Contact, string, bool>> FilterDefinition = (c, val) => c.CompanyName.Contains(val);
//this needs to filter the contacts using the FilterDefinition and the filterValue. Filterval needs to become the string parameter
private IQueryable<Contact> ApplyFilter(IQueryable<Contact> contacts, string filterValue)
{
//this method is what I do know know how to contruct.
// I need to take the FilterDefinition expression and create a new expression that would be the result if 'filtervalue' had been passed into it when it was created.
//ie the result would be (if 'mycompany' was the value of filterValue) an expression of
// c => c.CompanyName.Contains("mycompany")
Expression<Func<Contact, bool>> usableFilter = InjectParametersIntoCriteria(FilterDefinition, "SomeCompanyName");
//which I could use the results of to filter my full results.
return contacts.Where(usableFilter);
}
Are you looking for something like this?
private Func<string, Expression<Func<Contact, bool>>> FilterDefinition =
val => c => c.CompanyName.Contains(val);
private IQueryable<Contact> ApplyFilter(
IQueryable<Contact> contacts, string filterValue)
{
Expression<Func<Contact, bool>> usableFilter = FilterDefinition(filterValue);
return contacts.Where(usableFilter);
}
See: Currying
Place the following code in your ApplyFilter body:
var f = FilterDefinition.Compile();
return contacts.Where(x => f(x, filterValue));

Categories

Resources