我已经阅读了大约30个Stack Overflow问题,以及大约20个博客,但找不到我的答案。那就是说,我确信我要求的答案就在那里,所以如果你知道它,请指出(请注意关于不符合我要求的答案的最后一段/句子)。谢谢!
我持久化具有类似形状的类的实例:
public class Data {
public int Id { get; set; }
}
public class Container {
public int Id { get; set; }
public Data Data { get; set; }
}
目前编写查询是为了通过抽象层查找容器,该抽象层需要Lambda Expression接受Container并返回bool(谓词)。即使Entity Framework 5是首选的ORM,它也不会接受IQueryable。
我的任务是提供一个基于Lambda表达式的API表面,它接受数据类型。我无法更改抽象层(我必须传递一个接受Container的谓词)所以我试图将我收到的表达式转换为:
Expression<Func<Data , bool>>
to:
Expression<Func<Container , bool>>
我在我的存储库类中添加了一个额外的方法,如:
public Container Find( Expression<Func<Data , bool>> predicate ) {
IEnumerable<Container> result = QueryStrategy.Fetch( c => predicate.Compile().Invoke( c.Data ) );
return result.FirstOrDefault();
}
这与现有的Find方法相称:
public Container Find( Expression<Func<Container , bool>> predicate ) {
IEnumerable<Container> result = QueryStrategy.Fetch( predicate );
return result.FirstOrDefault();
}
使用前一种方法时,会产生以下异常:
LINQ to Entities无法识别方法'布尔值 Invoke(Container.Data)'方法,而这个方法不可能 翻译成商店表达。
我用Expression类尝试了各种各样的东西,我只是看不到映射的方法:
Expression<Func<Data , bool>>
to:
Expression<Func<Container , bool>>
不使用Entity Framework不支持的Invoke(但在内存可枚举数据中可以正常工作)。
有人可以帮助我使用表达式使用上述方案吗?
我知道我可以使用LinqKit库来解决这个问题,但我真的想在不引入第三方库的情况下解决这个问题。
更新:在我努力提出简化问题时,我暗示(最初在代码示例中使用DbContext)代码可以访问IQueryable和/或适合使用LinqKit的AsExpandable()。实际情况并非如此 - 不允许存储库类使用IQueryable或任何特定于供应商的扩展。我修改了上面的例子,希望这会让事情变得更清楚。
此问题现已解决
答案 0 :(得分:2)
在阅读/修改表达式(惊人的技术)4小时后,我设法解决了这个问题。
我把这个课程放在一起(它没有打磨或最终,但显示它是如何实现的):
class ParameterRewriter<TTarget , TSource> : ExpressionVisitor {
private ParameterExpression Source;
private MemberExpression Target;
public Expression<Func<TTarget , bool>> Rewrite( Expression<Func<TSource , bool>> predicate , Expression<Func<TTarget , TSource>> propertyNameExpression ) {
var parameter = Expression.Parameter( typeof( TTarget ) );
var propertyName = ( propertyNameExpression.Body as MemberExpression ).Member.Name;
Source = predicate.Parameters.Single();
Target = Expression.PropertyOrField( parameter , propertyName );
var body = Visit( predicate.Body );
return Expression.Lambda<Func<TTarget , bool>>(
body ,
parameter
);
}
protected override Expression VisitParameter( ParameterExpression node ) {
if ( node == Source ) {
return Target;
}
return base.VisitParameter( node );
}
}
它可以像这样使用:
var parameterRewriter = new ParameterRewriter<Container , Data>();
Expression<Func<Data , bool>> dataPredicate = d => ( d.Id == 1 );
var containerPredicate = parameterRewriter.Rewrite( dataPredicate , c => c.Data );
理论上,人们应该能够通过检查propertyNameExpression来遍历更深层次的关系,但我今天已经受够了。
我现在可以看到为每种查询风格生成的SQL实体框架是相同的:
========================
Expression<Func<Container , bool>> p = c => c.Data.Id == 1
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Data_Id] AS [Data_Id]
FROM [dbo].[Container] AS [Extent1]
WHERE 1 = [Extent1].[Data_Id]
========================
Expression<Func<Data , bool>> p = d => d.Id == 1
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[Data_Id] AS [Data_Id]
FROM [dbo].[Container] AS [Extent1]
WHERE 1 = [Extent1].[Data_Id]
========================
答案 1 :(得分:1)
通过谓词数据过滤加入客户,然后从结果中选择客户
public Container Find(Expression<Func<Data, bool>> predicate )
{
return MyDbContext.Containers
.Join(MyDbContext.Containers.Select(c => c.Data)
.Where(predicate),
c => c.Data.Id,
d => d.Id,
(c, d) => c)
.FirstOrDefault();
}