Lambda通过元组进行反射



我有一些代码来同步实体框架pocos在不同的数据库服务器之间(源服务器是SQL Server,目标是MySQL)。它写成的是使用[Key]属性一致的工作,以及代表数据同步数据的POCOS知道如何比较自己的事实。

代码当前如下:

var srcdbset = setprop.GetValue(src, null); var dstdbset = setprop.GetValue(dst, null);
var tabletype = srcdbset.GetType().GetGenericArguments().First();
var keys = tabletype.GetProperties().Where(p => p.GetCustomAttributesData().FirstOrDefault(a => a.AttributeType.Name == "KeyAttribute") != null).ToList();
var param = Expression.Parameter(tabletype);  // TODO - support compound keys
var dt = typeof(Func<,>).MakeGenericType(tabletype, keys[0].PropertyType);
var df = Expression.Lambda(dt, Expression.Property(param, keys[0].Name), param).Compile();
var qtodict = typeof(Enumerable).GetMethods().Where(m => m.Name == "ToDictionary").First();
var gtodict = qtodict.MakeGenericMethod(new[] { tabletype, keys[0].PropertyType });
var srcdict = ((IDictionary)gtodict.Invoke(null, new object[] { srcdbset, df }));
var dstdict = ((IDictionary)gtodict.Invoke(null, new object[] { dstdbset, df }));
var qexcept = typeof(Enumerable).GetMethods().Where(m => m.Name == "Except").First();
var gexcept = qexcept.MakeGenericMethod(new[] { keys[0].PropertyType });
dynamic snotd = gexcept.Invoke(null, new object[] { srcdict.Keys, dstdict.Keys });
dynamic dnots = gexcept.Invoke(null, new object[] { dstdict.Keys, srcdict.Keys });

一点帮助-src是源DbContextdst是目标DbContext,而setpropPropertyInfo被同步的CC_7对象。

这个问题确实不是关于实体框架,而是Linq和Reflection。如您所见,TODO评论说 - "支持复合密钥" - 上面的代码仅使用一个密钥对POCOS效果很好,但是为了支持复合键,lambda表达式需要从以下内容中进行更改:

dbcontext.Accounts.ToDictionary(a => a.AccountID);

类似:

dbcontext.OntLocations.ToDictionary(l => Tuple.Create(l.OntID, l.LocationID, l.Index);

上面紧接的两个linq表达式显然是以非通用方式编写的,可以使事情变得更简单 - 我的问题是 - 如何编写元组创建的通用lambda?如果有人可以将我指向正确的方向,我认为其余的代码将按原样工作。

另外,您可能正在考虑 - 为什么他们不使用交易复制 - 长话短说 - 无法找到可靠工作的产品 - 如果有人知道从SQL Server到MySQL良好的产品,并且不需要SQL Server的任何停机时间都安装了我所有的耳朵。

@dave m的答案很好,但是可以通过使用以下方便的Expression.Call Method Overload

Tuple.Create方法的一个呼叫来大大简化整个过程。
public static MethodCallExpression Call(
    Type type,
    string methodName,
    Type[] typeArguments,
    params Expression[] arguments
)

将为您提供大部分工作:

var source = Expression.Parameter(tabletype, "e");
var key = Expression.Call(typeof(Tuple), "Create", 
    keys.Select(pi => pi.PropertyType).ToArray(), // generic type arguments
    keys.Select(pi => Expression.MakeMemberAccess(source, pi)).ToArray() // arguments
);
var keySelector = Expression.Lambda(key, source).Compile();

以后,key.Type属性可用于获取元组类型。

首先,您需要获得需要创建的元组类型,这必须是每个键的硬编码,因为元组类实际上是不同的类,取决于数字项目:

Type tupleType = null;
if(keys.Count == 1) tupleType = typeof(Tuple<>);
else if(keys.Count == 2) tupleType = typeof(Tuple<,>);
else if(keys.Count == 3) tupleType = typeof(Tuple<,,>);
else if(keys.Count == 4) tupleType = typeof(Tuple<,,,>);
//and so on
tupleType = tupleType.MakeGenericType(keys.Select(t=>t.PropertyType).ToArray());

现在,您可以像上面的函数一样制作func:

var lambdaFuncType = typeof(Func<,>).MakeGenericType(tabletype, tupleType);

现在构建表达式以创建元组。我们需要每个密钥的元组构造函数和属性登录器表达式。

var parameterExpression = Expression.Parameter(tabletype);
var constructorExpression = Expression.New(tupleType.GetConstructors()[0], 
    keys.Select(t=>Expression.Property(parameterExpression, t.Name)).ToArray());

现在制作完整的lambda并进行编译:

var compiledExpression = Expression.Lambda(lambdaFuncType, constructorExpression, parameterExpression).Compile();

最新更新