为啥 DbContext 不实现 IDbContext 接口?

Posted

技术标签:

【中文标题】为啥 DbContext 不实现 IDbContext 接口?【英文标题】:Why DbContext doesn't implement IDbContext interface?为什么 DbContext 不实现 IDbContext 接口? 【发布时间】:2012-08-29 02:45:29 【问题描述】:

为什么Entity Framework中没有IDbContext接口?如果有一个现有接口,其中包含 SaveChanges() 等方法,您可以从中派生您的自定义数据库上下文接口,那么测试事情会不会更容易?

public interface ICustomDbContext : IDbContext

    // add entity set properties to existing set of methods in IDbContext
    IDbSet<SomeEntity> SomeEntities  get; 

【问题讨论】:

你仍然可以创建这样的接口并在你的派生上下文中实现它,但它真的会not be very helpful for unit testing。 是的,我只是好奇为什么一开始就没有这样的接口,开箱即用。不需要手动创建它。 您可以将 DbContext 包装在存储库中,因此可以模拟它。再次正如 Ladislav 指出的那样,您必须小心不要在存储库上公开任何可能受 linq to entity / linq to sql 影响的内容 @StevenMortimer 我相信上下文比存储库更接近工作单元。存储库更靠近 DbSet。 @Bruno,你是对的,但回购实现必须使用上下文。 “回购使用/拥有一个工作单元”没有意义。有意义的“工作单元使用/拥有一个或多个回购”。再一次,你是对的,一个上下文有一个或多个 DbSet。这警告我们,当围绕上下文构建 repo 并围绕已构建的 repos 构建工作单元时:我们正在做一些多余的事情,将设计模式嵌入到相同的设计模式中......为什么我们这样做?因为缺少原始实现的清晰接口:缺少 IDbRepository(那将是工作单元) 【参考方案1】:

我看到了这个IDbContext

See this link 然后你用那个接口为你的实体上下文创建一个新的部分类。

public partial class YourModelEntities : DbContext, IDbContext 

编辑: 我编辑了这篇文章,这对我有用。 我的上下文

namespace dao

    public interface ContextI : IDisposable
    
        DbSet<TEntity> Set<TEntity>() where TEntity : class;
        DbSet Set(Type entityType);
        int SaveChanges();
        IEnumerable<DbEntityValidationResult> GetValidationErrors();
        DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity:class;
        DbEntityEntry Entry(object entity);
        string ConnectionString  get; set; 
        bool AutoDetectChangedEnabled  get; set; 
        void ExecuteSqlCommand(string p, params object[] o);
        void ExecuteSqlCommand(string p);
    

YourModelEntities 是你自动生成的分部类,你需要创建一个新的同名分部类,然后添加你的新上下文接口,例如 ContextI

注意:接口并未实现所有方法,因为这些方法是在您的自动生成代码中实现的。

namespace dao

    public partial class YourModelEntities :DbContext, ContextI
    
        public string ConnectionString
        
            get
            
                return this.Database.Connection.ConnectionString;
            
            set
            
                this.Database.Connection.ConnectionString = value;
            
        

        bool AutoDetectChangedEnabled
        
            get
            
                return true;
            
            set
            
                throw new NotImplementedException();
            
        

        public void ExecuteSqlCommand(string p,params object[] os)
        
            this.Database.ExecuteSqlCommand(p, os);
        

        public void ExecuteSqlCommand(string p)
        
            this.Database.ExecuteSqlCommand(p);
        

        bool ContextI.AutoDetectChangedEnabled
        
            get
            
                return this.Configuration.AutoDetectChangesEnabled;
            
            set
            
                this.Configuration.AutoDetectChangesEnabled = value;
            
        
      
    

【讨论】:

【参考方案2】:

只需创建一个模拟 DbContext 扩展您的生产 DbContext 覆盖使测试复杂化的方法。这样,对生产 DbContext 的任何更改都会自动反映在测试中,除了覆盖的方法。对于任何其他处理持久性并采用 DbContext 的类,只需扩展它们以及传入扩展的模拟 DbContext。

