代码之家  ›  专栏  ›  技术社区  ›  Joelty

是否有类似于I DbContext接口的东西,或者我必须用所有可能的DbContext方法编写自己的接口?

  •  0
  • Joelty  · 技术社区  · 6 年前

    是否有类似IDbContext接口的东西,或者我必须用所有可能的DbContext方法编写自己的接口?

    我想创建两个不同的上下文,其中第二个将用于测试

    但我真的需要重新定义每个real的上下文方法吗?

    public interface IContext
    {
        DbSet<Car> Cars { get; set; }
        DbSet<Person> Persons { get; set; }
    
        int SaveChanges();
    
        Task<int> SaveChangesAsync(CancellationToken token);
        Task<int> SaveChangesAsync(bool test, CancellationToken token);
    
        EntityEntry<T> Add<T>(T t) where T : class;
    
        (...) and much, much more?
    }
    
    public class DatabaseContext : IContext // also maybe IdentityDbContext<User>
    {
        public Context(DbContextOptions<Context> options) : base(options)
        {
    
        }
    }
    
    public class FakeDatabase : IContext
    {
        DbSet<Car> Cars { get; set; }
    
        public int SaveChanges()
        {
            return 0;
        }
    }
    
    1 回复  |  直到 6 年前
        1
  •  2
  •   Harald Coppoolse    6 年前

    据我所知,没有这样的界面。即使有,实现这个接口也需要相当多的工作。

    你要求的理由 IContext 接口,不是因为你喜欢接口,而是你想创建一个Facade:接口的用户会认为他们在处理真实的数据库,而实际上他们会处理你的假数据库,可能是一个单元测试。

    实现类的所有功能是一项相当艰巨的工作 DbContext ,更重要的是 DbSet<...> ,尤其是如果要将项用作 DbContext.ChangeTracker DbContext.Database .

    如果您想要一个快速而简单的方法来测试原始的几乎没有变化的代码,我会使用一个表示内存中数据库的nuget包,比如 Nuget Effort

    这样,没有人,甚至实体框架,会知道您将使用一个假的数据库。

    class MyDbContext : DbContext
    {
         // probably already existing constructors:
         public MyDbContext() : base (...) { }
         public MyDbContext(string nameOrConectionString : base (nameOrConnectionString) { }
    
         // extra constructor for Effort, uses existing base constructor
         public MyDbContext(DbConnection existingConnection) : base(existingConnection) { }
    
         ... // DbSets etc
    }
    

    用法:

    const string unitTestDbName = "Db for Unit Tests";
    DbConnection inMemoryDbConnection = Effort.DbConnectionFactory.CreatePersistent(testDbName);
    
    using (var dbContext = new MyDbContext(inMemoryDbConnection))
    {
         // fill the database with test data that your test code expects
         ...
         dbContext.SaveChanges();
    }
    

    这样,您的原始代码就不会适合您的单元测试。使用它相当简单,特别是如果您熟悉实体框架的标准用法

    但是,如果您认为可以信任实体框架,并且希望在不使用实体框架的情况下测试代码,则可以为数据库创建适配器。

    好的一面是,如果将来决定不再使用实体框架,或者甚至不使用数据库,但使用其他存档方法,代码仍然可以工作。缺点:不能再使用实体框架的暗角。

    其思想是创建一个归档接口和一个归档类。标准存档类有一个DbContext。

    public interface IArchive : IDisposable
    {
        IQueryable<School> Schools {get; set;}
        IQueryable<Teacher> Teachers {get; set;}
        IQueryable<Student> Students {get; set;}
    }
    
    public class Archive : IArchive, IDisposable
    {
         private readonly DbContext = new SchoolDbContext(...);
    
         public IQueryable<School> Schools => this.DbContext.Schools;
         public IQueryable<Teacher> Teachers => this.DbContext.Teachers;
         public IQueryable<Student> Students => this.DbContext.Students;
    
         public int SaveChanges()
         {
             return this.DbContext.SaveChanges();
         }
    
         // TODO: Dispose disposes the DbContext
    }
    

    为测试创建特殊存档:

    class TestArchive : IArchive
    {
         private readonly List<School> schools = new List<School>();
         private readonly List<Student> students = new List<Student>();
         ...
    
         public IQueryable<Student> Students => this.students.AsQueryable();
         ...         
    
         public int SaveChanges() {return 1;}
    }
    

    在某个地方,你必须告诉你的代码实例化你的假存档,而不是你的真实存档。这是使用工厂模式完成的:

    interface IArchiveFactory
    {
         IArchive Create();
    }
    
    class ArchiveFactory : IArchiveFactory
    {
        public IArchive Create()
        {
            return new MyDbContext(...);
        }
    }
    
    class TestArchiveFactory : IArchivedFactory
    {
        public IArchive TestData {get; set;}
    
        public IArchive Create()
        {
             return this.TestData;
        }
    }
    

    这会导致代码发生一些变化:您将不得不要求ArchiveFactory创建一个MyDbContext,而不仅仅是调用新的MyDbContext。此外,每个想要使用新MyDbContext的类都需要有相同的ArchiveFactory。

    然而,尽管这个解决方案看起来非常简洁,但它的实现却需要做很多工作,即使完成了这项工作,它仍然具有有限的功能,特别是如果您希望添加/删除项并希望使用表之间的关系。

    数据库中表之间关系的每一次更改也将导致存档的更改。我想要一个像假数据库一样的工作,并使用原始的实体框架功能。