Можно ли изменить параметр универсального типа делегата Func‹T, bool›?

Я предполагаю, что ответ на этот вопрос «нет», но в любом случае.

По сути, у меня есть поставщик данных Linq2Sql с его сгенерированными типами. У меня также есть бизнес-объекты, имена свойств которых (ради этого вопроса) точно соответствуют именам свойств связанных с ними сгенерированных типов. Бизнес-типы используются во всем приложении, а сгенерированные типы используются ТОЛЬКО для доступа к базе данных — эта настройка желательна по ряду причин, поэтому, пожалуйста, не предлагайте ответы, которые требуют каких-либо изменений в этом.

На уровне пользовательского интерфейса есть различные элементы управления, которые позволяют пользователю настраивать способ поиска, например. какие поля искать, условия поиска и т. д. Используя эти элементы управления, я могу создать хороший делегат Func<T, bool> для инкапсуляции условий/запроса поиска. Проблема, с которой я столкнулся, заключается в том, что делегат Func создается с параметром типа T, являющимся бизнес-объектом, и когда он передается на уровень доступа к данным, мне нужно, чтобы вместо этого он был связанного сгенерированного типа.

Итак, мой вопрос: можно ли изменить параметр универсального типа делегата Func с типа бизнес-объекта на связанный сгенерированный тип при сохранении тех же условий?

eg. can Func<MasterTrack, bool>   =>   Func<DbMasterTrack, bool> when properties match?

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


person Sheridan    schedule 16.08.2012    source источник
comment
Создайте интерфейс IMasterTrack и используйте его в Func. Оба ваших класса должны реализовать IMasterTrack. Надеюсь, это сработает.   -  person Leri    schedule 16.08.2012
comment
Можете ли вы показать нам, как выглядят ваши объекты MasterTrack и DbMasterTrack, то есть существует ли между ними какая-либо форма наследования, они следуют общему контракту interface или абстракции, или это просто два отдельных конкретных класса с одинаковой структурой свойств?   -  person Paul Aldred-Bann    schedule 16.08.2012
comment
Как говорит @Maarten, Linq2Sql will NOT accept an interface-type in its expression.   -  person Sheridan    schedule 16.08.2012


Ответы (2)


Я не верю, что это возможно, но вы можете уйти, выполнив следующие действия:

  • Сделать DbMasterTrack неявно конвертируемым в MasterTrack;
  • При запросе просто оберните Func<MasterTack,bool> в Func<DbMasterTrack,bool>.

Я также должен отметить, что если вы используете Func<T, bool> вместо Expression<Func<T, bool>, вы на самом деле не фильтруете результирующий набор на уровне базы данных, но это может быть то, о чем вы уже знаете.

Пример следующий:

class MasterTrack
{
    public int Id { get; set; }
    public string Name { get; set; }
}
class DbMasterTrack
{
    public int Id { get; set; }
    public string Name { get; set; }
    public static implicit operator MasterTrack(DbMasterTrack @this)
    {
        return new MasterTrack { Id = @this.Id, Name = @this.Name };
    }
}
class Program
{
    static void Main(string[] args)
    {
        var tracks = new List<DbMasterTrack>
        {
            new DbMasterTrack { Id = 1, Name = "T1" },
            new DbMasterTrack { Id = 2, Name = "T2" },
        };

        Func<MasterTrack, bool> query = t => t.Id == 1;

        var result = tracks.Where((Func<DbMasterTrack, bool>)(t => query(t)));

        foreach (var item in result)
        {
            Console.WriteLine("{0}|{1}", item.Id, item.Name);
        }
    }
}
person João Angelo    schedule 16.08.2012
comment
Мне нравится идея этого решения, и я благодарен за то, что обнаружил implicit operator, так что +1 за это. Я пытался реализовать ваше решение (отсюда и задержка с ответом), но я упал на последнем препятствии! Все компилируется нормально, но во время выполнения я получил исключение, указывающее, что «Метод MasterTrack op_Implicit (DbMasterTrack)» не поддерживает преобразование в SQL». Для меня это выглядит как шоу-стопор, но если вы можете помочь с этим, я был бы очень признателен. - person Sheridan; 16.08.2012
comment
Причина, по которой вы это видите, заключается в том, что t => query(t) считается Expression<Func<T, bool>>, а затем LINQ2SQL пытается преобразовать его в SQL. Если вы примените его к Func<T, bool>, это должно сработать, но я снова должен предупредить вас, что вы не будете фильтровать базу данных. Если вы хотите фильтровать базу данных, вам нужно использовать Expression<Func<T,bool>> с самого начала и попробовать подход Маартена. - person João Angelo; 16.08.2012
comment
Аааа. Большое спасибо за указание на то, что Func‹T, bool› выполняет фильтрацию локально, что, конечно, имеет большое значение. Вместо этого мне придется использовать общий класс Expression. Все равно большое спасибо, и я уверен, что снова воспользуюсь материалом implicit. - person Sheridan; 16.08.2012

