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 中的接口和存储库抽象中断子查询?的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework 4.1 InverseProperty 属性和ForeignKey

Entity Framework 4.1 Fluent API 属性

卸载 Entity Framework 4.1 六月 CTP

通过 Entity Framework 4.1 中的用户定义函数进行热切加载

Entity Framework 4.1 - 映射错误的模式

Entity Framework 4.1 - 非键列之间的关系