如何将ExpressionTree的结果包装在容器类中?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了如何将ExpressionTree的结果包装在容器类中?相关的知识,希望对你有一定的参考价值。

我正在尝试创建一个与Linq2Sql兼容的帮助程序

我想要做的一般想法是这样的:

internal Expression<Func<TSource, Wrapper<TResult>>>
    Wrap<TSource, TResult>(Expression<Func<TSource, TResult>> dataSelector)
    where TSource : IHasOtherProperty
{
    return (TSource data) => new Wrapper<TResult> {
        Entity = dataSelector(data),
        Extra = data.OtherProperty,
    };
}

所以我可以打电话:

dataStore.Select(Wrap(query))

在我目前打电话的地方

dataStore.Select(query)

现在这需要与Linq2Sql兼容,这意味着它需要作为ExpressionTree完成。

我无法弄清楚如何以EntityFramework友好的方式将dataSelector的值分配给实体

下面是破碎的原型:

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Linq;
using System.Linq.Expressions;

namespace TestLinq
{
    class Program
    {
        static void Main(string[] args)
        {
            var parent = new ParentDomainModel
            {
                ID = Guid.NewGuid(),
            };

            var test = new TestContext { };
            test.Parents.Add(parent);
            test.Metadata.Add(new MetadataDomainModel { ID = Guid.NewGuid(), IsDeleted = false, Key = "test", Value = "value", Parent = parent });
            test.SaveChanges();

            var result = test.Parents
                .WithMetadata<ParentDomainModel, MetadataDomainModel, ParentApiModel>(d => new ParentApiModel { ID = d.ID });

            var materialized = result
                .ToArray();
        }
    }

    public class ParentApiModel : IDescribedEntity
    {
        public Guid ID { get; set; }
        public IDictionary<String, String> Metadata { get; set; }
    }

    public class TestContext : DbContext
    {
        public DbSet<ParentDomainModel> Parents { get; set; }
        public DbSet<MetadataDomainModel> Metadata { get; set; }

        public TestContext() : base()
        {
            this.Database.CommandTimeout = 120;
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
            modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
            modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();

            base.OnModelCreating(modelBuilder);
        }
    }

    public class ParentDomainModel : IDescribedDomainModel<MetadataDomainModel>
    {
        public Guid ID { get; set; }
        public ICollection<MetadataDomainModel> Metadata { get; set; }
    }

    public class MetadataDomainModel : IMetadata
    {
        public Guid ID { get; set; }
        public ParentDomainModel Parent { get; set; }
        public Guid ParentID { get; set; }
        public Boolean IsDeleted { get; set; }
        public String Key { get; set; }
        public String Value { get; set; }
    }

    public class KeyValuePairApiModel<TKey, TValue>
    {
        [JsonProperty("key")]
        public TKey Key { get; set; }

        [JsonProperty("value")]
        public TKey Value { get; set; }
    }

    public interface IDescribedEntity
    {
        IDictionary<String, String> Metadata { get; set; }
    }

    public interface IMetadata
    {
        Guid ParentID { get; set; }

        Boolean IsDeleted { get; set; }

        String Key { get; set; }

        String Value { get; set; }
    }

    public interface IDescribedDomainModel<TMetadata> where TMetadata : IMetadata
    {
        ICollection<TMetadata> Metadata { get; set; }
    }

    public class MetaWrapper<TEntity> where TEntity : IDescribedEntity
    {
        public TEntity Entity { get; set; }
        public IEnumerable<KeyValuePairApiModel<String, String>> Metadata { get; set; }

        public static implicit operator TEntity(MetaWrapper<TEntity> data)
        {
            if (data.Metadata != null)
            {
                var metadata = new Dictionary<String, String>(StringComparer.InvariantCultureIgnoreCase) { };

                foreach (var kvp in data.Metadata)
                {
                    metadata[kvp.Key] = kvp.Value;
                }

                data.Entity.Metadata = metadata;
            }

            return data.Entity;
        }
    }

    internal static class MetadataHelpers
    {
        internal static IEnumerable<TResult> WithMetadata<TSource, TMetadata, TResult>(
            this IQueryable<TSource> data,
            Expression<Func<TSource, TResult>> dataSelector)
            where TMetadata : IMetadata
            where TSource : IDescribedDomainModel<TMetadata>
            where TResult : IDescribedEntity
        {
            var query = data.Select(Wrap<TSource, TMetadata, TResult>(dataSelector));

            return query
                .ToArray()
                .Select(t => (TResult)t);
        }

