我正在使用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表达式。
然后我尝试在类中定义一个Expression,如下所示:
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是强制性的,重要的是拥有一个接口/任何其他方式支持“指出”哪个属性是描述属性并使其成为通用在那上面表达。
提前谢谢!
答案 0 :(得分:0)
最简单的方法是进行以下更改
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);
}
}