Запрос свойств интерфейса с помощью EntityFramework и LinqKit

Я использую EntityFramework и LinqKit для построения деревьев выражений, которые переводятся в SQL. Также мы используем шаблон спецификации для организации наших запросов.

Почти во всех объектах нашей предметной области есть необходимость выполнять запрос по описанию, но в некоторых из этих классов свойство называется «Имя», в других — «Описание» и т. д.

Итак, изначально был определен интерфейс следующим образом:

public interface IDescritible
{
    string Description { get; }
}

Проблема возникла, когда я попытался использовать его, явно реализованный в классе, и сделал при этом общий запрос:

public class Foo : IDescritible
{
    public string Name { get; set; }

    string IDescritible.Description
    {
        get { return this.Name; }
    }
}

public class DescriptionMatch<T> : AbstractSpecification<T> 
    where T : IDescritible
{
    public string Query { get; set; }

    public DescriptionMatch(string query)
    {
        this.Query = query;
    }

    public override Expression<Func<T, bool>> IsSatisfiedBy()
    {
        return x => x.Description.Contains(Query);
    }
}

При запуске EntityFramework не может преобразовать свойство get (вызов метода) в выражение SQL.

Затем я попытался определить выражение в классе следующим образом:

public interface IDescritible<T> where T: IDescritible<T>
{
    Expression<Func<T, string>> DescriptionExpression { get; }
}

public class Foo : IDescritible<Foo>
{
    public string Name { get; set; }

    public Expression<Func<Foo, string>> DescriptionExpression
    {
        get { return x => x.Name; }
    }
}

public class DescriptionMatch<T> : AbstractSpecification<T> where T : IDescritible<T>
{
    public string Query { get; set; }

    public DescriptionMatch(string query)
    {
        this.Query = query;
    }

    public override Expression<Func<T, bool>> IsSatisfiedBy()
    {
        Expression<Func<T, bool>> combinedExpression = x => x.DescriptionExpression.Invoke(x).Contains(this.Query);
        return combinedExpression.Expand();
    }
}

Но когда он выполняет .Expand(), возникает исключение: невозможно преобразовать объект типа «System.Linq.Expressions.PropertyExpression» в тип «System.Linq.Expressions.LambdaExpression».

Затем я узнал, что LinqKit поддерживает только расширение локальных определенных выражений (как указано в этом вопросе) и что есть некоторый неофициальный код, записываемый в решить эти проблемы (ответы на этот вопрос).

Но когда я изменил библиотеку LinqKit, чтобы попробовать одно из предложенных решений во втором вопросе, она начала выдавать: переменная «x» типа «Foo», на которую ссылается область, но она не определена.

Может быть, это было вызвано тем, что у меня еще нет экземпляра Foo? Получил только параметр универсального типа, и поэтому я не могу определить свое выражение для локальной переменной. Может быть, есть способ определить как статическое выражение, как-то «прикрепленное» к классу? Но тогда я не смогу использовать его в общем запросе, не будет интерфейса...

Есть ли способ изменить LinqKit, чтобы я мог создавать выражения, определенные для явной реализации интерфейса? Или есть другой способ сделать общий запрос по явно реализованным свойствам? Или любое другое решение или вещи, на которые нужно обратить внимание?

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

Заранее спасибо!


person Lucas Corsaletti    schedule 21.01.2014    source источник
comment
Вы пытались получить DescriptionExpression без экземпляра Foo? Это похоже на свойство класса, а не на экземпляр. Вы можете определить DescriptionAttribute, пометить им свое свойство Name и построить выражение, используя только тип Foo, не имея его экземпляра.   -  person Konstantin Oznobihin    schedule 01.02.2014
comment
Вы получили какой-нибудь ответ по этому поводу?   -  person Ryan Griffith    schedule 19.08.2014
comment
@Ryan Griffith Да, я сделал то, что предложил Константин Ознобихин в комментарии выше, и это сработало очень хорошо.   -  person Lucas Corsaletti    schedule 14.01.2017


Ответы (1)


Самый простой способ сделать это - внести следующее изменение

public class DescriptionMatch<T> : AbstractSpecification<T> where T : IDescritible<T>, new()

Затем вы можете получить экземпляр, просто обновив T (который EF), используя выражение и иначе отбросив объект

public interface IDescritible<T> where T : IDescritible<T>
{
    Expression<Func<T, string>> DescriptionExpression { get; }
}

public class Foo : IDescritible<Foo>
{
    public string Name { get; set; }

    public Expression<Func<Foo, string>> DescriptionExpression
    {
        get { return x => x.Name; }
    }
}

public abstract class AbstractSpecification<T>
{
    public abstract Expression<Func<T, bool>> IsSatisfiedBy();
}

public class DescriptionMatch<T> : AbstractSpecification<T> 
    where T : IDescritible<T>, new()
{
    public string Query { get; set; }

    public DescriptionMatch(string query)
    {
        this.Query = query;
    }

    public override Expression<Func<T, bool>> IsSatisfiedBy()
    {
        Expression<Func<T, string>> lambda = new T().DescriptionExpression;
        return Expression.Lambda<Func<T, bool>>(
            Expression.Call(
                lambda.Body,
                "Contains",
                Type.EmptyTypes,
                Expression.Constant(
                    this.Query,
                    typeof(string)
                )
            ),
            lambda.Parameters
        );
    }
}

public class ExpressionReplacer : ExpressionVisitor
{
    private readonly Expression _toBeReplaced;
    private readonly Expression _replacement;

    public ExpressionReplacer(Expression toBeReplaced, Expression replacement)
    {
        if (toBeReplaced.Type != replacement.Type)
        {
            throw new ArgumentException();
        }
        this._toBeReplaced = toBeReplaced;
        this._replacement = replacement;
    }

    public override Expression Visit(Expression node)
    {
        return Object.ReferenceEquals(node, this._toBeReplaced) ? this._replacement : base.Visit(node);
    }
}
person Double Down    schedule 14.01.2015