【发布时间】:2011-05-13 17:24:53
【问题描述】:
目标:
我正在尝试使用新的 Entity Framework 4.1 DbContext API(使用 Database First 和 POCO 类的新 ADO.NET DbContext Generator)并使用基本的通用存储库提供抽象层。
问题:
如果我尝试对我的存储库使用子查询,EF 无法完成翻译并引发错误:
System.NotSupportedException: LINQ to Entities 无法识别方法 'System.Linq.IQueryable`1[EntityFramework41Test. Data.Entity.Table2] Query()'方法,该方法不能翻译成store表达式。
过去我已经成功地将这个设计与旧的 4.0 ObjectContext 一起使用,但我想使用新的 API。同样的方法也失败了 4.0 ObjectContext API(测试使用生成的 POCO 实体)。
注意:我认为发布数百行代码并不现实,但我有一个示例解决方案,其中包含一个使用 SQL Server CE 4.0 的 ASP.NET MVC 3 项目和一个演示结果的基本单元测试项目如果有帮助,可以上传或通过电子邮件发送各种方法。
我使用的存储库接口非常简单:
public interface IRepository<TEntity> : IDisposable where TEntity : class, ITestEntity
{
TEntity GetById(int id);
IQueryable<TEntity> Query();
void Add(TEntity entity);
void Remove(TEntity entity);
void Attach(TEntity entity);
}
上下文接口更加简单:
public interface ITestDbContext : IDisposable
{
IDbSet<TEntity> Set<TEntity>() where T: class, ITestEntity;
void Commit();
}
以下是不工作的示例用法,使用存储库和上下文实例的接口:
using (ITestDbContext context = new TestDbContext())
using (IRepository<Table1> table1Repository = new Repository<Table1>(context))
using (IRepository<Table2> table2Repository = new Repository<Table2>(context))
{
// throws a NotSupportedException
var results = table1Repository.Query()
.Select(t1 => new
{
T1 = t1,
HasMatches = table2Repository.Query()
.Any(t2 => t2.Table1Id == t1.Id)
})
.ToList();
}
上面的代码是我想使用的方法。具体的类最终会被注入。
请忽略这样一个事实,即编写此特定查询的方法比使用子查询更好。我特意简化了代码以关注实际问题:EF 不会翻译查询。
将“内部”存储库 Query() 方法存储在局部变量中确实有效,但并不理想,因为您必须始终记住这样做。
using (ITestDbContext context = new TestDbContext())
using (IRepository<Table1> table1Repository = new Repository<Table1>(context))
using (IRepository<Table2> table2Repository = new Repository<Table2>(context))
{
var table2RepositoryQuery = table2Repository.Query();
// this time, it works!
var results = table1Repository.Query()
.Select(t1 => new
{
T1 = t1,
HasMatches = table2RepositoryQuery
.Any(t2 => t2.Table1Id == t1.Id)
})
.ToList();
}
我还注意到其他一些方法会失败或成功,例如无视存储库并调用TestDbContext.Set<TEntity>() 有效,但ITestDbContext.Set<TEntity>() 不会翻译。将 ITestDbContext.Set<TEntity>() 的定义更改为返回 DbSet<TEntity> 而不是 IDbSet<TEntity> 仍然失败。
编辑:
如果没有一些查询拦截和翻译,我认为这是不可能的。如果我将来真的找到解决方案,我一定会分享它。
【问题讨论】:
-
不知何故,您的第一种方法不起作用,我并不感到惊讶。就像您要编写:
HasMatches = SomeFunction()与bool value = SomeFunction(); ... HasMatches = value相比,第一个在 LINQ to Entities 中不起作用,但第二个可以。 LTE 可以在您的子查询中使用IQueryable,但不能使用返回IQueryable的函数。但我也很惊讶这在 EF 4.0 中与 ObjectContext API 一起工作。也可以在这里 (social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/…) 提出您的问题,希望得到开发人员的关注。 -
@Slauma:您的局部变量/方法调用比较非常有意义。注意:我忽略了我的旧 ObjectContext API 项目在我的 Repository 类中使用查询拦截器逻辑来执行一些自动 Enum 转换(以解决 EF 中缺乏 Enum 支持的问题)。我没有做任何特定的事情来转换任何
IQueryable方法调用,但我想知道查询包装是否有导致上述场景起作用的副作用。我将看看我是否可以首先使用香草 EF 4.0 ObjectContext 来完成上述工作(这应该是我的第一种方法)。感谢 MSDN 论坛链接。 -
当您有结果时告诉我们。如果您的查询确实适用于 ObjectContext,这意味着我们使用 DbContext 的查询能力比使用 ObjectContext 的要少,这对我来说非常令人惊讶,因为我和您写的一样理解 - 所有的辛勤工作最终都从 DbContext 委托给了 ObjectContext .
-
@Slauma:我创建了一个更好的测试解决方案,它同时使用了旧的 ObjectContext API 和新的 DbContext API。两种情况都未能通过第一个示例并通过第二个示例。但是,它们都失败/通过了不同的测试用例,具体取决于上下文和查询集的转换方式。我不认为 DbContext 会牺牲任何功能,我更喜欢新的 API。我将暂时使用上面的第二个示例,稍后再探讨查询拦截/翻译。我会更新我的问题以反映我的新发现。
标签: .net c#-4.0 entity-framework-4.1 abstraction