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
是源DbContext
和dst
是目的地DbContext
和setprop
是进行同步处理的DbSet
的PropertyInfo
对象。
这个问题其实不是关于实体框架,而是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
属性可以用来获取元组类型。
首先,你需要得到你需要创建一个元组类型,这有每个按键的数量进行硬编码,因为元组类实际上是一个不同的类取决于项目的数量:
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();
非常感谢你们两位 - 通过对这两个答案的深入了解,我可以添加额外5行代码来支持复合键! – MolallaComm