Как преобразовать Expression‹Func‹T, bool›› в Expression‹Func‹Type, bool››?

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

public class LoadEntityQuery : IQuery<LoadEntityQueryResult>
{
    public IDictionary<Type, Expression<Func<Type, bool>>> Entities { get; set; } 
    public LoadEntityQuery()
    {
        Entities = new Dictionary<Type, Expression<Func<Type, bool>>>();
    }

    public void Add<T>(Expression<Func<T, bool>> where = null) where T : Entity
    {
        Expression<Func<Type, bool>> _lambda = null;

        if (where != null)
        {
            ParameterExpression param = Expression.Parameter(typeof(T), where.Parameters[0].Name);

            var body = Expression.Invoke(where, param);
            _lambda = Expression.Lambda<Func<Type, bool>>(body, param);
        }

        Entities.Add(typeof(T), _lambda);
    }
}

Тело нового метода создано правильно. Проблема в том, что когда я пытаюсь создать новое лямбда-выражение с типом из передаваемого выражения, я получаю эту ошибку:

ParameterExpression типа TestNamespace.TestClass нельзя использовать для параметра делегата типа System.Type.

У кого-нибудь есть идея, что я могу сделать в этой ситуации? Как я уже говорил, в какой-то момент позже я пройдусь по этому словарю, чтобы выполнить рефлексивное программирование для каждой записи. Если есть лучший способ сделать это, я весь внимание.

В качестве примера того, что я пытаюсь сделать, я храню выражения для предложений Where для объектов POCO, которые необходимо инициализировать:

LoadEntityQuery _query = new LoadEntityQuery();
    _query.Add<PayrollLocation>();
    _query.Add<PayrollGroupBU>();
    _query.Add<PersonnelPosition>(t => t.DataSet == MasterDataSet);
    _query.Add<EmployeeStatus>();
    _query.Add<PayrollGrade>();

Этот список сущностей будет отличаться для каждого приложения. Идея состоит в том, чтобы собрать все сущности и предложение Where для каждой и найти определенный метод, используя отражение для каждой из них. (например, PayrollLocation имеет метод GetPayrollLocationsQuery(), PayrollGroupBU имеет метод GetPayrollGroupBUQuery()...). Метод Add является универсальным, чтобы я мог использовать лямбда-выражение в вызывающем коде.

Спасибо, Джейсон


person Rockdocta    schedule 10.05.2011    source источник


Ответы (2)


Присмотревшись к вашему коду, вы увидите, что сгенерированное вами выражение имеет некоторые проблемы. См. мое объяснение вверху этого ответа, чтобы объяснить один из них, это та же проблема здесь. Вы создаете новую лямбду, в которой созданный вами экземпляр параметра не используется в теле.

Большая проблема заключается в том, что ваши выражения просто не соответствуют тому, что вы пытаетесь сделать. Насколько я могу судить, вы просто пытаетесь создать сопоставление типов сущностей с функциями, которые принимают сущность этого типа и возвращают логическое значение. Type -> Expression<Func<TEntity, bool>>. Выражение, которое вы строите, просто не работает.

Вы должны сделать так, чтобы словарь хранил неуниверсальные лямбда-выражения, чтобы вы могли легко хранить эти функции без выполнения преобразований или перестроения выражений. Вы не сможете хранить их здесь как общие лямбда-выражения. Затем приведите к общей лямбде при доступе к ним. Я бы поместил это в отдельный класс, чтобы управлять кастингом и рефакторингом вашего кода:

// add all necessary error checking where needed and methods
public class EntityPredicateDictionary
{
    private Dictionary<Type, LambdaExpression> dict = new Dictionary<Type, LambdaExpression>();

    public Expression<Func<TEntity, bool>> Predicate<TEntity>() where TEntity : Entity
    {
        return (Expression<Func<TEntity, bool>>)dict[typeof(TEntity)];
    }

    public LambdaExpression Predicate(Type entityType)
    {
        return dict[entityType];
    }

    internal void Add<TEntity>(Expression<Func<TEntity, bool>> predicate) where TEntity : Entity
    {
        dict.Add(typeof(TEntity), predicate);
    }
}

public class LoadEntityQuery : IQuery<LoadEntityQueryResult>
{
    public EntityPredicateDictionary Entities { get; private set; }
    public LoadEntityQuery()
    {
        Entities = new EntityPredicateDictionary();
    }

    public void Add<TEntity>(Expression<Func<TEntity, bool>> predicate = null) where TEntity : Entity
    {
        Entities.Add(predicate);
    }
}

// then to access the predicates
LoadEntityQuery query = ...;
var pred1 = query.Entities.Predicate<Entity1>();
var pred2 = query.Entities.Predicate(typeof(Entity2));
person Jeff Mercado    schedule 10.05.2011
comment
Идеально! Спасибо, Джефф - это реальная помощь (как и ваш другой пост). Мне нужно многое узнать о LambdaExpressions, я все еще новичок в них. Спасибо за помощь. - person Rockdocta; 11.05.2011

Я не думаю, что это будет делать то, что вы ожидаете; Func<Type, bool> определяет функцию, которая принимает в качестве параметра тип и возвращает логическое значение. Func<T, bool> определяет функцию, которая принимает в качестве параметра объект типа T и возвращает логическое значение. Лямбда, определенная в вашем словаре, никогда не получит объект, который вы пытаетесь фильтровать, только его тип.

Для меня самым быстрым способом сделать это любым подходящим способом было бы сделать класс LoadEntityQuery универсальным для типа параметра, который вы ожидаете, что ваша функция примет, но это, вероятно, ограничит вас другими способами...

Вы можете использовать объект и бросить его... Не лучшее решение, но, по крайней мере, оно инкапсулирует литье и представляет собой довольно небольшую часть, но при этом позволяет вам делать то, что, как мне кажется, вам нужно.

public class LoadEntityQuery : IQuery<LoadEntityQueryResult>
{
    // Note: Hide this, we don't want anyone else to have to think about the cast.
    private IDictionary<Type, object> Entities { get; set; }

    public void Add<T>(Expression<Func<T, bool>> where = null) where T : Entity
    {
        Entities.Add(typeof(T), where);
    }

    public Expression<Func<T, bool>> Retrieve<T>() where T : Entity
    {
        if (!Entities.ContainsKey(typeof(T)))
            return null;
        return (Expression<Func<T, bool>>)Entities[typeof(T)];
    }
}
person Chris Shaffer    schedule 10.05.2011
comment
У меня изначально был такой дизайн - он в некотором роде ограничивает. - person Rockdocta; 10.05.2011
comment
@Rockdocta - я добавил альтернативное решение, может быть, оно хотя бы натолкнет вас на идею попробовать. - person Chris Shaffer; 11.05.2011