【问题标题】:How can I refactor this method using anonymous projection to be more generic?如何使用匿名投影重构此方法以使其更通用?
【发布时间】:2015-03-31 07:35:45
【问题描述】:

我有以下方法。我使用匿名投影来过滤包含 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种语言,目前我们有很多数据库开销......)。 我正在尝试以通用方式构建它,但我在表达式树方面非常缺乏经验。我以为我完全重新创建了该功能,但我遗漏了一些东西,因为它不起作用。 这是我到目前为止所拥有的:

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)))

在 cmets 中输入构建的表达式。我猜它看起来不错,但从未执行过连接。 如果我调试,我会得到以下堆栈跟踪:

System.NotSupportedException:LINQ to Entities 中仅支持无参数构造函数和初始化程序。 在 System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.NewTranslator.TypedTranslate(ExpressionConverter 父级,NewExpression linq) 在 System.Data.Entity.Core.Objects.ELinq.ExpressionConverter.TypedTranslator`1.Translate(ExpressionConverter parent, Expression linq)

【问题讨论】:

    标签: entity-framework generics linq-to-entities expression-trees projection


    【解决方案1】:

    所以..有几件事:

    1. 不能在 e L2E 表达式中调用函数
    2. 只能使用不带参数的构造函数
    3. 您可以在表达式中自己构建整个 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; }
        }
    

    【讨论】:

    • 您的 Console.WriteLine 中的 $ 是什么?是增强的 c# 吗?
    • C# 6 功能,对于这些实验,我喜欢使用 VS2015 :-) 只是编写 string.Format() 的好方法
    • 我会说!不再需要重新编号字符串格式项目或混乱的格式占位符!
    • 确实少了一点痛苦:')
    【解决方案2】:

    显然,我不知道您项目的更大结构,但是您能否通过将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;
        }
    }
    

    【讨论】:

    • Linq to entity 对它可以转换为 sql 的内容的支持有限。不允许方法调用。我昨天晚上解决了这个问题,示例发布在我的github上。
    • 如果您有兴趣,我发布了解释:-)
    • 如果你已经解决了,你应该发布你的问题的答案。但是,我的解决方案中没有方法调用,所以你确定它不起作用吗?
    • 它对我不起作用(至少我认为),因为我在函数中有一个匿名类型,它是在另一个程序集中创建的,因此没有共享。解决方案可能是使用命名类型(就像我在示例中所做的那样),但我的示例可以轻松调整为使用您真正想要的匿名类型 + 签名要简单得多。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-27
    • 1970-01-01
    • 2019-12-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多