是否有类似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;
}
}
答案 0 :(得分:2)
据我所知,没有这样的界面。即使有,实现该接口也需要做很多工作。
要求使用IContext
接口的原因不是因为您喜欢接口,而是要创建Facade:接口的用户会认为他们正在处理真实的数据库,而实际上将处理您的Fake数据库,可能是单元测试。
实现类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();
}
这样,您的原始代码中的任何内容都不会适合您的单元测试。使用它非常简单,特别是如果您熟悉实体框架的标准用法
但是,如果您认为您可以信任实体框架,并且想要在不使用实体框架的情况下测试代码,则可以为数据库创建适配器。
令人高兴的是,如果将来您决定不再使用实体框架,甚至不再使用数据库,而是选择其他归档方法,您的代码仍然可以使用。缺点:您不能再使用实体框架的黑暗角落。
这个想法是创建一个存档接口和一个存档类。标准的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。
但是,尽管此解决方案看起来很简洁,但实现起来却需要大量工作,即使完成后,它的功能仍然有限,特别是如果您要添加/删除项目并要使用表之间的关系。
数据库中表之间关系的每次更改也会导致存档中的更改。我会像努力一样去使用假数据库,并使用原始的实体框架功能。