namespace Test.Mocks
  
    public sealed class MockDatabaseContext : MainProject.Persistence.Database.DatabaseContext
    
        public MockDatabaseContext(ConfigurationWrapper config) : base(config)
        

              
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        

            var dbPath = "test.db";
            optionsBuilder.UseSqlite($"Filename=dbPath");


        
    


namespace Test.Mocks


    public class MockInventoryFacade : InventoryFacade
            
        public MockInventoryFacade(MockDatabaseContext databaseContext) : base(databaseContext)
        

            
    

【讨论】:

【参考方案3】:

我也在考虑这个问题,我假设你会用它来嘲笑DbContext。我找不到这样做的理由,除了你需要在你的模拟类中手动实现你自己的DbSet(所以无论如何都需要重写你自己的接口)。

【讨论】:

【参考方案4】:

没有 IDbContext 因为它没有用,唯一的实现就是 DbContext。

如果您查看此design meeting note,EF 团队也正在使用 IDbSet 进行此操作

对我来说,EF 在单元测试方面的真正问题是 DbContext 中的 DbConnection,幸运的是在 codeplex 上有一个很好的项目 Effort 开始填补这个问题。

Effort 是一个强大的工具,它可以方便地为基于实体框架的应用程序创建自动化测试。 它基本上是一个 ADO.NET 提供程序,它在轻量级进程内主内存数据库而不是传统的外部数据库上执行所有数据操作。它还提供了一些直观的帮助方法,可以很容易地将此​​提供程序与现有的 ObjectContext 或 DbContext 类一起使用。对现有代码的简单添加可能足以创建可以在没有外部数据库存在的情况下运行的数据驱动测试。

有了这个,您可以让您的 DbContext 和 DbSet 保持原样并轻松地进行单元测试。 唯一的缺点是 Linq 提供者之间的差异,其中一些单元测试可能会通过努力而不是真正的后端。

使用 EF7 更新

我仍然认为 IDbContext 没有用,问题来自 DbConnection。

EF7 也没有 IDbContext,为了进行单元测试,他们现在提供了一个内存提供程序。

您可以在此处看到 Rowan Miller 进行演示:Modern Data Applications with Entity Framework 7

【讨论】:

这根本没有用。现在我们正在使用依赖于 DBContext 实例的 WCF 数据服务。如果它依赖于接口,我们将能够访问 Set() 方法调用并提供我们迫切需要的过滤层(而查询拦截器根本不够用)。 EF6 DbSet 的方法现在是虚拟的,您可以从 DbSet 继承并覆盖所需的方法。 MS 数据服务直接在 DbContext 或从它继承的东西上工作。有没有办法让 Set 方法(即使是 EF6 也不是虚拟的)返回我自己的 DbSet?我真正需要做的是过滤掉实体(只返回其中的一些)。 IDbContext 绝对不会没用。作为 Repository 模式实现的主要组成部分之一,如果我们不能注入模拟上下文而不是实际实现的实际实现,则单元测试是不可能的。即使我们不能使用存储库模式,这个问题仍然有效。所有 DAL 层的主要组成部分是上下文,并且必须是可模拟的。当然,如果可以用一些技巧和代码生成器等来模拟,但是模拟它的通用和通用方式总是基于模拟组件正在实现的接口。 以来,EF 设计者完全忽略了这一点 你应该阅读这个msdn.microsoft.com/en-US/data/dn314429。您将看到没有任何界面的模拟。

以上是关于为啥 DbContext 不实现 IDbContext 接口?的主要内容,如果未能解决你的问题,请参考以下文章

为啥有多个 DbContext 类?

为啥我们在 DbContext 中使用 DependecyInjection 而不是 OnConfiguring 方法?

为啥 EF Scaffold-DbContext 命令在针对 net6.0 报告“未找到设计时服务”后会引发 NRE?

为啥不调用种子方法?

FreeSql.DbContext 第二个版本介绍

autofac 多个dbcontext 上下文不一致