нужен совет по шаблону дизайна репозитория - PullRequest
0 голосов
/ 25 апреля 2020

У меня много сущностей и репозиториев. Мои сущности бывают двух типов: стираемые и несмываемые. Итак, у меня есть два базовых класса для сущностей.

Несмываемые сущности реализуют этот базовый класс:

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 Ответ

2 голосов
/ 26 апреля 2020

Используйте глобальные фильтры запросов и переопределение SaveChanges (), чтобы реализовать мягкое удаление для образца здесь на объектах, которые его поддерживают, и вам не нужны два разных репозитория.

Затем упростите еще больше и просто используйте один DbContext с универсальными c вспомогательными методами вместо создания всего типа generi c. Таким образом, вы можете использовать тип Repository со всеми вашими сущностями. Также настройка N различных типов DbContext для N типов сущностей просто не будет работать. Вы не сможете писать запросы с несколькими объектами, загружать связанные данные или организовывать транзакции с несколькими типами объектов.

Примерно так:

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