        internal static Expression<Func<TSource, MetaWrapper<TResult>>> Wrap<TSource, TMetadata, TResult>(
            Expression<Func<TSource, TResult>> dataSelector)
            where TMetadata : IMetadata
            where TSource : IDescribedDomainModel<TMetadata>
            where TResult : IDescribedEntity
        {
            var dataParameter = Expression.Parameter(typeof(TSource), "data");

            Expression<Func<TSource, IEnumerable<KeyValuePairApiModel<String, String>>>> metaSelector = 
                (d) => d.Metadata == null ? null : d.Metadata
                    .Where(m => !m.IsDeleted)
                    .Select(m => new KeyValuePairApiModel<String, String> { Key = m.Key, Value = m.Value });

            var result = Expression.Variable(typeof(MetaWrapper<TResult>));
            var newWrapper = Expression.Assign(result, Expression.New(typeof(MetaWrapper<TResult>)));
            var entityProperty = Expression.Property(result, nameof(MetaWrapper<TResult>.Entity));
            var assignEntity = Expression.Assign(entityProperty, Expression.Invoke(dataSelector, dataParameter));
            var metaProperty = Expression.Property(result, nameof(MetaWrapper<TResult>.Metadata));
            var assignMetadata = Expression.Assign(metaProperty, Expression.Invoke(metaSelector, dataParameter));


            var block = Expression.Lambda<Func<TSource, MetaWrapper<TResult>>>(Expression.Block(new [] { dataParameter }, result, newWrapper, assignEntity, assignMetadata, result), dataParameter);

            return block;
        }
    }
}
答案

块表达式和调用表达式与EF查询转换器不兼容。你需要的是Expression.MemberInit

但是你可以使用Tracking number of expressions matched before .ToListing an EF Linq Query中描述的技术来避免所有这些并发症。基本上,您使用其他参数创建编译时lambda表达式,这些参数用作占位符,使用以下简单的辅助方法替换为另一个表达式:

public static class ExpressionExtensions
{
    public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
    {
        return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
    }

    class ParameterReplacer : ExpressionVisitor
    {
        public ParameterExpression Source;
        public Expression Target;
        protected override Expression VisitParameter(ParameterExpression node) =>
            node == Source ? Target : base.VisitParameter(node);
    }
}

将它应用于您的案例:

internal static Expression<Func<TSource, MetaWrapper<TResult>>> Wrap<TSource, TMetadata, TResult>(
    Expression<Func<TSource, TResult>> dataSelector)
    where TMetadata : class, IMetadata
    where TSource : class, IDescribedDomainModel<TMetadata>
    where TResult : class, IDescribedEntity
{
    Expression<Func<TSource, TResult, MetaWrapper<TResult>>> template = (source, entity) => new MetaWrapper<TResult>
    {
        Entity = entity,
        Metadata = source.Metadata == null ? null : source.Metadata
            .Where(m => !m.IsDeleted)
            .Select(m => new KeyValuePairApiModel<String, String> { Key = m.Key, Value = m.Value }),
    };
    var sourceParameter = template.Parameters[0];
    var entityParameter = template.Parameters[1];
    var entityValue = dataSelector.Body.ReplaceParameter(dataSelector.Parameters[0], sourceParameter);
    var selectorBody = template.Body.ReplaceParameter(entityParameter, entityValue);
    var selector = Expression.Lambda<Func<TSource, MetaWrapper<TResult>>>(selectorBody, sourceParameter);
    return selector;
}

以上是关于如何将ExpressionTree的结果包装在容器类中?的主要内容,如果未能解决你的问题,请参考以下文章

将后缀表示法转换为 ExpressionTree

如何将 H264 包装到 mp4 容器中?

如何在 iOS 应用程序中将传输流转换(重新包装)为 MPEG-4 容器?

将原始 .aac 文件包装到 .m4a 容器中的最简单方法是啥

ExpressionTree实现JSON解析器

用容器包装脚手架以获得渐变背景,如何在颤动中将渐变设置为容器背景?