RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 902787
Accepted
Aldmi
Aldmi
Asked:2020-11-06 14:24:44 +0000 UTC2020-11-06 14:24:44 +0000 UTC 2020-11-06 14:24:44 +0000 UTC

实体框架核心的存储库模式。有问题

  • 772

我一直使用存储库模式,但更像是单元测试所需的包装器,可以通过装饰器和其他一些贴花功能嵌入缓存,仅此而已,我没有看到任何其他好处。

我对模式经典实现的疑惑和疑问:

  1. 不仅在使用数据访问层的项目(特定存储技术的特定存储库)中存在上下文依赖关系,而且在必须在 DI 中注册 Ef 上下文的主项目中也存在上下文依赖关系。以便稍后存储库在构造函数中接收上下文。这不是很奇怪吗?该思想意味着使用抽象存储库作为对数据的通用访问,例如,10 个存储库实现 IGenericDataRepository,每个存储库(项目)都有自己的依赖项和自己的数据存储模型。也就是说,EFCore 上下文只能在实现基于 EFCore 技术的存储系统的项目中使用。与外界的唯一连接是设置和连接字符串。但基本上每个人都只使用这种存储库模式的变体(甚至在 EFCore 场外给出了一个示例)。

  2. 一些暴露在外面的 IQuerible 而不是 Ienumerable ——那何必费心呢,DbSet 接口已经不错了。

我试图制作最抽象的存储库,可以在 DI 中用各种存储技术实现(通过 EFCore 的 SQL,存储在 XML 文件中,存储在 NoSql 中)来替换。下面我将给出项目的结构和代码,请发表你的意见,T.K. 出现了一个具有复杂数据层的项目,我再次考虑存储库。您可能不必用另一个存储系统替换 EfCoreRepository,但您仍然希望尽可能独立和抽象地处理数据。

项目结构

DAL.Abstract 项目包含:

IGenericDataRepository.cs- 抽象存储库接口

    public interface IGenericDataRepository<T>
    {
        T GetById(int id);
        Task<T> GetByIdAsync(int id);

        T GetSingle(Expression<Func<T, bool>> predicate);
        Task<T> GetSingleAsync(Expression<Func<T, bool>> predicate);
        IEnumerable<T> GetWithInclude(params Expression<Func<T, object>>[] includeProperties); //?????

        IEnumerable<T> List();
        IEnumerable<T> List(Expression<Func<T, bool>> predicate);
        Task<IEnumerable<T>> ListAsync();
        Task<IEnumerable<T>> ListAsync(Expression<Func<T, bool>> predicate);

        int Count(Expression<Func<T, bool>> predicate);
        Task<int> CountAsync(Expression<Func<T, bool>> predicate);

        void Add(T entity);
        Task AddAsync(T entity);

        void AddRange(IEnumerable<T> entitys); 
        Task AddRangeAsync(IEnumerable<T> entitys); 

        void Delete(T entity);
        void Delete(Expression<Func<T, bool>> predicate);
        Task DeleteAsync(T entity);
        Task DeleteAsync(Expression<Func<T, bool>> predicate);

        void Edit(T entity);
        Task EditAsync(T entity);

        bool IsExist(Expression<Func<T, bool>> predicate);
        Task<bool> IsExistAsync(Expression<Func<T, bool>> predicate);
    }

IRepository.cs- 特定存储库的接口。突然之间,您需要一种非常具体的方法来处理特定存储库的数据,而不是将其添加到 IGenericDataRepository。

public interface ISerialPortOptionRepository : IGenericDataRepository<SerialOption>
{  
}
public interface ITcpIpOptionRepository : IGenericDataRepository<TcpIpOption>
{
}

public interface IHttpOptionRepository : IGenericDataRepository<HttpOption>
{
}

public interface IExchangeOptionRepository : IGenericDataRepository<ExchangeOption>
{
}
public interface IDeviceOptionRepository : IGenericDataRepository<DeviceOption>
{
}

在文件夹Entities中,需要保存在存储库中的数据模型。纯模型(没有属性和附加存储特定设备的属性)

DeviceOption.cs- 某种可用于业务逻辑的数据模型

    public class DeviceOption : EntityBase
    {
        public string Name { get; set; }
        public string TopicName4MessageBroker { get; set; }         
        public string Description { get; set; }
        public bool AutoBuild { get; set; }                        
        public bool AutoStart{ get; set; }                
        public List<string> ExchangeKeys { get; set; }
    }

该项目DAL.EFCore包含特定存储技术的实施

