简体   繁体   English

通过反射使用元组的lambda

[英]lambda using tuple via reflection

I have some code to sync Entity Framework POCOs between different database servers (source server is SQL Server, destination is MySQL). 我有一些代码可以在不同数据库服务器(源服务器是SQL Server,目标是MySQL)之间同步实体框架POCO。 It is written to work generically using the [Key] attribute and the fact that the POCOs representing the data being synced know how to compare themselves. 它是使用[Key]属性来编写的,通常可以正常工作,而且表示被同步数据的POCO知道如何进行比较。

The code is currently as follows: 目前的代码如下:

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

A little help - src is the source DbContext and dst is the destination DbContext and setprop is the PropertyInfo object of the DbSet being synced. 有一点帮助- src是源DbContextdst为目的地DbContextsetpropPropertyInfo的对象DbSet正在同步。

This question really isn't so much about Entity Framework, but linq and reflection. 这个问题实际上与实体框架无关,而是linq和反射。 As you can see the TODO comment says - "support compound keys" - the code above works fine for POCOs with just one key, but to support composite keys, the lambda expression needs to change from something like: 如您所见,TODO注释说-“支持复合键”-上面的代码对于仅带一个键的POCO来说就可以正常工作,但是为了支持复合键,lambda表达式需要从以下内容中更改:

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

to something like: 像这样:

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

The two linq expressions immediately above are obviously written in a non generic way to make things simpler to explain - my question is - how do I write a generic lambda that does the tuple create? 上面紧接的两个linq表达式显然是以非通用的方式编写的,这使事情更容易解释-我的问题是-我如何编写由元组创建的通用lambda? If anyone could point me in the right direction on that, I think the rest of the code would work as is. 如果有人能指出正确的方向,我认为其余的代码将照常工作。

Also, you are probably thinking - why aren't they using transactional replication - long story short - unable to find a product that works reliably - if anyone knows of one that works well from SQL Server to MySQL and doesn't require much/any downtime to the SQL Server to install I'm all ears. 另外,您可能在想-他们为什么不使用事务复制-长话短说-无法找到可靠运行的产品-如果有人知道从SQL Server到MySQL都运行良好并且不需要太多/任何功能的产品停机到SQL Server进行安装让我耳目一新。

First you will need to get the type of tuple you need to create, this has to be hard-coded per number of keys since the Tuple class is actually a different class depending on the number of items: 首先,您将需要获取要创建的元组的类型,这必须按键数进行硬编码,因为根据项目的数量,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());

Now you can make the Func as you had it above: 现在,您可以像上面一样制作Func了:

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

Now build the expression to create the tuple. 现在构建表达式以创建元组。 We'll need the tuple constructor and a property accessor expression for each key. 我们将需要每个键的元组构造函数和属性访问器表达式。

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

Now make the full lambda and compile it: 现在制作完整的lambda并进行编译:

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

The answer by @Dave M is fine, but the whole thing can be greatly simplified by emitting a call to one of the Tuple.Create method overloads using the following handy Expression.Call method overload @Dave M的答案很好,但是通过发出对其中一个Tuple.Create方法的重载可以大大简化整个过程,使用以下方便的Expression.Call方法重载

public static MethodCallExpression Call(
    Type type,
    string methodName,
    Type[] typeArguments,
    params Expression[] arguments
)

which will do the most of the work for you: 它将为您完成大部分工作:

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

Later on, key.Type property can be used to get the tuple type. 稍后,可以使用key.Type属性获取元组类型。

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

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