我有以下扩展方法:
public static decimal? GetValue(this Member member)
{
return member.Readings.SelectMany(r => r.Measurements).GetLatestValue();
}
GetLatestValue
是另一个仅使用其他 LINQ 扩展的扩展:OrderBy
、Where
、Select
和 First
。
我希望这会执行JOIN
查询。相反,当我查看 SQL 事件探查器时,它正在执行单独的查询以选择每个读数的所有测量值。
我从这个问题和这个问题中了解到,如果我传入数据库上下文并使用它,我可以得到一个JOIN
,但这对我来说不是一个选择。
这是怎么回事?为什么Readings
属性是ICollection
,而不是IQueryable
?如何在此处获取单个查询,而无需更改扩展方法签名?
这是怎么回事?
您对问题的描述是准确的。
"为什么读数属性是 ICollection,而不是 IQueryable?"
这是实体框架中的设计错误。
如何在此处获取单个查询,而无需更改扩展方法签名?
这是不可能的。您的方法强制计算查询。即使member.Readings
IQueryable
你仍然会在这里强制评估。
请注意,EF 永远无法远程GetLatestValue
到 SQL(我假设它是您的自定义函数)。没有解决方法。EF 无法为任意 C# 函数生成 SQL。
不幸的是,对于您的情况,没有很好的解决方案。您必须重构代码,以便它与实体框架及其限制很好地配合使用。您链接到的帖子与执行此操作相关。
我想出了一个解决方案,允许我按照我希望它工作的方式使用我的调用代码,但有一些技巧:
在我的扩展方法和需要类似查询的任何其他地方,我只需要确保我调用以下代码:
EvilHackyContextUtilities.SetReadingsQueryableHack(member);
这样做是用我自己的ICollection
替换member.Readings
属性,这也是一个IQueryable
。事情的IQueryable
方面使用我链接到的其他问题中建议的相同查询。我已经设法使用反射获取ObjectContext
,然后将其传递到我的新MyDbContext
的构造函数中。我不得不稍微更改.tt
源代码以添加一个使用 DbContext(ObjectContext objectContext, bool dbContextOwnsObjectContext)
作为其基础的构造函数。
public static class EvilHackyContextUtilities
{
private static MyDbContext GetDbContext(object entity)
{
var entityWrapper = entity.GetType().GetField("_entityWrapper").GetValue(entity);
var objectContext = entityWrapper.GetType().GetProperty("Context").GetValue(entityWrapper, null) as ObjectContext;
return new MyDbContext(objectContext, false);
}
public static void SetReadingsQueryableHack(Member entity)
{
if (entity.Readings is EvilHackyQueryableCollection<Reading>)
return;
IQueryable<Reading> query = GetDbContext(entity).Readings.Where(r => r.MemberID == entity.MemberID);
entity.Readings = new EvilHackyQueryableCollection<Reading>(entity.Readings, query);
}
}
internal class EvilHackyQueryableCollection<TEntity> : ICollection<TEntity>, IQueryable<TEntity>
where TEntity : class
{
private readonly IQueryable<TEntity> _baseQuery;
private readonly ICollection<TEntity> _baseCollection;
public EvilHackyQueryableCollection(ICollection<TEntity> baseCollection, IQueryable<TEntity> baseQuery)
{
_baseQuery = baseQuery;
_baseCollection = baseCollection;
}
#region ICollection members
//All middle-man methods wrapping up the _baseCollection field.
#endregion
#region IQueryable members
//All middle-man methods wrapping up the _baseQuery field.
#endregion
}