【问题标题】:Using LINQ to map dynamically (or construct projections)使用 LINQ 动态映射(或构造投影)
【发布时间】:2013-04-18 22:55:44
【问题描述】:

我知道我可以使用投影映射两种对象类型与 LINQ:

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
}

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

【问题讨论】:

    标签: linq mapping map-projections


    【解决方案1】:

    你必须生成一个表达式树,但是一个简单的,所以它不是那么难......

    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 中测试,因此是 Dumps)

    生成的投影表达式如下所示:

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

    【讨论】:

    • 感谢您提供此解决方案。我深入了解它是如何工作的。如果我想让它钻入复杂的对象,我将不得不改变 propertyMap,对吗?
    • 如果你想了解表达式是如何构造的,我建议你使用LinqPad;它允许您轻松检查表达式的每个节点。至于你的问题,我不确定我理解你的意思......如果你只知道源和目标类型,你真的不能做比复制同名属性更复杂的事情了。
    • 如果你想合并复杂的对象以便 item => new DestModelType() {A = item.A.X, C = item.C, E = item.E}。这可以通过一个属性属性来指定映射到什么。
    • 我认为这会更难,但仍然可行...您必须根据自己的逻辑更改propertyMapmemberBindings
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-06-17
    • 2013-02-10
    相关资源
    最近更新 更多