DDD領域驅動開發,實際是為復雜的業務場景而生的,為了讓開發人員專注於業務,而操作系統、數據庫、網絡之類的技術細節,必須要持久透明化:實際就是數據庫系統DBMS的ORM抽象,目標就是業務不需要考慮數據是如何存儲的,業務是獨立於DBMS, 通俗講業務系統不依賴某個DBMS實現,可以通過配置,靈活動態支持各種DBMS,如MS SQL、MySql、Orcacle等。ABP的目標是DDD, 在持久透明化這塊,是用IRepository倉儲抽象來做的,具體的DBMS實現的ORM就放在基礎架構層Infrastructure里。理想是很美好的,實際還是有些坑的,我在做一個項目,當把MSSQL切換到MySql,還是遇到了問題!
public static class DbContextConfigurer { public static void Configure(DbContextOptionsBuilder<DbContext> builder, string connectionString) { //builder.UseSqlServer(connectionString); //使用MSSQL builder //.UseLoggerFactory(MyLoggerFactory) .UseMySql(connectionString); //使用MySql } public static void Configure(DbContextOptionsBuilder<DbContext> builder, DbConnection connection) { //builder.UseSqlServer(connection); builder.UseMySql(connection); } }
用ABP模板生成的項目中,如果使用.netcore和EFcore, 就會有DbContextConfigurer,只要安裝了Microsoft.entityframeworkcore.mysql, 就可以使用UseMySql這個擴展方法,
"ConnectionStrings": { //"Default": "Server=localhost; Database=Db3; Trusted_Connection=True;" "Default": "Server=localhost;port=3306;database=Db3;uid=root;password=root;character set=utf8;Old Guids=true" },
appsettings.json 里ConnectionStrings,也使用Mysql連接字符串,然后再PMC(程序包控制台)執行
update-dabase
完成以上步驟,從MSSQL切換到了Mysql, 大功告成!理想是這樣的,事實上也大部分可行,但會報一些莫名其妙的錯誤:約束錯誤!這些問題,一度讓我很沮喪,甚至想放棄mysql, 迫於linux下裝MSSQL的恐怖,還是堅持用mysql, 后在baidu的幫助下,找到了原因:Microsoft.entityframeworkcore.mysql 有問題,必須用melo.EntityFrameworkCore.MySql
然后再PMC(程序包控制台)執行
install-package Pomelo.EntityFrameworkCore.MySql
再執行update-dabase, 真的一切都好了,其實在ORM的抽象和實現方面,ABP都比較好的Module, 當家的是Microsoft的EF
Abp.EntityFrameWork.Common //EF的公共部分
Abp.EntityFrameWork //EF
Abp.EntityFrameWorkCore //EFCore
還有來自Java陣營的
Abp.NHibernate
還有幾個比較流行的,
Abp.Dapper //據說性能是ef幾十倍的Dapper
Abp.MongoDB //nosql,KV和文檔數據庫
Abp.MemoryDb //內存數據庫,可用於單元測試,棒棒的
個人還是偏愛EF系列,特別是linux下用EFCore+Mysql, 是最佳組合,經過幾個項目的實戰,證明是個不錯選擇,可以放心使用!
ABP有關ORM的抽象機制,主要通過Repostitory和UnitOfWork來做的
namespace Abp.Domain.Repositories { /// <summary> /// This interface is implemented by all repositories to ensure implementation of fixed methods. 說白了就是CRUD得封裝 /// </summary> /// <typeparam name="TEntity">Main Entity type this repository works on</typeparam> /// <typeparam name="TPrimaryKey">Primary key type of the entity</typeparam> public interface IRepository<TEntity, TPrimaryKey> : IRepository where TEntity : class, IEntity<TPrimaryKey> { #region Select/Get/Query ...#endregion #region Insert ...#endregion #region Update ...#endregion #region Delete ...#endregion #region Aggregates ...#endregion } }
/// <summary> /// Defines a unit of work. /// This interface is internally used by ABP. 實際上就是數據庫連接和事務的封裝 /// Use <see cref="IUnitOfWorkManager.Begin()"/> to start a new unit of work. /// </summary> public interface IUnitOfWork : IActiveUnitOfWork, IUnitOfWorkCompleteHandle { /// <summary> /// Unique id of this UOW. /// </summary> string Id { get; } /// <summary> /// Reference to the outer UOW if exists. /// </summary> IUnitOfWork Outer { get; set; } /// <summary> /// Begins the unit of work with given options. /// </summary> /// <param name="options">Unit of work options</param> void Begin(UnitOfWorkOptions options);
具體我們在Service中,只要用DI就可以注入了,其實現機理,以EFCore為例
/// <summary> /// This module is used to implement "Data Access Layer" in EntityFramework. 類似Mybatis的數據訪問層,在ABP中叫基礎架構 /// </summary> [DependsOn(typeof(AbpKernelModule))] public class AbpEntityFrameworkCoreModule : AbpModule { 。。。private void RegisterGenericRepositoriesAndMatchDbContexes() { var dbContextTypes = _typeFinder.Find(type => type.IsPublic && !type.IsAbstract && type.IsClass && typeof(AbpDbContext).IsAssignableFrom(type) ); if (dbContextTypes.IsNullOrEmpty()) { Logger.Warn("No class found derived from AbpDbContext."); return; } using (var repositoryRegistrar = IocManager.ResolveAsDisposable<IEfCoreGenericRepositoryRegistrar>()) { foreach (var dbContextType in dbContextTypes) { Logger.Debug("Registering DbContext: " + dbContextType.AssemblyQualifiedName); repositoryRegistrar.Object.RegisterForDbContext(dbContextType, IocManager); } } 。。。 } }
public class EfCoreGenericRepositoryRegistrar : IEfCoreGenericRepositoryRegistrar, ITransientDependency { 。。。public void RegisterForDbContext(Type dbContextType, IIocManager iocManager) { var autoRepositoryAttr = dbContextType.GetSingleAttributeOrNull<AutoRepositoryTypesAttribute>() ?? EfCoreAutoRepositoryTypes.Default; foreach (var entityTypeInfo in DbContextHelper.GetEntityTypeInfos(dbContextType)) { var primaryKeyType = EntityHelper.GetPrimaryKeyType(entityTypeInfo.EntityType); if (primaryKeyType == typeof(int)) { var genericRepositoryType = autoRepositoryAttr.RepositoryInterface.MakeGenericType(entityTypeInfo.EntityType); if (!iocManager.IsRegistered(genericRepositoryType)) { var implType = autoRepositoryAttr.RepositoryImplementation.GetGenericArguments().Length == 1 ? autoRepositoryAttr.RepositoryImplementation.MakeGenericType(entityTypeInfo.EntityType) : autoRepositoryAttr.RepositoryImplementation.MakeGenericType(entityTypeInfo.DeclaringType, entityTypeInfo.EntityType); iocManager.Register( genericRepositoryType, implType, DependencyLifeStyle.Transient ); } } var genericRepositoryTypeWithPrimaryKey = autoRepositoryAttr.RepositoryInterfaceWithPrimaryKey.MakeGenericType(entityTypeInfo.EntityType, primaryKeyType); if (!iocManager.IsRegistered(genericRepositoryTypeWithPrimaryKey)) { var implType = autoRepositoryAttr.RepositoryImplementationWithPrimaryKey.GetGenericArguments().Length == 2 ? autoRepositoryAttr.RepositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.EntityType, primaryKeyType) : autoRepositoryAttr.RepositoryImplementationWithPrimaryKey.MakeGenericType(entityTypeInfo.DeclaringType, entityTypeInfo.EntityType, primaryKeyType); iocManager.Register( genericRepositoryTypeWithPrimaryKey, implType, DependencyLifeStyle.Transient ); } } } }
整個目的就是為了可以注入泛型的IRepostitory<TEntity, TPrimaryKey>,我們的service,就可以愉快的使用了
public IRepository<FoodMaterial, long> FoodMaterialRepository { get; set; } //屬性注入 //或 private IRepository<FoodMaterialCategory, long> _categoryRepository; public FoodMaterialAppService(IRepository<FoodMaterial, long> repository , IRepository<FoodMaterialCategory, long> categoryRepository) : base(repository) { _categoryRepository = categoryRepository; } // 構造注入
總結:.net 已經擁抱開源和linux了,曾經的四大金剛LAMP(Linux+Apache+Mysql+Php),如今.net也可以Cover了,跨平台(操作系統、數據庫等),是一個上線系統的必須,特別部署到雲上,linux+mysql的性價比還是比較高的,可真正要做到跨平台和持久透明,在設計DB時,還是要注意:不要用存儲過程,所有邏輯必須在代碼里完成,DB只是用來存儲的;ABP在這個領域,給了我們OutOfBox開箱即用的很多Module, 可以讓我們效率大大提升!