Пара вещей

  1. Linq2Sql также может использовать Expression<Func<T, bool>> вместо Func<T, bool>.

  2. Невозможно изменить тип Expression<Func<T, bool>>

  3. Можно ли скопировать/воссоздать Expression<Func<T, bool>>, где вы замените тип T другим типом.

  4. Linq2Sql НЕ будет принимать тип интерфейса в своем выражении. Поэтому, если вы думаете о создании интерфейсов для «абстракции» фактического типа, это не сработает.

Теперь, чтобы создать Expression<Func<T2, bool>> из Expression<Func<T, bool>>, я однажды создал следующий код. Он не является «полным», так как поддерживаются не все возможные пути в выражении. Но основные и/или комбинации, в которых вы проверяете свойства на наличие значений (‹ > = != или комбинации), тогда работали нормально.

Используя этот код, вы можете сделать:

Expression<Func<MasterTrack, bool>> criteria = m => m.Id == 1;
Expression<Func<DbMasterTrack, bool>> dbCriteria = ExpressionRewriter.CastParam<MasterTrack, DbMasterTrack>(criteria);

Вот так.

public static class ExpressionRewriter {
    /// <summary>
    /// Casts the param of an expression.
    /// </summary>
    /// <typeparam name="TIn">The type of the in.</typeparam>
    /// <typeparam name="TOut">The type of the out.</typeparam>
    /// <param name="inExpr">The in expr.</param>
    /// <returns></returns>
    public static Expression<Func<TOut, bool>> CastParam<TIn, TOut>(Expression<Func<TIn, bool>> inExpr) {
        if (inExpr.NodeType == ExpressionType.Lambda &&
            inExpr.Parameters.Count > 0) {

            var inP = inExpr.Parameters[0];
            var outP = Expression.Parameter(typeof(TOut), inP.Name);

            var outBody = Rewrite<TIn, TOut>(
                inExpr.Body,
                expr => (expr is ParameterExpression) ? outP : expr
            );
            return Expression.Lambda<Func<TOut, bool>>(
                    outBody,
                    new ParameterExpression[] { outP });
        } else {
            throw new NotSupportedException();
        }
    }