进入数据模型文件夹,可以方便地Entities在其中存储特定技术的数据(在本例中为EFCore)。

EfDeviceOption.cs- 相同的模型 ( DeviceOption),仅适用于存储系统,以它可以理解的形式。

public class EfDeviceOption : IEntity
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int Id { get; set; }

    [Required]
    [MaxLength(256)]
    public string Name { get; set; }

    [Required]
    [MaxLength(256)]
    public string TopicName4MessageBroker { get; set; }         

    [Required]
    public string Description { get; set; }
    public bool AutoBuild { get; set; }                         
    public bool AutoStart { get; set; }                        


    private string _exchangeKeysMetaData;
    [NotMapped]
    public string[] ExchangeKeys
    {
        get => _exchangeKeysMetaData.Split(';');
        set => _exchangeKeysMetaData = string.Join($"{';'}", value);
    }
}

Context.cs- EfCore 的数据上下文。

public sealed class Context : Microsoft.EntityFrameworkCore.DbContext
{
    private readonly string _connStr;  // строка подключенния

    #region Reps

    public DbSet<EfSerialOption> SerialPortOptions { get; set; }
    public DbSet<EfTcpIpOption> TcpIpOptions { get; set; }
    public DbSet<EfHttpOption> HttpOptions { get; set; }
    public DbSet<EfDeviceOption> DeviceOptions { get; set; }
    public DbSet<EfExchangeOption> ExchangeOptions { get; set; }

    #endregion

    #region ctor

    public Context(string connStr)
    {
        _connStr = connStr;
        ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
        Database.EnsureCreated();
    }

    #endregion

    #region Config

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(_connStr);
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
       modelBuilder.ApplyConfiguration(new EfDeviceOptionConfig());
       modelBuilder.ApplyConfiguration(new EfExchangeOptionConfig());
       modelBuilder.ApplyConfiguration(new EfHttpOptionConfig());
       base.OnModelCreating(modelBuilder);
    }

    #endregion
}

DesignTimeDbContextFactory.cs- 用于迁移系统的上下文创建工厂 - 模型与之间的AutoMapperConfig.cs映射设置DAL.AbstractDAL.EFCore

Repository在特定存储库的文件夹实现中

EfBaseRepository.cs- EfCore 的基础存储库类

/// <summary>
/// Базовый тип репозитория для EntitiFramework
/// </summary>
/// <typeparam name="TDb">Тип в системе хранения</typeparam>
/// <typeparam name="TMap">Тип в бизнесс логики</typeparam>
public abstract class EfBaseRepository<TDb, TMap> : IDisposable
                                                    where TDb : class, IEntity
                                                    where TMap : class
{
    #region field
    protected readonly Context Context;
    protected readonly DbSet<TDb> DbSet;
    #endregion


    #region ctor
    protected EfBaseRepository(string connectionString)
    {
        Context = new Context(connectionString);
        DbSet = Context.Set<TDb>();
    }
    static EfBaseRepository()
    {
        AutoMapperConfig.Register();
    }
    #endregion


    #region CRUD
    protected TMap GetById(int id)
    {
        var efSpOption = DbSet.Find(id);
        var spOptions = AutoMapperConfig.Mapper.Map<TMap>(efSpOption);
        return spOptions;
    }

    protected async Task<TMap> GetByIdAsync(int id)
    {
        var efSpOption = await DbSet.FindAsync(id);
        var spOptions = AutoMapperConfig.Mapper.Map<TMap>(efSpOption);
        return spOptions;
    }

    protected TMap GetSingle(Expression<Func<TMap, bool>> predicate)
    {
        var efPredicate = AutoMapperConfig.Mapper.MapExpression<Expression<Func<TDb, bool>>>(predicate);
        var efSpOption = DbSet.SingleOrDefault(efPredicate);
        var spOption = AutoMapperConfig.Mapper.Map<TMap>(efSpOption);
        return spOption;
    }

    protected async Task<TMap> GetSingleAsync(Expression<Func<TMap, bool>> predicate)
    {
        var efPredicate = AutoMapperConfig.Mapper.MapExpression<Expression<Func<TDb, bool>>>(predicate);
        var efSpOption = await DbSet.SingleOrDefaultAsync(efPredicate);
        var spOption = AutoMapperConfig.Mapper.Map<TMap>(efSpOption);
        return spOption;
    }

    // ... И ДРУГИЕ МЕТОДЫ РЕПОЗИТОРИЯ
    #endregion


    #region Methode
    private IQueryable<TDb> Include(params Expression<Func<TDb, object>>[] includeProperties)
    {
        IQueryable<TDb> query = DbSet.AsNoTracking();
        return includeProperties.Aggregate(query, (current, includeProperty) => current.Include(includeProperty));
    }
    #endregion


    #region Disposable
    public void Dispose()
    {
        Context?.Dispose();
    }
    #endregion
}

