使用LINQ进行动态映射(或构建投影)

3

我知道可以使用LINQ将两种对象类型映射起来,方法是使用投影(projection):

var destModel = from m in sourceModel
               select new DestModelType {A = m.A, C = m.C, E = m.E}

在哪里

class SourceModelType
{
    string A {get; set;}
    string B {get; set;}
    string C {get; set;}
    string D {get; set;}
    string E {get; set;}
}

class DestModelType
{
    string A {get; set;}
    string C {get; set;}
    string E {get; set;}
}

但如果我想创建一个类似于通用方法的东西,我不知道具体处理的两种类型。因此,它将遍历“Dest”类型并与匹配的“Source”类型相匹配。这可能吗?并且,为了实现延迟执行,我只想返回一个IQueryable。

例如:

public IQueryable<TDest> ProjectionMap<TSource, TDest>(IQueryable<TSource> sourceModel)
{
   // dynamically build the LINQ projection based on the properties in TDest

   // return the IQueryable containing the constructed projection
}

我知道这很具有挑战性,但我希望这不是不可能的,因为这将为我节省大量模型和视图模型之间的显式映射工作。

1个回答

6

你需要生成一个表达式树,但是要尽量简单,这样就不会太难了...

void Main()
{
    var source = new[]
    {
        new SourceModelType { A = "hello", B = "world", C = "foo", D = "bar", E = "Baz" },
        new SourceModelType { A = "The", B = "answer", C = "is", D = "42", E = "!" }
    };

    var dest = ProjectionMap<SourceModelType, DestModelType>(source.AsQueryable());
    dest.Dump();
}

public static IQueryable<TDest> ProjectionMap<TSource, TDest>(IQueryable<TSource> sourceModel)
    where TDest : new()
{
    var sourceProperties = typeof(TSource).GetProperties().Where(p => p.CanRead);
    var destProperties =   typeof(TDest).GetProperties().Where(p => p.CanWrite);
    var propertyMap = from d in destProperties
                      join s in sourceProperties on new { d.Name, d.PropertyType } equals new { s.Name, s.PropertyType }
                      select new { Source = s, Dest = d };
    var itemParam = Expression.Parameter(typeof(TSource), "item");
    var memberBindings = propertyMap.Select(p => (MemberBinding)Expression.Bind(p.Dest, Expression.Property(itemParam, p.Source)));
    var newExpression = Expression.New(typeof(TDest));
    var memberInitExpression = Expression.MemberInit(newExpression, memberBindings);
    var projection = Expression.Lambda<Func<TSource, TDest>>(memberInitExpression, itemParam);
    projection.Dump();
    return sourceModel.Select(projection);
}

(在 LinqPad 中测试,因此有 Dump

生成的投影表达式如下:

item => new DestModelType() {A = item.A, C = item.C, E = item.E}

感谢您提供的解决方案。我正在深入了解它的工作原理。如果我想让它深入到复杂对象中,我需要更改propertyMap,对吗? - CodeGrue
1
如果您想了解表达式的构造方式,我建议您使用LinqPad;它可以让您轻松检查表达式的每个节点。至于您的问题,我不确定我理解您的意思...如果您只知道源类型和目标类型,那么您不能做比复制具有相同名称的属性更复杂的事情。 - Thomas Levesque
如果您想要将复杂对象合并到代码中,该怎么办呢?例如:item => new DestModelType() {A = item.A.X, C = item.C, E = item.E}。这可以通过属性标记来指定映射内容来实现。 - CodeGrue
我认为这会更加困难,但仍然可行...您将不得不根据自己的逻辑更改propertyMapmemberBindings - Thomas Levesque

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接