    /// <summary>
    /// Rewrites the specified expression.
    /// </summary>
    /// <typeparam name="TIn">The type of the in.</typeparam>
    /// <typeparam name="TOut">The type of the out.</typeparam>
    /// <param name="exp">The exp.</param>
    /// <param name="c">The c.</param>
    /// <returns></returns>
    private static Expression Rewrite<TIn, TOut>(Expression exp, Func<Expression, Expression> c) {
        Expression clone = null;
        var be = exp as BinaryExpression;
        switch (exp.NodeType) {
            case ExpressionType.AndAlso:
                clone = Expression.AndAlso(Rewrite<TIn, TOut>(be.Left, c), Rewrite<TIn, TOut>(be.Right, c), be.Method);
                break;
            case ExpressionType.OrElse:
                clone = Expression.OrElse(Rewrite<TIn, TOut>(be.Left, c), Rewrite<TIn, TOut>(be.Right, c), be.Method);
                break;
            case ExpressionType.Equal:
                clone = Expression.Equal(Rewrite<TIn, TOut>(be.Left, c), Rewrite<TIn, TOut>(be.Right, c), be.IsLiftedToNull, be.Method);
                break;
            case ExpressionType.GreaterThan:
                clone = Expression.GreaterThan(Rewrite<TIn, TOut>(be.Left, c), Rewrite<TIn, TOut>(be.Right, c), be.IsLiftedToNull, be.Method);
                break;
            case ExpressionType.GreaterThanOrEqual:
                clone = Expression.GreaterThanOrEqual(Rewrite<TIn, TOut>(be.Left, c), Rewrite<TIn, TOut>(be.Right, c), be.IsLiftedToNull, be.Method);
                break;
            case ExpressionType.LessThan:
                clone = Expression.LessThan(Rewrite<TIn, TOut>(be.Left, c), Rewrite<TIn, TOut>(be.Right, c), be.IsLiftedToNull, be.Method);
                break;
            case ExpressionType.LessThanOrEqual:
                clone = Expression.LessThanOrEqual(Rewrite<TIn, TOut>(be.Left, c), Rewrite<TIn, TOut>(be.Right, c), be.IsLiftedToNull, be.Method);
                break;
            case ExpressionType.NotEqual:
                clone = Expression.NotEqual(Rewrite<TIn, TOut>(be.Left, c), Rewrite<TIn, TOut>(be.Right, c), be.IsLiftedToNull, be.Method);
                break;
            case ExpressionType.Not:
                var ue = exp as UnaryExpression;
                clone = Expression.Not(Rewrite<TIn, TOut>(ue.Operand, c));
                break;
            case ExpressionType.MemberAccess:
                var me = exp as MemberExpression;

                MemberInfo newMember = me.Member;
                Type newType = newMember.DeclaringType;
                if (newType == typeof(TIn)) {
                    newType = typeof(TOut);
                    MemberInfo[] members = newType.GetMember(me.Member.Name);
                    if (members.Length == 1) {
                        newMember = members[0];
                    } else {
                        throw new NotSupportedException();
                    }
                }
                clone = Expression.MakeMemberAccess(Rewrite<TIn, TOut>(me.Expression, c), newMember);
                break;
            case ExpressionType.Constant:
                var ce = exp as ConstantExpression;
                clone = Expression.Constant(ce.Value);
                break;
            case ExpressionType.Parameter:
                var pe = exp as ParameterExpression;
                Type peNewType = pe.Type;
                if (peNewType == typeof(TIn)) {
                    peNewType = typeof(TOut);
                }
                clone = Expression.Parameter(peNewType, pe.Name);
                break;
            case ExpressionType.Call:
                MethodCallExpression mce = exp as MethodCallExpression;
                if (mce.Arguments != null && mce.Arguments.Count > 0) {
                    List<Expression> expressionList = new List<Expression>();
                    foreach (Expression expression in mce.Arguments) {
                        expressionList.Add(Rewrite<TIn, TOut>(expression, c));
                    }
                    clone = Expression.Call(Rewrite<TIn, TOut>(mce.Object, c), mce.Method, expressionList.ToArray());
                } else {
                    clone = Expression.Call(Rewrite<TIn, TOut>(mce.Object, c), mce.Method);
                }
                break;
            case ExpressionType.Invoke:
                InvocationExpression ie = exp as InvocationExpression;
                List<Expression> arguments = new List<Expression>();
                foreach (Expression expression in ie.Arguments) {
                    arguments.Add(Rewrite<TIn, TOut>(expression, c));
                }
                clone = Rewrite<TIn, TOut>(ie.Expression, c);
                //clone = Expression.Invoke(Rewrite<TIn, TOut>(ie.Expression, c), arguments);
                break;
            case ExpressionType.Convert:
                var ue2 = exp as UnaryExpression;
                //clone = Expression.Not(Rewrite<TIn, TOut>(ue2.Operand, c));
                clone = Expression.Convert(ue2.Operand, ue2.Type, ue2.Method);
                break;
            default:
                throw new NotImplementedException(exp.NodeType.ToString());
        }
        return c(clone);
    }
}
person Maarten    schedule 16.08.2012
comment
Опять же, мне нравится это решение, так что +1 за это... в конце концов, это самый близкий ответ на мой вопрос. Сначала реализовав решение @Joao Angelo, я попытаюсь заставить все это работать правильно, но если это не удастся, я вернусь к этому. Большое спасибо. - person Sheridan; 16.08.2012
comment
Попробовав ваше решение, я боюсь сказать, что оно тоже не сработало. Я получил исключение, потому что в вашем методе Rewrite нет Case вместо ExpressionType.Lambda. - person Sheridan; 16.08.2012
comment
Вы изменили свой тип на Expression‹Func‹MasterTrack, bool››? И из чего состоит ваше выражение? - person Maarten; 16.08.2012
comment
Да. Сейчас я использую отличный PredicateBuilder с сайта albahari.com/nutshell/predicatebuilder.aspx. . Внутри он просто содержит такие операторы (но с пометкой «и» для разных свойств): m => m.Title.Contains(SearchTerm). - person Sheridan; 16.08.2012