С# Как преобразовать выражение <Func <SomeType> в выражение <Func <OtherType>>

Я использовал выражения С# раньше, чем на основе lamdas, но у меня нет опыта составлять их вручную. Учитывая Expression<Func<SomeType, bool>> originalPredicate, я хочу создать Expression<Func<OtherType, bool>> translatedPredicate.

В этом случае SomeType и OtherType имеют одинаковые поля, но они не связаны (нет наследования и не основаны на общем интерфейсе).

Справочная информация. У меня есть реализация репозитория на основе LINQ to SQL. Я проектирую объекты LINQ to SQL для своих объектов модели, чтобы сохранить мою модель в POCO. Я хочу передать выражения в репозиторий (как форму спецификации), но они должны основываться на объектах модели. Но я не могу передать эти выражения в контексте данных, так как он ожидает выражения, основанные на объектах LINQ to SQL.

Ответы

Ответ 1

С Expression самый простой способ - преобразование выражение:

class Foo {
    public int Value { get; set; }
}
class Bar {
    public int Value { get; set; }
}
static class Program {
    static void Main() {
        Expression<Func<Foo, bool>> predicate =
            x => x.Value % 2 == 0;
        Expression<Func<Bar, Foo>> convert =
            bar => new Foo { Value = bar.Value };

        var param = Expression.Parameter(typeof(Bar), "bar");
        var body = Expression.Invoke(predicate,
              Expression.Invoke(convert, param));
        var lambda = Expression.Lambda<Func<Bar, bool>>(body, param);

        // test with LINQ-to-Objects for simplicity
        var func = lambda.Compile();
        bool withOdd = func(new Bar { Value = 7 }),
             withEven = func(new Bar { Value = 12 });
    }
}

Обратите внимание, что это будет поддерживаться разными поставщиками. EF может не понравиться, например, даже если LINQ-to-SQL делает.

Другой вариант - перестроить дерево выражений полностью, используя отражение, чтобы найти соответствующие элементы. Гораздо сложнее.

Ответ 2

Есть еще один способ, который я нашел, который также включает в себя перенос оригинального делегата.

Func<T, object> ExpressionConversion<U>(Expression<Func<T, U>> expression)
{
    Expression<Func<T, object>> g = obj => expression.Compile().Invoke(obj);
    return g.Compile();
}

Ответ 3

Существует неявный способ сделать перевод. Вы должны обернуть существующий делегат внутри лямбда, который создает новый тип из типа аргумента:

var translatedPredicate = x => originalPredicate(OtherTypeFromSomeType(x))

Где OtherTypeFromSomeType создает экземпляр OtherType из аргумента SomeType.

Ответ 4

У меня была та же проблема, что и вы, и я исправил ее с помощью EF:

var viewModeValue = dbContext.Model.Select(m => new ViewModel{Field = m.Field}).Where(predicate) //predicate is an Expression<Func<ViewModel, bool>>

Entity Framework знает, как создать правильную команду sql. Преобразование выражения намного сложнее, потому что оно построено как неизменное и может вызвать нежелательные эффекты времени выполнения, если вы делаете что-то неправильно и, по крайней мере, в моем случае, это не нужно.