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

时间:2018-11-29 14:36:02

标签: c# entity-framework

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

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

但是实际上我是否必须重新定义每个real的Context方法?

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 个答案:

答案 0 :(得分:2)

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

要求使用IContext接口的原因不是因为您喜欢接口,而是要创建Facade:接口的用户会认为他们正在处理真实的数据库,而实际上将处理您的Fake数据库,可能是单元测试。

实现类DbContext的所有功能是一项相当大的工作,更重要的是实现类DbSet<...>的所有功能,尤其是如果您想将项目用作DbContext.ChangeTrackerDbContext.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();
}

这样,您的原始代码中的任何内容都不会适合您的单元测试。使用它非常简单,特别是如果您熟悉实体框架的标准用法

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

令人高兴的是,如果将来您决定不再使用实体框架,甚至不再使用数据库,而是选择其他归档方法,您的代码仍然可以使用。缺点:您不能再使用实体框架的黑暗角落。

这个想法是创建一个存档接口和一个存档类。标准的Archive类具有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;
    }
}

这会导致代码中发生很多更改:您不仅需要调用新的MyDbContext,还必须要求ArchiveFactory创建一个。此外,每个要使用新MyDbContext的类都必须具有相同的ArchiveFactory。

但是,尽管此解决方案看起来很简洁,但实现起来却需要大量工作,即使完成后,它的功能仍然有限,特别是如果您要添加/删除项目并要使用表之间的关系。

数据库中表之间关系的每次更改也会导致存档中的更改。我会像努力一样去使用假数据库,并使用原始的实体框架功能。