需要存储库设计模式建议

时间:2020-04-24 23:09:57

标签: c# design-patterns .net-core entity-framework-core repository-pattern

我有很多实体和存储库。我的实体有两种:可擦和不可擦。所以我有两个实体的基类。

不可磨灭的实体实现此基类:

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; }
}

可擦除实体实现此基类:

    public abstract class BaseErasableEntity
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]       
    public int Id { get; set; }
    public DateTime InsertedDate { get; set; }
    public DateTime? UpdatedDate { get; set; }
}

使用可擦除实体的存储库实现此基类:

 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();
    }
}

使用不可删除实体的存储库实现此基类:

 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();
    }
}

我的问题:我有更多的存储库方法。它们对于两个存储库都是相同的。添加新功能时,我必须编写两次相同的代码。有没有更好的方法来使用单个基本存储库类?

1 个答案:

答案 0 :(得分:2)

使用全局查询过滤器和SaveChanges()覆盖对支持它的实体上的示例here进行软删除,并且您不需要两个不同的存储库。

然后进一步简化,仅使用具有通用便捷方法的单个DbContext,而不是使整个类型通用。这样,您可以对所有实体使用存储库类型。同样无法为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();
        }
    }
}
相关问题