lambda使用元组通过反射

问题描述:

我有一些代码来同步实体框架POCOs之间不同的数据库服务器(源服务器是SQL Server,目标是MySQL)。它被写为通常使用[Key]属性的工作,并且表示正在同步的数据的POCO知道如何比较它们自己。lambda使用元组通过反射

的代码是目前如下:

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是目的地DbContextsetprop是进行同步处理的DbSetPropertyInfo对象。

这个问题其实不是关于实体框架,而是linq和反射。正如你可以看到TODO注释说 - “支持复合键” - 上面的代码工作正常波苏斯只有一个键,但支持组合键,lambda表达式需要从一些改变,如:

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

喜欢的东西:

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

两个LINQ表达式正上方有明显写在一个非通用的方式,使事情变得更简单解释 - 我的问题是 - 我怎么写就的元组创建一个通用的拉姆达?如果任何人都可以指出我正确的方向,我认为其余的代码将按原样工作。另外,你可能正在想 - 为什么他们不使用事务复制 - 长话短说 - 无法找到可靠工作的产品 - 如果有人知道从SQL Server到MySQL的效果很好,而且不会需要很多/任何宕机时间来安装SQL Server我全是耳朵。

通过@Dave M答案是好的,但整个事情可以通过发射一个呼叫使用大大简化到Tuple.Create方法重载之一下面的实用Expression.Call方法重载

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属性可以用来获取元组类型。

+0

非常感谢你们两位 - 通过对这两个答案的深入了解,我可以添加额外5行代码来支持复合键! – MolallaComm

首先,你需要得到你需要创建一个元组类型,这有每个按键的数量进行硬编码,因为元组类实际上是一个不同的类取决于项目的数量:

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

现在做出全面的λ和编译:

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