如何使用匿名投影重构此方法以使其更通用?
How can I refactor this method using anonymous projection to be more generic?
我有的是下面的方法。我使用匿名投影来过滤包含 EF 的内容。我从这篇博文中学到了这种方法:
http://thedatafarm.com/data-access/use-projections-and-a-repository-to-fake-a-filtered-eager-load/
public IEnumerable<Entities.Nutrient> FindAllForSpecificLanguage(bool overridePossibleLogicalDelete)
{
using (var context = CreateObjectContext())
{
context.ContextOptions.LazyLoadingEnabled = false;
Entities.Nutrient[] result;
var list = context.Nutrients
.Select(nut => new
{
Entity = nut,
Descriptions = nut.Descriptions.Where(desc => desc.LanguageCode.Equals(DataLanguageContext.Current.DataLanguageCode))
}).ToList(); //perform query
var resultList = list
.Select(entity => entity.Entity);
return resultList;
}
}
这个方法应该内置到所有服务中(api 支持大约 30 种语言,目前我们有很多 DB 开销...)。
我正在尝试以通用方式构建它,但我在表达式树方面非常缺乏经验。我以为我完全重新创建了该功能,但我遗漏了一些东西,因为它不起作用。
这是我目前所拥有的:
public virtual IEnumerable<TEntity> FindAllForSpecificLanguage(bool overridePossibleLogicalDelete, Expression<Func<TEntity, IEnumerable<object>>> selectEntityDescriptions)
{
using (var context = CreateObjectContext())
{
context.ContextOptions.LazyLoadingEnabled = false;
ObjectQuery<TEntity> queryObjectSet = GetObjectSet(context);
TEntity[] result;
Type anonType = new {Entity = default(TEntity), Descriptions = Enumerable.Empty<object>()}.GetType();
// (entityManagerBaseEntity) => new { Entity = entityManagerBaseEntity, Descriptions = selectEntityDescriptions(entityManagerBaseEntity) }
// 1) "(entityManagerBaseEntity) =>"
var pe = Expression.Parameter(typeof(TEntity), "entityManagerBaseEntity");
// 2) "selectEntityDescriptions(entityManagerBaseEntity)"
var exprFunc = Expression.Invoke(selectEntityDescriptions, pe);
// get constructor for anonymous type
var constructorInfo = anonType.GetConstructor(new[] { typeof(TEntity), typeof(IEnumerable<object>) });
// 3) "new AnonType(entityManagerBaseEntity, exprFunc(entityManagerBaseEntity))"
var constructAnonType = Expression.New(constructorInfo, pe, exprFunc);
// 4) combine all to a lambda
// {entity => new <>f__AnonymousType0`2(entity, Invoke(entity => entity.Descriptions.Where(desc => desc.LanguageCode.Equals(DataLanguageContext.Current.DataLanguageCode)), entity))}
var cooleExpression = Expression.Lambda<Func<TEntity, dynamic>>(constructAnonType, pe);
//var bla = cooleExpression.Compile();
//var list = queryObjectSet.AsQueryable().Provider.CreateQuery<dynamic>(cooleExpression).ToList();
var list = queryObjectSet.Select(cooleExpression).ToList(); //perform query
var resultList = list
.Select(entity => entity.Entity as TEntity);
return resultList;
}
}
(注意:CreateObjectContext 和 GetObjectSet 是完美的工作方法)
应该这样称呼:
_nutrientManager.FindAllForSpecificLanguage(true, (entity) => entity.Descriptions.Where(desc => desc.LanguageCode.Equals(DataLanguageContext.Current.DataLanguageCode)))
构建的表达式在评论中键入。我猜它看起来不错,但从未执行过连接。
如果我调试我得到以下堆栈跟踪:
System.NotSupportedException: Only parameterless constructors and initializers are supported in LINQ to Entities.
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.NewTranslator.TypedTranslate(ExpressionConverter parent, NewExpression linq)
at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
显然,我不知道您项目的更大结构,但是您能否通过将 Entities.Nutrient
替换为 TEntity
来使您的第一个示例更通用?这就是我正在考虑的事情:
public IEnumerable<TEntity> FindAllForSpecificLanguage<TEntity>(
Func<DbContext, IEnumerable<TEntity>> selectEntities,
Func<TEntity, IEnumerable<object>> selectEntityDescriptions)
{
using (var context = CreateObjectContext())
{
context.ContextOptions.LazyLoadingEnabled = false;
var list = selectEntities(context)
.Select(nut => new
{
Entity = nut,
Descriptions = selectEntityDescriptions
}).ToList(); //perform query
var resultList = list
.Select(entity => entity.Entity);
return resultList;
}
}
所以 .. 一些事情:
- 您不能在 L2E 表达式中调用函数
- 只能使用不带参数的构造函数
- 您可以在表达式中自己构建整个 where 子句
完整的解决方案可以在这里找到:
https://github.com/YoeriVD/entity-framework-filter-on-include/blob/master/ExpressionTrees/Program.cs
重要的部分是:
private static void Main(string[] args)
{
FilterOnInclude<Car, Wheel>(car => car.Wheels, wheel => wheel.SizeInInches == 14)
.ForEach(car => Console.WriteLine($"car : {car.Name} wheels: {car.Wheels.Count}"));
}
private static IEnumerable<TEntity> FilterOnInclude<TEntity, TChildEntity>(
Expression<Func<TEntity, IEnumerable<TChildEntity>>> propertyExpression,
Expression<Func<TChildEntity, bool>> predicateExpression)
where TEntity : class
{
using (var context = new CarContext())
{
context.Configuration.LazyLoadingEnabled = false;
var selector = CreateSelector(propertyExpression, predicateExpression);
return
context.Set<TEntity>().Select(
selector
).ToList().Select(e => e.Entity).ToArray();
}
}
private static Expression<Func<TEntity, EntityWithFilteredChildren<TEntity, TChildEntity>>> CreateSelector
<TEntity, TChildEntity>(
Expression<Func<TEntity, IEnumerable<TChildEntity>>> propertyExpression,
Expression<Func<TChildEntity, bool>> predicateExpression)
{
var selectType = typeof (EntityWithFilteredChildren<TEntity, TChildEntity>);
//bind entity
var entityValueParam = Expression.Parameter(typeof (TEntity), "entityValue");
var entityProp = selectType.GetProperty("Entity");
var entityValueAssignment = Expression.Bind(
entityProp, entityValueParam);
//bind collection
var childrenProp = selectType.GetProperty("Children");
var descriptionsMemberExpression = (propertyExpression.Body as MemberExpression);
var descriptionsPropertyInfo = (PropertyInfo) descriptionsMemberExpression.Member;
var descriptionsProperty = Expression.Property(entityValueParam, descriptionsPropertyInfo);
//perform where call
var whereCall = Expression.Call(typeof (Enumerable), "Where", new[] {typeof (TChildEntity)}, descriptionsProperty,
predicateExpression);
var descriptionValueAssignment = Expression.Bind(
childrenProp, whereCall);
var ctor = Expression.New(selectType);
var memberInit = Expression.MemberInit(ctor, entityValueAssignment, descriptionValueAssignment);
var selector = Expression.Lambda<Func<TEntity, EntityWithFilteredChildren<TEntity, TChildEntity>>>(memberInit,
entityValueParam);
return selector;
}
public class EntityWithFilteredChildren<T, TChild>
{
public T Entity { get; set; }
public IEnumerable<TChild> Children { get; set; }
}
我有的是下面的方法。我使用匿名投影来过滤包含 EF 的内容。我从这篇博文中学到了这种方法: http://thedatafarm.com/data-access/use-projections-and-a-repository-to-fake-a-filtered-eager-load/
public IEnumerable<Entities.Nutrient> FindAllForSpecificLanguage(bool overridePossibleLogicalDelete)
{
using (var context = CreateObjectContext())
{
context.ContextOptions.LazyLoadingEnabled = false;
Entities.Nutrient[] result;
var list = context.Nutrients
.Select(nut => new
{
Entity = nut,
Descriptions = nut.Descriptions.Where(desc => desc.LanguageCode.Equals(DataLanguageContext.Current.DataLanguageCode))
}).ToList(); //perform query
var resultList = list
.Select(entity => entity.Entity);
return resultList;
}
}
这个方法应该内置到所有服务中(api 支持大约 30 种语言,目前我们有很多 DB 开销...)。 我正在尝试以通用方式构建它,但我在表达式树方面非常缺乏经验。我以为我完全重新创建了该功能,但我遗漏了一些东西,因为它不起作用。 这是我目前所拥有的:
public virtual IEnumerable<TEntity> FindAllForSpecificLanguage(bool overridePossibleLogicalDelete, Expression<Func<TEntity, IEnumerable<object>>> selectEntityDescriptions)
{
using (var context = CreateObjectContext())
{
context.ContextOptions.LazyLoadingEnabled = false;
ObjectQuery<TEntity> queryObjectSet = GetObjectSet(context);
TEntity[] result;
Type anonType = new {Entity = default(TEntity), Descriptions = Enumerable.Empty<object>()}.GetType();
// (entityManagerBaseEntity) => new { Entity = entityManagerBaseEntity, Descriptions = selectEntityDescriptions(entityManagerBaseEntity) }
// 1) "(entityManagerBaseEntity) =>"
var pe = Expression.Parameter(typeof(TEntity), "entityManagerBaseEntity");
// 2) "selectEntityDescriptions(entityManagerBaseEntity)"
var exprFunc = Expression.Invoke(selectEntityDescriptions, pe);
// get constructor for anonymous type
var constructorInfo = anonType.GetConstructor(new[] { typeof(TEntity), typeof(IEnumerable<object>) });
// 3) "new AnonType(entityManagerBaseEntity, exprFunc(entityManagerBaseEntity))"
var constructAnonType = Expression.New(constructorInfo, pe, exprFunc);
// 4) combine all to a lambda
// {entity => new <>f__AnonymousType0`2(entity, Invoke(entity => entity.Descriptions.Where(desc => desc.LanguageCode.Equals(DataLanguageContext.Current.DataLanguageCode)), entity))}
var cooleExpression = Expression.Lambda<Func<TEntity, dynamic>>(constructAnonType, pe);
//var bla = cooleExpression.Compile();
//var list = queryObjectSet.AsQueryable().Provider.CreateQuery<dynamic>(cooleExpression).ToList();
var list = queryObjectSet.Select(cooleExpression).ToList(); //perform query
var resultList = list
.Select(entity => entity.Entity as TEntity);
return resultList;
}
}
(注意:CreateObjectContext 和 GetObjectSet 是完美的工作方法)
应该这样称呼:
_nutrientManager.FindAllForSpecificLanguage(true, (entity) => entity.Descriptions.Where(desc => desc.LanguageCode.Equals(DataLanguageContext.Current.DataLanguageCode)))
构建的表达式在评论中键入。我猜它看起来不错,但从未执行过连接。 如果我调试我得到以下堆栈跟踪:
System.NotSupportedException: Only parameterless constructors and initializers are supported in LINQ to Entities. at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.NewTranslator.TypedTranslate(ExpressionConverter parent, NewExpression linq) at System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)
显然,我不知道您项目的更大结构,但是您能否通过将 Entities.Nutrient
替换为 TEntity
来使您的第一个示例更通用?这就是我正在考虑的事情:
public IEnumerable<TEntity> FindAllForSpecificLanguage<TEntity>(
Func<DbContext, IEnumerable<TEntity>> selectEntities,
Func<TEntity, IEnumerable<object>> selectEntityDescriptions)
{
using (var context = CreateObjectContext())
{
context.ContextOptions.LazyLoadingEnabled = false;
var list = selectEntities(context)
.Select(nut => new
{
Entity = nut,
Descriptions = selectEntityDescriptions
}).ToList(); //perform query
var resultList = list
.Select(entity => entity.Entity);
return resultList;
}
}
所以 .. 一些事情:
- 您不能在 L2E 表达式中调用函数
- 只能使用不带参数的构造函数
- 您可以在表达式中自己构建整个 where 子句
完整的解决方案可以在这里找到: https://github.com/YoeriVD/entity-framework-filter-on-include/blob/master/ExpressionTrees/Program.cs
重要的部分是:
private static void Main(string[] args)
{
FilterOnInclude<Car, Wheel>(car => car.Wheels, wheel => wheel.SizeInInches == 14)
.ForEach(car => Console.WriteLine($"car : {car.Name} wheels: {car.Wheels.Count}"));
}
private static IEnumerable<TEntity> FilterOnInclude<TEntity, TChildEntity>(
Expression<Func<TEntity, IEnumerable<TChildEntity>>> propertyExpression,
Expression<Func<TChildEntity, bool>> predicateExpression)
where TEntity : class
{
using (var context = new CarContext())
{
context.Configuration.LazyLoadingEnabled = false;
var selector = CreateSelector(propertyExpression, predicateExpression);
return
context.Set<TEntity>().Select(
selector
).ToList().Select(e => e.Entity).ToArray();
}
}
private static Expression<Func<TEntity, EntityWithFilteredChildren<TEntity, TChildEntity>>> CreateSelector
<TEntity, TChildEntity>(
Expression<Func<TEntity, IEnumerable<TChildEntity>>> propertyExpression,
Expression<Func<TChildEntity, bool>> predicateExpression)
{
var selectType = typeof (EntityWithFilteredChildren<TEntity, TChildEntity>);
//bind entity
var entityValueParam = Expression.Parameter(typeof (TEntity), "entityValue");
var entityProp = selectType.GetProperty("Entity");
var entityValueAssignment = Expression.Bind(
entityProp, entityValueParam);
//bind collection
var childrenProp = selectType.GetProperty("Children");
var descriptionsMemberExpression = (propertyExpression.Body as MemberExpression);
var descriptionsPropertyInfo = (PropertyInfo) descriptionsMemberExpression.Member;
var descriptionsProperty = Expression.Property(entityValueParam, descriptionsPropertyInfo);
//perform where call
var whereCall = Expression.Call(typeof (Enumerable), "Where", new[] {typeof (TChildEntity)}, descriptionsProperty,
predicateExpression);
var descriptionValueAssignment = Expression.Bind(
childrenProp, whereCall);
var ctor = Expression.New(selectType);
var memberInit = Expression.MemberInit(ctor, entityValueAssignment, descriptionValueAssignment);
var selector = Expression.Lambda<Func<TEntity, EntityWithFilteredChildren<TEntity, TChildEntity>>>(memberInit,
entityValueParam);
return selector;
}
public class EntityWithFilteredChildren<T, TChild>
{
public T Entity { get; set; }
public IEnumerable<TChild> Children { get; set; }
}