Entity Framework 4.1 DbContext API 中的接口和存储库抽象中断子查询?

Posted

技术标签:

【中文标题】Entity Framework 4.1 DbContext API 中的接口和存储库抽象中断子查询?【英文标题】:Interface and repository abstraction break subqueries in Entity Framework 4.1 DbContext API? 【发布时间】: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&lt;TEntity&gt;() 有效,但ITestDbContext.Set&lt;TEntity&gt;() 不会翻译。将 ITestDbContext.Set&lt;TEntity&gt;() 的定义更改为返回 DbSet&lt;TEntity&gt; 而不是 IDbSet&lt;TEntity&gt; 仍然失败。

编辑: 如果没有一些查询拦截和翻译,我认为这是不可能的。如果我将来真的找到解决方案,我一定会分享它。

【问题讨论】:

不知何故,您的第一种方法不起作用,我并不感到惊讶。就像您要编写: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。我将暂时使用上面的第二个示例,稍后再探讨查询拦截/翻译。我会更新我的问题以反映我的新发现。 【参考方案1】:

我没有使用 EF 的经验,但基于使用 NHibernate 及其不断发展的 LINQ 支持,我怀疑您的问题的答案是您不喜欢的答案——听起来这个特定的构造不是(还没有?)受 EF LINQ 提供程序支持,您需要更改查询。

【讨论】:

嗯,作为“没有 EF 经验”的人,您在提问者收到“NotSupportedException”后“怀疑”查询“不支持”?你真的认为这是一个答案吗? @Slauma,中肯的批评。我愿意,因为我的回答是基于我对开发综合 LINQ 提供程序的(非常重要的)复杂性的经验以及我对另一个 ORM 实现的经验。我认为 OP 查询没有任何根本性的错误,除了他遇到了不受支持的边缘情况。当然,我同意 EF 专家很有可能会提出不同(更好)的答案。 这个答案在技术上是正确的。它根本不(还?)受支持。【参考方案2】:

在创建 IQuerable 之前,您需要在 DbSet&lt;SomeEntity&gt; 上包含相关对象的所有属性。

public class SomeEntity

    public Guid Id  get; set; 
    public virtual SomeOtherEntity Other  get; set; 


public class Repository

    public IQueryable<SomeEntity> Query()
    
        _context.Set<SomeEntity>().Include("Other").AsQueryable();
    

您可以通过使用 func 或其他方式将其提供给您的 Query 方法。发挥你的创造力;)

【讨论】:

以上是关于Entity Framework 4.1 DbContext API 中的接口和存储库抽象中断子查询?的主要内容,如果未能解决你的问题,请参考以下文章