CQRS 的 EF 读写模式
EF read & write modes for CQRS
我读到 Dapper 通常比 EF 快得多,并且正在考虑将 Dapper 用于查询端,将 EF 用于使用 CQRS(精简版)模式的应用程序的写入端。
不过,我也知道 EF 可以默认关闭跟踪。是否值得创建 2 个数据库上下文,一个用于在所有实体启用 AsNoTracking 的情况下读取,一个用于在启用跟踪的情况下写入?这样我就可以在不使用其他库和增加额外复杂性的情况下获得性能优势。
至于Entity Framework,你的申请中应该只有一个DbContext
。
您可以考虑使用上下文的简单抽象来为您提供读取端和写入端:
public abstract class Entity
{
// Marker class
}
public interface IEntityReader<out TEntity> where TEntity : Entity
{
IQueryable<TEntity> Query();
}
public interface IEntityWriter<TEntity> where TEntity : Entity
{
TEntity Get(object primaryKey);
void Save(TEntity entity);
void Delete(TEntity entity);
}
实施如下:
internal sealed class EntityFrameworkReader<TEntity> : IEntityReader<TEntity> where TEntity : Entity
{
private readonly Func<DbContext> _contextProvider;
public EntityFrameworkReader(Func<DbContext> contextProvider)
{
_contextProvider = contextProvider;
}
public IQueryable<TEntity> Query()
{
return _contextProvider().Set<TEntity>().AsNoTracking();
}
}
internal sealed class EntityFrameworkWriter<TEntity> : IEntityWriter<TEntity> where TEntity : Entity
{
private readonly Func<DbContext> _contextProvider;
public EntityFrameworkWriter(Func<DbContext> contextProvider)
{
_contextProvider = contextProvider;
}
public void Save(TEntity entity)
{
var context = _contextProvider();
var entry = context.Entry(entity);
// If it is not tracked by the context, add it to the context
if (entry.State == EntityState.Detached)
{
// This also sets the entity state to added.
context.Set<TEntity>().Add(entity);
}
else
{
// Tells the context that the entity should be updated during saving changes
entry.State = EntityState.Modified;
}
// In a perfect world, you should use a decorator and save the changes there using Dependency Injection
context.SaveChanges();
}
public void Delete(TEntity entity)
{
var context = _contextProvider();
var entry = context.Entry(entity);
if (entry.State != EntityState.Deleted)
{
// This also sets the entity state to Deleted.
context.Set<TEntity>().Remove(entity);
}
// In a perfect world, you should use a decorator and save the changes there using Dependency Injection
context.SaveChanges();
}
public TEntity Get(object primaryKey)
{
var context = _contextProvider();
var entity = context.Set<TEntity>().Find(primaryKey);
if (entity == null) return null;
// We found the entity, set the state to unchanged.
context.Entry(entity).State = EntityState.Unchanged;
return entity;
}
}
如果您使用的是依赖注入库,例如 Simple Injector,您可以像这样连接它:
container.Register<DbContext, MyDbContext>(Lifestyle.Scoped);
container.Register(typeof(IEntityWriter<>), typeof(EntityFrameworkWriter<>), Lifestyle.Scoped);
container.Register(typeof(IEntityReader<>), typeof(EntityFrameworkReader<>), Lifestyle.Scoped);
我读到 Dapper 通常比 EF 快得多,并且正在考虑将 Dapper 用于查询端,将 EF 用于使用 CQRS(精简版)模式的应用程序的写入端。
不过,我也知道 EF 可以默认关闭跟踪。是否值得创建 2 个数据库上下文,一个用于在所有实体启用 AsNoTracking 的情况下读取,一个用于在启用跟踪的情况下写入?这样我就可以在不使用其他库和增加额外复杂性的情况下获得性能优势。
至于Entity Framework,你的申请中应该只有一个DbContext
。
您可以考虑使用上下文的简单抽象来为您提供读取端和写入端:
public abstract class Entity
{
// Marker class
}
public interface IEntityReader<out TEntity> where TEntity : Entity
{
IQueryable<TEntity> Query();
}
public interface IEntityWriter<TEntity> where TEntity : Entity
{
TEntity Get(object primaryKey);
void Save(TEntity entity);
void Delete(TEntity entity);
}
实施如下:
internal sealed class EntityFrameworkReader<TEntity> : IEntityReader<TEntity> where TEntity : Entity
{
private readonly Func<DbContext> _contextProvider;
public EntityFrameworkReader(Func<DbContext> contextProvider)
{
_contextProvider = contextProvider;
}
public IQueryable<TEntity> Query()
{
return _contextProvider().Set<TEntity>().AsNoTracking();
}
}
internal sealed class EntityFrameworkWriter<TEntity> : IEntityWriter<TEntity> where TEntity : Entity
{
private readonly Func<DbContext> _contextProvider;
public EntityFrameworkWriter(Func<DbContext> contextProvider)
{
_contextProvider = contextProvider;
}
public void Save(TEntity entity)
{
var context = _contextProvider();
var entry = context.Entry(entity);
// If it is not tracked by the context, add it to the context
if (entry.State == EntityState.Detached)
{
// This also sets the entity state to added.
context.Set<TEntity>().Add(entity);
}
else
{
// Tells the context that the entity should be updated during saving changes
entry.State = EntityState.Modified;
}
// In a perfect world, you should use a decorator and save the changes there using Dependency Injection
context.SaveChanges();
}
public void Delete(TEntity entity)
{
var context = _contextProvider();
var entry = context.Entry(entity);
if (entry.State != EntityState.Deleted)
{
// This also sets the entity state to Deleted.
context.Set<TEntity>().Remove(entity);
}
// In a perfect world, you should use a decorator and save the changes there using Dependency Injection
context.SaveChanges();
}
public TEntity Get(object primaryKey)
{
var context = _contextProvider();
var entity = context.Set<TEntity>().Find(primaryKey);
if (entity == null) return null;
// We found the entity, set the state to unchanged.
context.Entry(entity).State = EntityState.Unchanged;
return entity;
}
}
如果您使用的是依赖注入库,例如 Simple Injector,您可以像这样连接它:
container.Register<DbContext, MyDbContext>(Lifestyle.Scoped);
container.Register(typeof(IEntityWriter<>), typeof(EntityFrameworkWriter<>), Lifestyle.Scoped);
container.Register(typeof(IEntityReader<>), typeof(EntityFrameworkReader<>), Lifestyle.Scoped);