1. 程式人生 > 實用技巧 >ABP框架實戰系列(一)-持久層介紹篇

ABP框架實戰系列(一)-持久層介紹篇

ABP框架實戰系列(一)-持久層介紹篇

資料持久化

在開始持久層的介紹之前,我們先引入一個基礎概念:持久化

狹義的理解: “持久化”僅僅指把域物件永久儲存到資料庫中;廣義的理解,“持久化”包括和資料庫相關的各種操作(持久化就是將有用的資料以某種技術儲存起來,將來可以再次取出來應用,資料庫技術,將記憶體資料一檔案的形式儲存在永久介質中(磁碟等)都是持久化的意思。

但是僅僅的持久化會使專案不可維護或者後期維護不利,簡單的儲存功能已經完全滿足不了現在軟體開發的模組性、可維護性、
擴充套件性、分層性原則,所以就需要一種技術框架,將業務層和資料庫之間儲存的操作做到可維護性、擴充套件性、分層性,於是就出現“持久層”的概念

資料持久層

持久層:設計目標是為整個專案提供一個銜接高低層、統一、安全和併發的資料持久機制,完成對各種資料庫進行持久化的程式設計工作,併為系統業務邏輯提供服務。資料持久層提供了資料訪問方法,能夠使程式設計師避免手動編寫程式訪問資料持久層,使其專注於業務邏輯的開發,並且能夠在不同的專案中重用對映框架,大大簡化了資料增刪改查等功能的開發過程,同時又不喪失多層結構的天然優勢,具備可伸縮性和可擴充套件性

ORM

ORM資料持久層的一種子實現,它通過將對映的機制,把資料庫中的一條記錄當做程式的一個類處理,這樣在CURD的處理上,真正實現了面向物件開發,也將軟體的後期維護週期大大縮短

市面上 .Net Core ORM 工具多如牛毛,就列舉如下四種常見框架,使用方式大同小異,此次鏈上一篇很不錯的EF Core 的入門教程:

EntityFrameWork Core(推薦關注該公眾號,乾貨滿滿)

[FreeSql]

[Nhibernate-core]

[MyBatis]

ORM優缺點

  • 優點
    • ORM最大的優勢,隱藏了資料訪問細節,“封閉”的通用資料庫互動,ORM的核心。他使得我們的通用資料庫互動變得簡單易行,並且完全不用考慮該死的SQL語句。快速開發,由此而來。
    • ORM使我們構造固化資料結構變得簡單易行。在ORM年表的史前時代,我們需要將我們的物件模型轉化為一條一條的SQL語句,通過直連或是DB helper在關係資料庫構造我們的資料庫體系。而現在,基本上所有的ORM框架都提供了通過物件模型構造關係資料庫結構的功能。
  • 缺點
    • 對於複雜查詢,ORM仍然力不從心。雖然可以實現,但是不值的。檢視可以解決大部分calculated column,case ,group,having,order by, exists

ABP 框架的 Entity FrameWork Core

DbContext

EF核心需要定義來自DbContext類。在ABP,我們應該從abpdbcontext獲得,如下所示

public class MyDbContext : AbpDbContext
{
    public DbSet<Product> Products { get; set; }

    public MyDbContext(DbContextOptions<MyDbContext> options)
        : base(options)
    {
    }
}

建構函式必須得到 DbContextOptions 如上面所示. 引數名稱必須是選項。無法改變它,因為ABP將它作為匿名物件引數提供。

Configuration(配置資料庫連線字串)
ABP Module Zero 中,將路徑配置在appsettings.json檔案中 配置格式基本如下

"ConnectionStrings": {
    "Default": "Data Source = 10.28.253.2;Initial Catalog = EventCloudDb;User Id = sa;Password = Ecsgui123;"

  }

而對配置檔案的載入、以及DbContext的註冊是通過如下程式碼完成的(ABP 預設生成)
程式碼中,EFModule 繼承自 ABPModule,表名EF 這個模組,是一個可插拔的模組,該模組下有三個初始化函式PreInitialize(預初始化)、Initialize(初始化)、PostInitialize(StartUp 完成之後執行)。

其中,PreInitilize方法中,包含ABP 框架下對模組中的AbpEFCore模組的註冊。

    [DependsOn(
        typeof(UniversalCoreModule), 
        typeof(AbpZeroCoreEntityFrameworkCoreModule))]
    public class UniversalEntityFrameworkModule : AbpModule
    {
        /* Used it tests to skip dbcontext registration, in order to use in-memory database of EF Core */
        public bool SkipDbContextRegistration { get; set; }

        public bool SkipDbSeed { get; set; }

        public override void PreInitialize()
        {
            if (!SkipDbContextRegistration)
            {
                Configuration.Modules.AbpEfCore().AddDbContext<UniversalDbContext>(options =>
                {
                    if (options.ExistingConnection != null)
                    {
                        UniversalDbContextConfigurer.Configure(options.DbContextOptions, options.ExistingConnection);
                    }
                    else
                    {
                        UniversalDbContextConfigurer.Configure(options.DbContextOptions, options.ConnectionString);
                    }
                });
            }
        }

        public override void Initialize()
        {
            IocManager.RegisterAssemblyByConvention(typeof(UniversalEntityFrameworkModule).GetAssembly());
        }

        public override void PostInitialize()
        {
            if (!SkipDbSeed)
            {
                SeedHelper.SeedHostDb(IocManager);
            }
        }
    }

在熟悉了上述DbContext載入配置的過程後,我們就相對應的會來到建立DbContext部分,這部分內容,ABP 模板在UniversalDbContextFactory中,實現了物件DbContext的建立

1、建立DbContextOptionBuilder建立

2、載入配置檔案資訊到記憶體

3、初始化Builder 資訊

4、new 一個新的 DbContext物件


 public class Git_ECSGUI_UniversalDbContextFactory : IDesignTimeDbContextFactory<Git_ECSGUI_UniversalDbContext>
    {
        public Git_ECSGUI_UniversalDbContext CreateDbContext(string[] args)
        {
            var builder = new DbContextOptionsBuilder<Git_ECSGUI_UniversalDbContext>();
            var configuration = AppConfigurations.Get(WebContentDirectoryFinder.CalculateContentRootFolder());

            Git_ECSGUI_UniversalDbContextConfigurer.Configure(builder, configuration.GetConnectionString(Git_ECSGUI_UniversalConsts.ConnectionStringName));

            return new Git_ECSGUI_UniversalDbContext(builder.Options);
        }
    }

ABP 的倉儲

講完EF Core中的DbContext在ABP中的建立過程,我們將視線轉到Repositories(倉庫)中過來,他是領域層和持久之間的橋樑,儲存庫用於從高層抽象資料訪問。
在ABP 框架中領域和持久化層之間的媒介,使用一種類似集合的介面來訪問實體,每個實體(或者聚合根)使用給一個分離的倉庫

預設倉儲

在ABP裡,一個倉儲類實現IRepository<TEntity,TPrimaryKey>介面。ABP預設地為每個實體型別自動建立一個預設倉儲。你可以直接注入IRepository(或IRepository<TEntity,TPrimaryKey>)。一個應用服務使用倉儲把一個實體插入資料庫的例子:PersonService構造器注入IRepository並使用Insert方法。


public class PersonService : IPersonService
{
    private readonly IRepository<Person> _personRepository;

    public PersonService(IRepository<Person> personRepository)
    {
        _personRepository = personRepository;
    }

    public void CreatePerson(CreatePersonInput input)
    {        
        person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };
        _personRepository.Insert(person);
    }
}

自定義倉儲

只有當實體需要建立一個自定義的倉儲方法時,才需要你建立一個倉儲類。


    public abstract class Git_ECSGUI_UniversalRepositoryBase<TEntity, TPrimaryKey> : EfCoreRepositoryBase<Git_ECSGUI_UniversalDbContext, TEntity, TPrimaryKey>
        where TEntity : class, IEntity<TPrimaryKey>
    {
        protected Git_ECSGUI_UniversalRepositoryBase(IDbContextProvider<Git_ECSGUI_UniversalDbContext> dbContextProvider)
            : base(dbContextProvider)
        {
        }

        // Add your common methods for all repositories
    }

    /// <summary>
    /// Base class for custom repositories of the application.
    /// This is a shortcut of <see cref="Git_ECSGUI_UniversalRepositoryBase{TEntity,TPrimaryKey}"/> for <see cref="int"/> primary key.
    /// </summary>
    /// <typeparam name="TEntity">Entity type</typeparam>
    public abstract class Git_ECSGUI_UniversalRepositoryBase<TEntity> : Git_ECSGUI_UniversalRepositoryBase<TEntity, int>, IRepository<TEntity>
        where TEntity : class, IEntity<int>
    {
        protected Git_ECSGUI_UniversalRepositoryBase(IDbContextProvider<Git_ECSGUI_UniversalDbContext> dbContextProvider)
            : base(dbContextProvider)
        {
        }

        // Do not add any method here, add to the class above (since this inherits it)!!!
    }

要實現自定義儲存庫,只需從上面建立的應用程式特定的基礎儲存庫類中獲得。


        public interface IUniversalTaskRepository : IRepository<Task>
    {
        List<Task> GetTaskByAssignedPersonId(long taskId);
    }

    public UniversalTaskRepository:Git_ECSGUI_UniversalRepositoryBase, IUniversalTaskRepository
    {
        public UniversalTaskRepository(IDbContextProvider<Git_ECSGUI_UniversalDbContext> dbContextProvider) : base(dbContextProvider)
        {
        }

        /// <summary>
        /// 獲取某個使用者分配了哪些任務
        /// </summary>
        /// <param name="personId">使用者Id</param>
        /// <returns>任務列表</returns>
        public List<Task> GetUniversalTaskId(long taskId)
        {
            var query = GetAll();

            if (taskId > 0)
            {
                query = query.Where(t => t.taskid == taskId);
            }

            return query.ToList();
        }
    }

倉儲的注意事項

  • 倉儲方法中,ABP自動進行資料庫連線的開啟和關閉。
  • 倉儲方法被呼叫時,資料庫連線自動開啟且啟動事務。
  • 當倉儲方法呼叫另外一個倉儲的方法,它們實際上共享的是同一個資料庫連線和事務。
  • 倉儲物件都是暫時性的,因為IRepository介面預設繼承自ITransientDependency介面。所以,倉儲物件只有在需要注入的時候,才會由Ioc容器自動建立新例項。
  • 預設的泛型倉儲能滿足我們大部分的需求。只有在不滿足的情況下,才建立定製化的倉儲。

博主GitHub地址

https://github.com/yuyue5945

關注公眾號留下您的困惑或見解