Использование LINQ для динамического отображения (или построения проекций) - PullRequest
3 голосов
/ 28 мая 2010

Я знаю, что могу сопоставить два типа объектов с помощью 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
}

Я знаю, что это сложно, но я надеюсь, что это невозможно, потому что это сэкономит мне кучу явных картографических работ между моделями и моделями представления.

1 Ответ

6 голосов
/ 28 мая 2010

Вы должны сгенерировать дерево выражений, но простое, так что это не так сложно ...

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 s)

Сгенерированное проекционное выражение выглядит так:

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