EfDeviceOptionRepository.cs- 特定的存储库实施IDeviceOptionRepository

public class EfExchangeOptionRepository : EfBaseRepository<EfExchangeOption, ExchangeOption>, IExchangeOptionRepository
{
    #region ctor

    public EfExchangeOptionRepository(string connectionString) : base(connectionString)
    {
    }

    #endregion



    #region CRUD

    public new ExchangeOption GetById(int id)
    {
        return base.GetById(id);
    }

    public new async Task<ExchangeOption> GetByIdAsync(int id)
    {
        return await base.GetByIdAsync(id);
    }

    public new ExchangeOption GetSingle(Expression<Func<ExchangeOption, bool>> predicate)
    {
        return base.GetSingle(predicate);
    }

     // ... И ДРУГИЕ МЕТОДЫ РЕПОЗИТОРИЯ (ЕCЛИ protected ДОCТУП В БАЗОВОМ КЛАССЕ ПОМЕНЯТЬ НА public то можно использовать базовую реализацию, не замещая метод через new)

    #endregion
}

该项目BL.Services包含各种业务逻辑服务,其中一项服务与存储库集成工作,提供方便的界面。

MediatorForOptions.cs- 一些与存储库一起使用的高级逻辑

/// <summary>
/// Сервис объединяет работу с репозиотриями опций для устройств.
/// DeviceOption + ExchangeOption + TransportOption.
/// </summary>
public class MediatorForOptions
{
    #region fields
    private readonly IDeviceOptionRepository _deviceOptionRep;
    private readonly IExchangeOptionRepository _exchangeOptionRep;
    private readonly ISerialPortOptionRepository _serialPortOptionRep;
    private readonly ITcpIpOptionRepository _tcpIpOptionRep;
    private readonly IHttpOptionRepository _httpOptionRep;
    #endregion


    #region ctor
    public MediatorForOptions(IDeviceOptionRepository deviceOptionRep,
        IExchangeOptionRepository exchangeOptionRep,
        ISerialPortOptionRepository serialPortOptionRep,
        ITcpIpOptionRepository tcpIpOptionRep,
        IHttpOptionRepository httpOptionRep)
    {
        _deviceOptionRep = deviceOptionRep;
        _exchangeOptionRep = exchangeOptionRep;
        _serialPortOptionRep = serialPortOptionRep;
        _tcpIpOptionRep = tcpIpOptionRep;
        _httpOptionRep = httpOptionRep;
    }
    #endregion


    #region Methode
     //МЕТОДЫ ОБЪЕДИНЯЮЩИЕ РАБОТУ С РЕПОЗИТОРИЯМИ 
    #endregion
}

项目WebServer- 应用程序入口点 ( WebApi)Autofac用作DI容器。

RepositoryAutofacModule.cs - 用于注册 DI 依赖项以解析存储库的模块(选择特定的存储系统)

public class RepositoryAutofacModule : Module
    {
        private readonly string _connectionString;


        #region ctor
        public RepositoryAutofacModule(string connectionString)
        {
            _connectionString = connectionString;
        }
        #endregion


        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterType<EfSerialPortOptionRepository>().As<ISerialPortOptionRepository>()
                .WithParameters(new List<Parameter>
                {
                    new NamedParameter("connectionString", _connectionString),
                })
                .InstancePerLifetimeScope();

            builder.RegisterType<EfTcpIpOptionRepository>().As<ITcpIpOptionRepository>()
                .WithParameters(new List<Parameter>
                {
                    new NamedParameter("connectionString", _connectionString),
                })
                .InstancePerLifetimeScope();

            builder.RegisterType<EfHttpOptionRepository>().As<IHttpOptionRepository>()
                .WithParameters(new List<Parameter>
                {
                    new NamedParameter("connectionString", _connectionString),
                })
                .InstancePerLifetimeScope();

            builder.RegisterType<EfExchangeOptionRepository>().As<IExchangeOptionRepository>()
                .WithParameters(new List<Parameter>
                {
                    new NamedParameter("connectionString", _connectionString),
                })
                .InstancePerLifetimeScope();

            builder.RegisterType<EfDeviceOptionRepository>().As<IDeviceOptionRepository>()
                .WithParameters(new List<Parameter>
                {
                    new NamedParameter("connectionString", _connectionString),
                })
                .InstancePerLifetimeScope();
        }
    }

