繁体   English   中英

通过反射使用元组的lambda

[英]lambda using tuple via reflection

我有一些代码可以在不同数据库服务器(源服务器是SQL Server,目标是MySQL)之间同步实体框架POCO。 它是使用[Key]属性来编写的,通常可以正常工作,而且表示被同步数据的POCO知道如何进行比较。

目前的代码如下:

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为目的地DbContextsetpropPropertyInfo的对象DbSet正在同步。

这个问题实际上与实体框架无关,而是linq和反射。 如您所见,TODO注释说-“支持复合键”-上面的代码对于仅带一个键的POCO来说就可以正常工作,但是为了支持复合键,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进行安装让我耳目一新。

首先,您将需要获取要创建的元组的类型,这必须按键数进行硬编码,因为根据项目的数量,Tuple类实际上是一个不同的类:

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

@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属性获取元组类型。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM