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.
Related
I am building a service that retrieves Enum-like instances from various repositories. The repositories store the data differently, but all can be retrieved into an EnumEntry { int Id; string Name; } class.
My idea is to use a base class that takes Func delegates as selectors to identify the Id and Name fields on each underlying repository record. e.g.
// constructor:
BaseEnumService<T>(Table<T> table,
Expression<Func<T, int>> idSelector,
Expression<Func<T, string>> nameSelector) {... }
// usage
var caseCategoryService = new BaseEnumService(CaseCategoriesTable, x => x.id, x => x.category)
So far so good.
To retrieve an EnumEntry, given that idSelector and nameSelector are Expressions of pure functions, I thought I could use a lambda expression like this and the compiler would figure it all out:
table.Select(x => new EnumEntry() { Id = idSelector(x), Name = nameSelector(x) }).ToList();
...but the database IQueryable implementation complains as it does not like 'external' functions.
Combining everything with expression trees works ok, e.g.
var record = Expression.Parameter(typeof(T));
Expression<Func<int, string, EnumEntry>> createEnumExpression = (id, name) => new EnumEntry() { Id = id, Name = name };
var selectExpression = Expression.Invoke(createEnumExpression,
Expression.Invoke(idSelector, record),
Expression.Invoke(nameSelector, record));
return table.Select(Expression.Lambda<Func<T, EnumEntry>>(selectExpression, record));
.. but this seems rather verbose and ugly for something so simple.
Is there no way to combine Expression<>s using lambda syntax in the way I originally tried?
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.
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);
}
}
This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Declaring func<in T,Out Result> dynamically
I'm trying to build a query using linq-to-sql and, in order to provide sorting capabilities, I wrote something like this:
private Dictionary<string, Expression<Func<DataAccess.Auditing, object>>> OrderDict { get; set; }
OrderDict = new Dictionary<string,Expression<Func<Augeos.GereonWeb.DataAccess.Auditing, object>>>();
OrderDict.Add("Date", p => (DateTime)p.RequestDateTime);
OrderDict.Add("Username", p => (string)p.CdUsername);
OrderDict.Add("Portfolio", p => (string)p.CdPortfolio);
OrderDict.Add("Url", p => (string)p.RequestedUrl);
OrderDict.Add("Result", p => (bool)p.RequestResult);
OrderDict.Add("Duration", p => (float)p.RequestDuration);
private IQueryable<DataAccess.Auditing> SetOrder(string orderBy, bool orderDirection, IQueryable<DataAccess.Auditing> query)
{
if (orderDirection)
return query.OrderByDescending(OrderDict[orderBy]);
else
return query.OrderBy(OrderDict[orderBy]);
}
My goal is to use the SortOrder function to sort the query. The main problem is that Func returns an object and linq cannot sort elements of this type. I truly need to use object as a return type because I have to sort by a wide range of types. Is it actually possible to slightly modify this code and make it to work?
Thank you,
Gio
Hm you'd have to go very low-level in the expression tree of your query to do such a thing. A much easier way would be to use the DynamicLinq library, where you can do .OrderBy() with a column as string value. Documentation can be found here on Scott Guthrie's blog.
If you imagine ordering your query like so:
query = orderDict["Date"](query, true);
Then your dictionary would have to be defined like this:
var orderDict = new Dictionary<string, Func<IQueryable<Auditing>, bool, IQueryable<Auditing>>>();
and you could add items to it like so:
orderDict.Add("Date", MakeOrderedQueryDelegate(a => a.RequestDateTime));
orderDict.Add("UserName", MakeOrderedQueryDelegate(a => a.CdUsername));
which would need a MakeOrderedQueryDelegate to be like this:
private Func<IQueryable<Auditing>, bool, IQueryable<Auditing>> MakeOrderedQueryDelegate<T>(Expression<Func<Auditing, T>> keyselector)
{
return (q, descending) => { return MakeOrderedQuery(q, descending, keyselector); };
}
..and you can implement MakeOrderedQuery like this:
private IQueryable<Auditing> MakeOrderedQuery<T>(IQueryable<Auditing> query, bool descending,
Expression<Func<Auditing, T>> keyselector)
{
if (descending)
{
return query.OrderByDescending(keyselector);
}
else
{
return query.OrderBy(keyselector);
}
}
If I have an IEnumerable where ClassA exposes an ID property of type long.
Is it possible to use a Linq query to get all instances of ClassA with ID belonging to a second IEnumerable?
In other words, can this be done?
IEnumerable<ClassA> = original.Intersect(idsToFind....)?
where original is an IEnumerable<ClassA> and idsToFind is IEnumerable<long>.
Yes.
As other people have answered, you can use Where, but it will be extremely inefficient for large sets.
If performance is a concern, you can call Join:
var results = original.Join(idsToFind, o => o.Id, id => id, (o, id) => o);
If idsToFind can contain duplicates, you'll need to either call Distinct() on the IDs or on the results or replace Join with GroupJoin (The parameters to GroupJoin would be the same).
I will post an answer using Intersect.
This is useful if you want to intersect 2 IEnumerables of the same type.
First we will need an EqualityComparer:
public class KeyEqualityComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, object> keyExtractor;
public KeyEqualityComparer(Func<T, object> keyExtractor)
{
this.keyExtractor = keyExtractor;
}
public bool Equals(T x, T y)
{
return this.keyExtractor(x).Equals(this.keyExtractor(y));
}
public int GetHashCode(T obj)
{
return this.keyExtractor(obj).GetHashCode();
}
}
Secondly we apply the KeyEqualityComparer to the Intersect function:
var list3= list1.Intersect(list2, new KeyEqualityComparer<ClassToCompare>(s => s.Id));
You can do it, but in the current form, you'd want to use the Where extension method.
var results = original.Where(x => yourEnumerable.Contains(x.ID));
Intersect on the other hand will find elements that are in both IEnumerable's. If you are looking for just a list of ID's, you can do the following which takes advantage of Intersect
var ids = original.Select(x => x.ID).Intersect(yourEnumerable);
A simple way would be:
IEnumerable<ClassA> result = original.Where(a => idsToFind.contains(a.ID));
Use the Where method to filter the results:
var result = original.Where(o => idsToFind.Contains(o.ID));
Naming things is important. Here is an extension method base on the Join operator:
private static IEnumerable<TSource> IntersectBy<TSource, TKey>(
this IEnumerable<TSource> source,
IEnumerable<TKey> keys,
Func<TSource, TKey> keySelector)
=> source.Join(keys, keySelector, id => id, (o, id) => o);
You can use it like this var result = items.IntersectBy(ids, item => item.id).
I've been tripping up all morning on Intersect, and how it doesn't work anymore in core 3, due to it being client side not server side.
From a list of items pulled from a database, the user can then choose to display them in a way that requires children to attached to that original list to get more information.
What use to work was:
itemList = _context.Item
.Intersect(itemList)
.Include(i => i.Notes)
.ToList();
What seems to now work is:
itemList = _context.Item
.Where(item => itemList.Contains(item))
.Include(i => i.Notes)
.ToList();
This seems to be working as expected, without any significant performance difference, and is really no more complicated than the first.