我正在尝试创建一个与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;
}