I'm attempting to do an outer join on two sets of data using this statement:
var destinationList = inners.GroupJoin(outers, inner => inner.JoinField, outer => outer.JoinField,
(inner, outerList) =>
outerList.Select(outer => new DestinationModel { Id = inner.JoinField, AggregationField = outer.DataField })
.DefaultIfEmpty(new DestinationModel { Id = inner.JoinField })).SelectMany(destination => destination).ToList();
This works correctly without problem, but I ultimately need to convert this to an expression tree to allow the datasets and the fields to change.
My data models look like this:
InnerModel:
public class InnerModel
{
public int JoinField;
public decimal DataField;
}
OuterModel:
public class OuterModel
{
public int JoinField;
public decimal DataField;
}
DestinationModel:
public class DestinationModel
{
public int Id;
public decimal AggregationField;
}
inners is a List<InnerModel>
outers is a List<OuterModel>
I've managed to get most of the way, but I'm falling short at the last step. This is what I have so far:
// Declare variables
var innerParameter = Expression.Parameter(typeof (InnerModel), "inner");
var innerSelect = Expression.Lambda<Func<InnerModel, int>>(Expression.Field(innerParameter, "JoinField"), innerParameter);
var outerParameter = Expression.Parameter(typeof (OuterModel), "outer");
var outerListParameter = Expression.Parameter(typeof (IEnumerable<OuterModel>), "outerList");
var outerSelect = Expression.Lambda<Func<OuterModel, int>>(Expression.Field(outerParameter, "JoinField"), outerParameter);
var existingBinding = Expression.MemberInit(Expression.New(typeof (DestinationModel)), Expression.Bind(typeof (DestinationModel).GetField("Id"), Expression.Field(innerParameter, "JoinField")));
// Create lambdas
var selector = Expression.Lambda<Func<OuterModel, DestinationModel>>(existingBinding, outerParameter);
var selectMethod = typeof (Enumerable).GetMethods().First(x => x.Name == "Select" && x.GetParameters().Length == 2).MakeGenericMethod(typeof(OuterModel), typeof(DestinationModel));
var selectCall = Expression.Call(selectMethod, outerListParameter, selector);
// Create the inner key selector for the GroupJoin method
var innerKeySelector = Expression.Lambda(selectCall, innerParameter, outerListParameter);
Everything works up until this point. When I try to plug the innerKeySelector into the original statement:
var result = inners.GroupJoin(outers, innerSelect.Compile(), outerSelect.Compile(), (inner, outerList) => outerList.Select(outer => new DestinationModel {Id = inner.JoinField, AggregationField = outer.DataField}).DefaultIfEmpty(new DestinationModel {Id = inner.JoinField})).SelectMany(destination => destination).ToList();
I get a compile error:
The type arguments for method 'Enumerable.GroupJoin(IEnumerable, IEnumerable, Func, Func, Func, TResult>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
I know I'm just missing something obvious, but after working on this for hours, I'm not seeing it. Can someone point me in the right direction?
I found my answer. I needed to place the DefaultIfEmpty call and give it the result of the Select call. I created a MethodInfo for the DefaultIfEmpty call:
var defaultIfEmptyMethod = typeof (Enumerable).GetMethods().First(x => x.Name == "DefaultIfEmpty" && x.GetParameters().Length == 2).MakeGenericMethod(typeof (DestinationTestModel));
Then I created a lambda expression that calls DefaultIfEmpty and Select together:
var innerKeySelectorWithDefault = Expression.Lambda<Func<InnerTestModel,IEnumerable<OuterTestModel>,IEnumerable<DestinationTestModel>>>(Expression.Call(defaultIfEmptyMethod, selectCall, nonExistingBinding), innerParameter, outerListParameter);
That enabled me to call the final methods:
var result = inners.GroupJoin(outers, innerSelect.Compile(), outerSelect.Compile(),innerKeySelectorWithDefault.Compile()).SelectMany(destination => destination).ToList();
Related
I'm trying to convert the following using Expressions so I could engineer it to be more generic and work over different collections with differing properties:
var roleId = 1;
users.Where(u => u.Roles.Any(ur => ur.Id == roleId));
I have created an "Any" expression but I'm unsure how to move this within an Expression.Call to "Where":
var predicateParameter = Expression.Parameter(typeof(ListItem), "ur");
var left = Expression.Property(predicateParameter, "Id");
var right = Expression.Constant(roleId, typeof(int));
var expression = Expression.Equal(left, right);
var anyCheckFunction = Expression.Lambda<Func<ListItem, bool>>(expression, predicateParameter);
Expression<Func<UserDto, Expression<Func<int, int, int>>, Expression<Func<int>>>> power2 =
(o, f) => Expression.Lambda<Func<int>>(Expression.Invoke(f, Expression.Constant(o), Expression.Constant(o)));
var anyMethod2 = Expression.Call(
anyMethod,
users.First().UserRoles.AsQueryable().Expression,
anyCheckFunction);
Any help much appreciated.
You can use generic classes and methods.
For example:
public class Repository<T>
{
public List<T> GetByPredicate(Func<T, bool> predicate, IEnumerable<T> items)
{
return items.Where(predicate).ToList();
}
}
var repository = new Repository<User>();
var result = repository.GetByPredicate(u => u.Roles.Any(ur => ur.Id == roleId), users).ToList();
You can replace items with the database context, or you can use a collection that you fill in yourself.
This question already has answers here:
Generate EF orderby expression by string [duplicate]
(6 answers)
Closed 7 years ago.
I am using .NET 4.51, EF 6
I make a number of calls to my repository layer where I need to do some basic ordering on a single field in either ascending or descending order such as:
The result of GetAllList() is a List<T>. Now unfortunately the Id field I have to sort by is not always called Id nor is the Text field. They can be other things such as MyId, SomeTextField and so on.
So I was wondering if there was a way I could do the OrderBy() and OrderByDescending() clauses by supplying a string for the field name something like:
_Repository.GetAllList().OrderBy(r => r."SomeTextField")
In this way I could move all this code to a common method.
Any pointers greatly appreciated.
This will work:
public static class LinqExtensions
{
private static PropertyInfo GetPropertyInfo(Type objType, string name)
{
var properties = objType.GetProperties();
var matchedProperty = properties.FirstOrDefault (p => p.Name == name);
if (matchedProperty == null)
throw new ArgumentException("name");
return matchedProperty;
}
private static LambdaExpression GetOrderExpression(Type objType, PropertyInfo pi)
{
var paramExpr = Expression.Parameter(objType);
var propAccess = Expression.PropertyOrField(paramExpr, pi.Name);
var expr = Expression.Lambda(propAccess, paramExpr);
return expr;
}
public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> query, string name)
{
var propInfo = GetPropertyInfo(typeof(T), name);
var expr = GetOrderExpression(typeof(T), propInfo);
var method = typeof(Enumerable).GetMethods().FirstOrDefault(m => m.Name == "OrderBy" && m.GetParameters().Length == 2);
var genericMethod = method.MakeGenericMethod(typeof(T), propInfo.PropertyType);
return (IEnumerable<T>) genericMethod.Invoke(null, new object[] { query, expr.Compile() });
}
public static IQueryable<T> OrderBy<T>(this IQueryable<T> query, string name)
{
var propInfo = GetPropertyInfo(typeof(T), name);
var expr = GetOrderExpression(typeof(T), propInfo);
var method = typeof(Queryable).GetMethods().FirstOrDefault(m => m.Name == "OrderBy" && m.GetParameters().Length == 2);
var genericMethod = method.MakeGenericMethod(typeof(T), propInfo.PropertyType);
return (IQueryable<T>) genericMethod.Invoke(null, new object[] { query, expr });
}
}
Testing:
var r = new List<temp> {
new temp { a = 5 },
new temp { a = 1 },
new temp { a = 15 }
}.OrderBy("a");
Gives the correct result (1, 5, 15) - and will provide lazy execution for your use with EF
You will need to implement the overloads if needed.
Does it have to be a string? Why not just make a method that takes a Func key selector as a parameter.
public List<T> GetAllListOrdered<T,TProp>(SimpleOrderingDirectionEnum direction, Func<T,TProp> keySelector)
{
return direction == SimpleOrderingDirectionEnum.Ascending ? _Repository.GetAllList().OrderBy(keySelector).ToList() : _Repository.GetAllList().OrderByDescending(keySelector).ToList();
}
Then call it like
Func<ObjectToSortType, ObjectPropertyToSortBy> keySelector = r => r.Id;
GetAllListOrdered(SimpleOrderingDirectionEnum.Ascending, keySelector);
If the Rob's answer is not enough for you. Try Linq Dynamic. http://dynamiclinq.azurewebsites.net/
using System.Linq.Dynamic; //Import the Dynamic LINQ library
//The standard way, which requires compile-time knowledge
//of the data model
var result = myQuery
.Where(x => x.Field1 == "SomeValue")
.Select(x => new { x.Field1, x.Field2 });
//The Dynamic LINQ way, which lets you do the same thing
//without knowing the data model before hand
var result = myQuery
.Where("Field1=\"SomeValue\"")
.Select("new (Field1, Field2)");
Thanks to all. Rob, your solution was pretty close to what I ended up with.
Based on your insights I did some more searching and came across Marc Gravel's answer here Dynamic LINQ OrderBy on IEnumerable<T> (second post).
It added dynamic's as an additional bonus.
I'm attempting to build an expression tree to perform a LINQ to Entities query in .NET 4.0 with EF4. When I attempt to execute the query I've built, I get a NotSupportedException with the following message:
LINQ to Entities does not recognize the method
'System.Data.Objects.ObjectQuery`1[TestWpf.Customer]
Where(System.String, System.Data.Objects.ObjectParameter[])' method,
and this method cannot be translated into a store expression.
I'm querying against the Northwind database. My entities were generated from the database. In my code below, I have the query I'm trying to build in the method GetQuery1(), and I'm attempting to build it in the GetQuery2() method.
If I set a breakpoint and inspect the query1 variable, it's Expression property is:
Convert(value(System.Data.Objects.ObjectSet`1[TestWpf.Customer])).MergeAs(AppendOnly).Where(c => c.CompanyName.Contains("z"))
What is this Convert().MergeAs(AppendOnly) doing? I attempted to search on MSDN but couldn't locate what I need (at least I don't think I could find it...). Additionally, what am I doing wrong?
I think that perhaps I'm calling an incorrect Where() method, as Intellisense says there is another, which is an extension method. I have not tried to update the whereMethod variable to fetch that one, but I'm not sure how, either.
private static IQueryable<Customer> GetQuery1(NorthEntities context) {
return context.Customers.Where(c => c.CompanyName.Contains("z"));
}
private static IQueryable<Customer> GetQuery2(NorthEntities context) {
var custParam = Expression.Parameter(typeof(Customer), "c");
var custCollection = Expression.Constant(context.Customers);
var companyNamePropValue = Expression.Property(custParam, typeof(Customer).GetProperty("CompanyName"));
var containsParameter = Expression.Constant("z");
var containsMethod = Expression.Call(companyNamePropValue, typeof(string).GetMethod("Contains"), containsParameter);
var whereMethod = context.Customers.GetType().GetMethod("Where", new Type[] { typeof(string), typeof(ObjectParameter[]) });
var param2 = Expression.Constant(new ObjectParameter[] { });
var where = Expression.Call(custCollection, whereMethod, companyNamePropValue, param2);
return ((IQueryable<Customer>)context.Customers).Provider.CreateQuery<Customer>(where);
}
private static void Main(string[] args) {
using (var context = new NorthEntities()) {
var query1 = GetQuery1(context);
var query2 = GetQuery2(context);
foreach (var c in query1)
Console.WriteLine(c.CompanyName);
foreach (var c in query2)
Console.WriteLine(c.CompanyName);
}
Console.ReadLine();
}
To construct the specific query you're working with, try the following:
private static IQueryable<Customer> GetQuery2(NorthEntities context) {
IQueryable<Customer> customers = context.Customers;
var custParam = Expression.Parameter(typeof(Customer), "c");
var companyNamePropValue = Expression.Property(custParam, typeof(Customer).GetProperty("CompanyName"));
var containsParameter = Expression.Constant("z");
var containsCall = Expression.Call(companyNamePropValue, typeof(string).GetMethod("Contains"), containsParameter);
var wherePredicate = Expression.Lambda<Func<Customer, bool>>(containsCall, custParam);
return customers.Where(wherePredicate);
}
In general, to get access to the LINQ extension methods (e. g. Where), you'll have to look in the Queryable class:
var genericWhereMethod = typeof(Queryable).GetMethods()
.Single(m => m.Name == "Where"
// distinguishes between Where((T, int) => bool) and Where(T => bool)
&& m.GetParameters()[1].ParameterType
.GetGenericArguments()[0].GetGenericTypeDefinition() == typeof(Func<,>));
// make the Where method that applies to IQueryable<Customer>
var whereMethod = genericWhereMethod.MakeGenericMethod(typeof(Customer));
I am having trouble creating an extension method for an IQueryable that will include the translation for a specified column in a Linq Query.
Suppose i have the query below.
I would like to call a method IncludeTranslation on the CFG_Article IQueryable specifying the column i want to get the translation for.
Could someone help me in the right direction.
var translations =
from t in UoW.CFG_TRANSLATION.GetAll()
select t;
var result = (
from a in UoW.CFG_ARTICLE.GetAll()
select new
{
a,
translation = translations
.Where(t=> t.TR_TEXT == a.AR_NAME).FirstOrDefault()
});
All i have come up so far is the code below but this does not compile.
public static IQueryable IncludeTranslation<T>(
this IQueryable<T> query,
Expression<Func<t, bool>> fieldToTranslate)
{
// this will get an IQueryable of CFG_TRANSLATION
var translations = GetTranslations();
var result = (
from q in query
select new
{
q,
translation = translations
.Where(t=> t.TR_TEXT == fieldToTranslate)
.FirstOrDefault()
});
// even better is to return all fields from query
// + the TR_TRANSLATION field from the translations table
return result;
}
Try this (I'm having to guess class names are CFG_ARTICLE & CFG_TRANSLATION - replace as required)
public static IQueryable IncludeTranslation(
this IQueryable<CGF_ARTICLE> query,
Func<CFG_ARTICLE, CFG_TRANSLATION, bool> fieldToTranslate)
{
var translations = GetTranslations();
var result =
from a in query
select new
{
a,
translation = translations
.Where(t => fieldToTranslate(a, t))
.FirstOrDefault()
};
return result;
}
calling like this
var result = query.IncludeTranslation(
(article, translation) => article.TR_TEXT == translation.AR_NAME);
I found an other way of returning the same result using a Generic Way.
public static IQueryable IncludeTranslation<S>(this IQueryable<S> source, Expression<Func<S, string>> keyField)
where S : class
{
IQueryable<CFG_TRANSLATION> translations = GetTranslations();
var trans = source.GroupJoin(translations, keyField, t => t.TR_TEXT, (s, t) => new { Source = s, Translations = t });
var result = trans.Select(t => new {
Source = t.Source,
Translation = t.Translations
.FirstOrDefault()
});
return result;
}
Maybe someone can use this as a sollution
this can be called as follow
var Result = QueryableTable.IncludeTranslation(t => t.FieldToTranslate);
This method works great and generates only 1 query (utilizing .Includes() to eager load).
However, I would like to refactor the AbleToDelete code to be more reusable.
(Using EF4)
public override IEnumerable<AffectProjection> SelectAll(SearchAffects page)
{
IQueryable<Affect> query = BuildSearchQuery(page);
IEnumerable<AffectProjection> results = query.Select(entity => new AffectProjection()
{
AffectID = entity.AffectID,
AffectCode = entity.AffectCode,
AffectName = entity.AffectName,
AbleToDelete = !((entity.Foo1s.Any(pa => !pa.Inactive))
|| (entity.Foo2s.Any(ea => !ea.Inactive))
|| (entity.Foo3s.Any(sa => !sa.Inactive))
|| (entity.Foo4s.Any(spa => !spa.Inactive)))
}).ToArray();
return results;
}
I moved the code into an Expression Func structure but can't figure out how to replace my code that is setting the AbleToDelete property. Advise?
public Expression<Func<Affect, bool>> DelegateExpression = entity =>
!((entity.Foo1s.Any(pa => !pa.Inactive))
|| (entity.Foo2s.Any(ea => !ea.Inactive))
|| (entity.Foo3s.Any(sa => !sa.Inactive))
|| (entity.Foo4s.Any(spa => !spa.Inactive)));
Last but not least, this solution compiles but fails at runtime with error: "NotSupportedException - The LINQ expression node type 'Invoke' is not supported in LINQ to Entities." Trying to use a delegate which isn't supported:
public override IEnumerable<AffectProjection> SelectAll(SearchAffects page)
{
IQueryable<Affect> query = BuildSearchQuery(page);
//Create delegate instance and register method -- single statement
var deleteAbilityAllowed = new CanDelete(GetAbleToDelete);
IEnumerable<AffectProjection> results = query.Select(entity => new AffectProjection()
{
AffectID = entity.AffectID,
AffectCode = entity.AffectCode,
AffectName = entity.AffectName,
AbleToDelete = deleteAbilityAllowed(entity)
}).ToArray();
return results;
}
public delegate bool CanDelete(Affect entity);
public bool GetAbleToDelete(Affect entity)
{
return !((entity.Foo1s.Any(pa => !pa.Inactive))
|| (entity.Foo2s.Any(ea => !ea.Inactive))
|| (entity.Foo3s.Any(sa => !sa.Inactive))
|| (entity.Foo4s.Any(spa => !spa.Inactive)));
}
Please advise and thank you in advance!
LINQKit contains methods that allow you to do this. With it, you can use yout DelegateExpression. To do that, add AsExpandable() to the source of your query and then invoke the Expression using Invoke():
var expression = DelegateExpression;
var results = query.AsExpandable().Select(entity => new AffectProjection()
{
AffectID = entity.AffectID,
AffectCode = entity.AffectCode,
AffectName = entity.AffectName,
AbleToDelete = expression.Invoke(entity)
}).ToArray();
Be careful though, it seems you can't use the expression directly from the field, it has to be from a local variable.