需要存储库设计模式建议
need repository design pattern advice
我有很多实体和存储库。我的实体有两种类型:可擦除和不可磨灭。所以我有两个基础 classes 用于实体。
不可磨灭的实体实现了这个基础class:
public abstract class BaseEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime InsertedDate { get; set; }
public DateTime? UpdatedDate { get; set; }
//For recovering
public DateTime? DeletedDate { get; set; }
public bool Active { get; set; }
}
可擦除实体实现了这个基础class:
public abstract class BaseErasableEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime InsertedDate { get; set; }
public DateTime? UpdatedDate { get; set; }
}
使用可擦除实体的存储库实现了这个基础class:
public class BaseErasableRepository<TEntity, TRepository> : DbContext, IBaseErasableRepository<TEntity> where TEntity : BaseErasableEntity where TRepository : DbContext
{
public BaseErasableRepository(DbContextOptions<TRepository> options) : base(options)
{ }
protected DbSet<TEntity> Entities { get; set; }
public IEnumerable<TEntity> GetAll()
{
return Entities ?? throw new CannotFindEntityException();
}
public TEntity GetById(int id)
{
var entity = Entities.Find(id) ?? throw new CannotFindEntityException(id);
return entity;
}
public void Update(TEntity entity)
{
Entities.Update(entity);
SaveChanges();
}
public void Delete(TEntity entity)
{
Entities.Remove(entity);
SaveChanges();
}
public IEnumerable<TEntity> GetFiltered(Func<TEntity, bool> condition = null)
{
return Entities.Where(condition) ?? throw new CannotFindEntityException();
}
}
使用不可磨灭实体的存储库实现了这个基础class:
public class BaseRepository<TEntity, TRepository> : DbContext, IBaseRepository<TEntity> where TEntity : BaseEntity where TRepository : DbContext
{
public BaseRepository(DbContextOptions<TRepository> options) : base(options)
{ }
protected DbSet<TEntity> Entities { get; set; }
public IEnumerable<TEntity> GetAll()
{
return Entities.Where(entity => entity.Active == true) ?? throw new CannotFindEntityException();
}
public TEntity GetById(int id)
{
var entity = Entities.Find(id) ?? throw new CannotFindEntityException(id);
if(!entity.Active)
throw new CannotFindEntityException();
return entity;
}
public void Update(TEntity entity)
{
Entities.Update(entity);
SaveChanges();
}
public void Delete(TEntity entity)
{
var toBeDeleteEntity = GetById(entity.Id);
toBeDeleteEntity.Active = false;
toBeDeleteEntity.DeletedDate = DateTime.Now;
Entities.Update(toBeDeleteEntity);
SaveChanges();
}
public IEnumerable<TEntity> GetFiltered(Func<TEntity, bool> condition = null)
{
return Entities.Where(entity => entity.Active).Where(condition) ?? throw new CannotFindEntityException();
}
}
我的问题:我有更多的存储库方法。它们对于两个存储库都是相同的。添加新功能时,我必须编写两次相同的代码。有没有更好的方法来使用单一基础存储库 class?
使用全局查询过滤器和 SaveChanges() 覆盖在支持它的实体上根据示例 here 实施软删除,并且您不需要两个不同的存储库。
然后进一步简化,只使用带有通用便利方法的单个 DbContext,而不是使整个类型通用。这样您就可以将 Repository 类型与您的所有实体一起使用。同样,为 N 个实体类型配置 N 个不同的 DbContext 类型是行不通的。您将无法编写多实体查询、加载相关数据或编排涉及多个实体类型的事务。
像这样:
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.SqlServer;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace EfCore3Test
{
public abstract class BaseEntity
{
}
public abstract class SoftDeleteEntity : BaseEntity
{
}
public class Recipe : BaseEntity
{
public int RecipeId { get; set; }
public string Title { get; set; }
public ICollection<Resource> Resources { get; } = new List<Resource>();
}
public class Shop : BaseEntity
{
public int ShopId { get; set; }
public string Title { get; set; }
public Resource Logo { get; set; }
}
public class Resource : SoftDeleteEntity
{
public int ResourceId { get; set; }
public string Path { get; set; }
public int ItemRefId { get; set; }
}
public class Repository : DbContext
{
private static readonly ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddFilter((category, level) =>
category == DbLoggerCategory.Database.Command.Name
&& level == LogLevel.Information).AddConsole();
});
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLoggerFactory(loggerFactory)
.UseSqlServer("Server=.;database=EfCore3Test;Integrated Security=true",
o => o.UseRelationalNulls());
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var entitytypes = Assembly.GetExecutingAssembly()
.DefinedTypes
.Where(t => typeof(BaseEntity).IsAssignableFrom(t))
.Where(t => ! t.IsAbstract);
foreach (var et in entitytypes)
{
Console.WriteLine($"Configuring {et.Name} as a Repository Entity");
modelBuilder.Entity(et);
if ( typeof(SoftDeleteEntity).IsAssignableFrom(et) )
{
Console.WriteLine($"Configuring {et.Name} as a SoftDelete Entity");
this.GetType()
.GetMethod(nameof(ConfigureSoftDelete), BindingFlags.NonPublic | BindingFlags.Static)
.MakeGenericMethod(et)
.Invoke(null, new object[] { modelBuilder });
}
}
base.OnModelCreating(modelBuilder);
}
static void ConfigureSoftDelete<T>(ModelBuilder builder) where T : SoftDeleteEntity
{
builder.Entity<T>().Property<bool>("IsDeleted");
builder.Entity<T>().HasQueryFilter(e => EF.Property<bool>(e, "IsDeleted") == false);
}
public override int SaveChanges()
{
foreach (var e in ChangeTracker.Entries())
{
if ( e.State == EntityState.Deleted && e.Entity is SoftDeleteEntity )
{
e.State = EntityState.Unchanged;
e.Property("IsDeleted").CurrentValue = true;
e.Property("IsDeleted").IsModified = true;
}
}
return base.SaveChanges();
}
public IEnumerable<TEntity> GetAll<TEntity>() where TEntity:class
{
return Set<TEntity>();
}
public TEntity GetById<TEntity>(int id) where TEntity : class
{
var entity = Set<TEntity>().Find(id);
if (entity == null)
throw new InvalidOperationException($"No Entity {typeof(TEntity).Name} foound for id {id}");
return entity;
}
public void Delete<TEntity>(TEntity entity) where TEntity : class
{
this.Set<TEntity>().Remove(entity);
}
}
class Program
{
static void Main(string[] args)
{
using var db = new Repository();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
var r = new Recipe();
r.Resources.Add(new Resource() { ItemRefId = 2, Path = "/" });
db.Add(r);
db.SaveChanges();
db.Delete(r.Resources.First());
db.SaveChanges();
var s = new Shop();
s.Logo = new Resource { ItemRefId = 2, Path = "/" };
db.Add(s);
db.SaveChanges();
s.Logo = null;
db.SaveChanges();
}
}
}
我有很多实体和存储库。我的实体有两种类型:可擦除和不可磨灭。所以我有两个基础 classes 用于实体。
不可磨灭的实体实现了这个基础class:
public abstract class BaseEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime InsertedDate { get; set; }
public DateTime? UpdatedDate { get; set; }
//For recovering
public DateTime? DeletedDate { get; set; }
public bool Active { get; set; }
}
可擦除实体实现了这个基础class:
public abstract class BaseErasableEntity
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime InsertedDate { get; set; }
public DateTime? UpdatedDate { get; set; }
}
使用可擦除实体的存储库实现了这个基础class:
public class BaseErasableRepository<TEntity, TRepository> : DbContext, IBaseErasableRepository<TEntity> where TEntity : BaseErasableEntity where TRepository : DbContext
{
public BaseErasableRepository(DbContextOptions<TRepository> options) : base(options)
{ }
protected DbSet<TEntity> Entities { get; set; }
public IEnumerable<TEntity> GetAll()
{
return Entities ?? throw new CannotFindEntityException();
}
public TEntity GetById(int id)
{
var entity = Entities.Find(id) ?? throw new CannotFindEntityException(id);
return entity;
}
public void Update(TEntity entity)
{
Entities.Update(entity);
SaveChanges();
}
public void Delete(TEntity entity)
{
Entities.Remove(entity);
SaveChanges();
}
public IEnumerable<TEntity> GetFiltered(Func<TEntity, bool> condition = null)
{
return Entities.Where(condition) ?? throw new CannotFindEntityException();
}
}
使用不可磨灭实体的存储库实现了这个基础class:
public class BaseRepository<TEntity, TRepository> : DbContext, IBaseRepository<TEntity> where TEntity : BaseEntity where TRepository : DbContext
{
public BaseRepository(DbContextOptions<TRepository> options) : base(options)
{ }
protected DbSet<TEntity> Entities { get; set; }
public IEnumerable<TEntity> GetAll()
{
return Entities.Where(entity => entity.Active == true) ?? throw new CannotFindEntityException();
}
public TEntity GetById(int id)
{
var entity = Entities.Find(id) ?? throw new CannotFindEntityException(id);
if(!entity.Active)
throw new CannotFindEntityException();
return entity;
}
public void Update(TEntity entity)
{
Entities.Update(entity);
SaveChanges();
}
public void Delete(TEntity entity)
{
var toBeDeleteEntity = GetById(entity.Id);
toBeDeleteEntity.Active = false;
toBeDeleteEntity.DeletedDate = DateTime.Now;
Entities.Update(toBeDeleteEntity);
SaveChanges();
}
public IEnumerable<TEntity> GetFiltered(Func<TEntity, bool> condition = null)
{
return Entities.Where(entity => entity.Active).Where(condition) ?? throw new CannotFindEntityException();
}
}
我的问题:我有更多的存储库方法。它们对于两个存储库都是相同的。添加新功能时,我必须编写两次相同的代码。有没有更好的方法来使用单一基础存储库 class?
使用全局查询过滤器和 SaveChanges() 覆盖在支持它的实体上根据示例 here 实施软删除,并且您不需要两个不同的存储库。
然后进一步简化,只使用带有通用便利方法的单个 DbContext,而不是使整个类型通用。这样您就可以将 Repository 类型与您的所有实体一起使用。同样,为 N 个实体类型配置 N 个不同的 DbContext 类型是行不通的。您将无法编写多实体查询、加载相关数据或编排涉及多个实体类型的事务。
像这样:
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.SqlServer;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace EfCore3Test
{
public abstract class BaseEntity
{
}
public abstract class SoftDeleteEntity : BaseEntity
{
}
public class Recipe : BaseEntity
{
public int RecipeId { get; set; }
public string Title { get; set; }
public ICollection<Resource> Resources { get; } = new List<Resource>();
}
public class Shop : BaseEntity
{
public int ShopId { get; set; }
public string Title { get; set; }
public Resource Logo { get; set; }
}
public class Resource : SoftDeleteEntity
{
public int ResourceId { get; set; }
public string Path { get; set; }
public int ItemRefId { get; set; }
}
public class Repository : DbContext
{
private static readonly ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddFilter((category, level) =>
category == DbLoggerCategory.Database.Command.Name
&& level == LogLevel.Information).AddConsole();
});
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLoggerFactory(loggerFactory)
.UseSqlServer("Server=.;database=EfCore3Test;Integrated Security=true",
o => o.UseRelationalNulls());
base.OnConfiguring(optionsBuilder);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var entitytypes = Assembly.GetExecutingAssembly()
.DefinedTypes
.Where(t => typeof(BaseEntity).IsAssignableFrom(t))
.Where(t => ! t.IsAbstract);
foreach (var et in entitytypes)
{
Console.WriteLine($"Configuring {et.Name} as a Repository Entity");
modelBuilder.Entity(et);
if ( typeof(SoftDeleteEntity).IsAssignableFrom(et) )
{
Console.WriteLine($"Configuring {et.Name} as a SoftDelete Entity");
this.GetType()
.GetMethod(nameof(ConfigureSoftDelete), BindingFlags.NonPublic | BindingFlags.Static)
.MakeGenericMethod(et)
.Invoke(null, new object[] { modelBuilder });
}
}
base.OnModelCreating(modelBuilder);
}
static void ConfigureSoftDelete<T>(ModelBuilder builder) where T : SoftDeleteEntity
{
builder.Entity<T>().Property<bool>("IsDeleted");
builder.Entity<T>().HasQueryFilter(e => EF.Property<bool>(e, "IsDeleted") == false);
}
public override int SaveChanges()
{
foreach (var e in ChangeTracker.Entries())
{
if ( e.State == EntityState.Deleted && e.Entity is SoftDeleteEntity )
{
e.State = EntityState.Unchanged;
e.Property("IsDeleted").CurrentValue = true;
e.Property("IsDeleted").IsModified = true;
}
}
return base.SaveChanges();
}
public IEnumerable<TEntity> GetAll<TEntity>() where TEntity:class
{
return Set<TEntity>();
}
public TEntity GetById<TEntity>(int id) where TEntity : class
{
var entity = Set<TEntity>().Find(id);
if (entity == null)
throw new InvalidOperationException($"No Entity {typeof(TEntity).Name} foound for id {id}");
return entity;
}
public void Delete<TEntity>(TEntity entity) where TEntity : class
{
this.Set<TEntity>().Remove(entity);
}
}
class Program
{
static void Main(string[] args)
{
using var db = new Repository();
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
var r = new Recipe();
r.Resources.Add(new Resource() { ItemRefId = 2, Path = "/" });
db.Add(r);
db.SaveChanges();
db.Delete(r.Resources.First());
db.SaveChanges();
var s = new Shop();
s.Logo = new Resource { ItemRefId = 2, Path = "/" };
db.Add(s);
db.SaveChanges();
s.Logo = null;
db.SaveChanges();
}
}
}