【问题标题】:Query over interface properties using EntityFramework and LinqKit使用 EntityFramework 和 LinqKit 查询接口属性
【发布时间】:2014-01-21 16:45:07
【问题描述】:

我正在使用 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 只支持扩展本地定义的表达式(如 this question 所述),并且有一些非官方的代码可以解决这些问题(this question 的答案)。

但是,当我修改 LinqKit 库以尝试第二个问题中提出的解决方案之一时,它开始抛出:从范围 '' 引用的 'Foo' 类型的变量 'x',但它没有定义。

也许这是因为我还没有 Foo 的实例?只有泛型类型参数,这就是为什么我不能在局部变量上定义我的表达式。也许有一种方法可以以某种方式将静态表达式定义为“附加”到类中?但是那样我就无法在通用查询中使用它,不会有接口......

有一种方法可以修改 LinqKit,以便我可以构建在接口的显式实现上定义的表达式?或者还有另一种方法可以对显式实现的属性进行通用查询?或者有什么其他的解决方案或需要研究的东西?

没有必要为此使用 LinqKit 或规范模式,但 EntityFramework 是强制性的,重要的是要有一个接口/任何其他方式支持“指出”哪个属性是描述属性并制作通用对此的表达。

提前谢谢你!

【问题讨论】:

  • 您是否尝试在没有Foo 实例的情况下获得DescriptionExpression?它看起来确实是类的属性而不是实例。您可以定义DescriptionAttribute,用它标记您的Name 属性,并仅使用Foo 类型构建表达式,而无需它的实例。
  • 您对此有任何答复吗?
  • @Ryan Griffith 是的,我按照 Konstantin Oznobihin 在上述评论中的建议做了,效果很好。

标签: c# properties lambda expression-trees linqkit


【解决方案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);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-09-16
    • 2017-09-26
    • 1970-01-01
    • 1970-01-01
    • 2012-10-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多