MediatorsAutofacModule.cs- 用于注册 DI 依赖项以解决业务逻辑服务的模块。

MediatorsAutofacModule.cs   
{
    protected override void Load(ContainerBuilder builder)
    {
        builder.RegisterType<MediatorForOptions>().InstancePerDependency();
    }
}

全部 - - - - - - - - - - - - -

т.е. я использую MediatorForOptions для работы с опциями везде по проекту.

МИНУСЫ которые вижу я 

1. МНОООГО маппинга - т.к. у каждой системы хранения своя модель данных, но система хранения обязуется работать в общих типах (Entities из DAL.Abstract).

2. Запросы к БД сложно оптимизировать т.к. наружу торчит не IQuereble, а Ienumerable. Следовательно каждый метод репозитория выполняет какое-то 1 действие и их нельзя объединит. (паттерн UnitOfWork не использую).

3. В новой версии 2.1 EfCore появилась система регистрации контекста в ПУЛЕ (services.AddDbContextPool(...)), вместо perScope. Что должно увеличить производительность. Но в моей модели где я контекст создаю сам, эту фишку НЕЛЬЗЯ использовать.

4. Довольно много кода.

Стоит ли вообще заморачиваться?

И что можно улучшить?

c#
  • 1 1 个回答
  • 10 Views

1 个回答

  • Voted
  1. Best Answer
    A K
    2020-11-06T15:03:20Z2020-11-06T15:03:20Z

    Из подобных приложений на гитхабе сразу вспоминается RealWorld example app, хотя вообще вариаций шаблона масса.

    У Марка Симана ( Mark Seeman) в его книге Dependency injection CSharp показан вариант, как в приложении совсем отвязаться от EF - и я как-то ради любопытства собрал приложение, полностью отвязанное от DAL (солюшен, в котором было две реализации dal - одна на EF, другая на dapper, переключаться можно было в рантайме, а студия показывала что проект с exe не зависел от этих двух). Так что если хотите - отвязаться можно практически полностью, было бы желание.

    По поводу DbSet вы правильно пишете, это ведь уже готовая реализация паттерна репозиторий от майкрософт и он уже есть в EF. Да и в книгах/статях такое много где проскальзывает, плюс на so обсуждалось в комментариях к вопросам/ответам (можете поискать у PashaPash подобное - в моих и Bald вопросах). Я когда-то раньше предпочитал IEnumerable и IReadOnlyCollection только за то, что меньше зависимостей от EF. У Симана в статьях много порой спорных моментов, поэтому лучше составьте своё мнение. Или даже так: его не понимают те, кто не особо любит DDD и предпочитает готовые либы от майкрософт не выходя особо за рамки типовых решений. Я думаю, что с вашим подходом вам наоборот многое понравится. И ещё - вы скоро придёте к тому, что EF даже и для миграций не особо-то и поймёте прелесть подхода Database First (который в core практически полностью под нож пустили)

    О маппинге. От маппинга при подобном подходе никуда не деться. Я видел много вариантов реализаций паттерна и есть только один способ (неправильный, разумеется) не делать постоянный маппинг - это когда одни и те же классы используются и как доменные объекты и как объекты DAL. Однако если вы читали "Чистую архитектуру" дядюшки Боба и понимаете, что такое архитектурные слои и как выглядят архитектурные границы -- то должны понимать, что либо вы чётко обозначаете архитектурную границу и тогда - только конвертация из одних классов в другие на границе (маппинг, либо вручную писать, либо на автомапперы полагаться), либо стирание этой границы.

    • 2

相关问题

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    是否可以在 C++ 中继承类 <---> 结构?

    • 2 个回答
  • Marko Smith

    这种神经网络架构适合文本分类吗?

    • 1 个回答
  • Marko Smith

    为什么分配的工作方式不同?

    • 3 个回答
  • Marko Smith

    控制台中的光标坐标

    • 1 个回答
  • Marko Smith

    如何在 C++ 中删除类的实例?

    • 4 个回答
  • Marko Smith

    点是否属于线段的问题

    • 2 个回答
  • Marko Smith

    json结构错误

    • 1 个回答
  • Marko Smith

    ServiceWorker 中的“获取”事件

    • 1 个回答
  • Marko Smith

    c ++控制台应用程序exe文件[重复]

    • 1 个回答
  • Marko Smith

    按多列从sql表中选择

    • 1 个回答
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Suvitruf - Andrei Apanasik 什么是空? 2020-08-21 01:48:09 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5