在ABP框架下使用NHibernate和Dapper實現資料庫訪問。 ...
為了防止不提供原網址的轉載,特在這裡加上原文鏈接:
http://www.cnblogs.com/skabyy/p/7517397.html
本篇我們實現資料庫的訪問。我們將實現兩種資料庫訪問方法來訪問一個SQLite資料庫——使用NHibernate實現的ORM映射訪問和使用Dapper實現的SQL語句訪問。然後完成前一篇未完成的CreateTweet
和GetTweets
介面。
在開始之前,先做一些準備工作,新建Domain層的Module:
public class MyTweetDomainModule : AbpModule
{
public override void Initialize()
{
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
}
}
同時MyTweetApplicationModule
添加對MyTweetDomainModule
的依賴:
[DependsOn(typeof(MyTweetDomainModule))]
public class MyTweetApplicationModule : AbpModule
安裝NuGet包Abp.NHibernate
到MyTweet.Domain
和MyTweet.Infrastructure
。
下麵我們將完成這些步驟來實現資料庫的訪問:
- 配置資料庫連接
- 新建
tweet
表以及相應的Model類型 - 實現訪問數據的Repository
- 用Dapper實現通過SQL訪問資料庫
使用Fluent NHibernate配置資料庫連接
我們這裡使用的資料庫是SQLite資料庫,其他資料庫的配置也是類似的。我們將連接到App_Data
文件夾下的一個SQLite資料庫。新建LocalDbSessionProvider
類併在構造函數處配置資料庫連接。由於LocalDbSessionProvider
實現了介面ISingletonDependency
,模塊初始化時LocalDbSessionProvider
會以單例的形式註冊到IoC容器。
public class LocalDbSessionProvider : ISessionProvider, ISingletonDependency, IDisposable
{
protected FluentConfiguration FluentConfiguration { get; private set; }
private ISessionFactory _sessionFactory;
public LocalDbSessionProvider()
{
FluentConfiguration = Fluently.Configure();
// 資料庫連接串
var connString = "data source=|DataDirectory|MySQLite.db;";
FluentConfiguration
// 配置連接串
.Database(SQLiteConfiguration.Standard.ConnectionString(connString))
// 配置ORM
.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()));
// 生成session factory
_sessionFactory = FluentConfiguration.BuildSessionFactory();
}
private ISession _session;
public ISession Session
{
get
{
if (_session != null)
{
// 每次訪問都flush上一個session。這裡有效率和多線程問題,暫且這樣用,後面會改。
_session.Flush();
_session.Dispose();
}
_session = _sessionFactory.OpenSession();
return _session;
}
}
public void Dispose()
{
_sessionFactory.Dispose();
}
}
這裡每次用到session都只是簡單地把上一次的session flush
了,然後打開新的session。這會有效率和多線程衝突的問題。這裡只是單純為了展示實現資料庫鏈接的方法而先用的簡單實現。後面做工作單元(UoW)時會解決這個問題。
為了NHibernate能創建SQLite的連接,還需要安裝System.Data.SQLite.Core
到MyTweet.Web
(其他資料庫的話要安裝其他相應的包)。
新建tweet
表以及相應的Model類型
我們用tweet
表保存tweet數據。tweet數據表介面以及對應Model屬性如下:
資料庫欄位 | Model屬性 | 類型 | 描述 |
---|---|---|---|
pk_id |
PkId |
string |
主鍵 |
content |
Content |
string |
內容 |
create_time |
CreateTime |
string |
創建時間 |
使用SQLite工具新建MySQLite.db文件,並新建表tweet
。
然後將MySQLite.db文件拷貝到App_Data
文件夾下。
CREATE TABLE `tweet` (
`pk_id` TEXT,
`content` TEXT,
`create_time` TEXT NOT NULL,
PRIMARY KEY(`pk_id`)
);
接下來新建Model類Tweet
以及映射TweetMapper
。Tweet
繼承Entity<string>
,其中的string
表示Tweet
的主鍵Id
是string
類型的。TweetMapper
繼承ClassMap<Tweet>
,上面LocalDbSessionProvider
構造函數執行到.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()))
這個方法時,會用反射的方式搜索程式集中ClassMap<T>
的子類,建立Model和資料庫表的映射(Tweet
和tweet
表的映射)。
public class Tweet : Entity<string> // 主鍵為string類型
{
public string Content { get; set; }
public DateTime CreateTime { get; set; }
}
public class TweetMapper : ClassMap<Tweet>
{
public TweetMapper()
{
// 禁用惰性載入
Not.LazyLoad();
// 映射到表tweet
Table("tweet");
// 主鍵映射
Id(x => x.Id).Column("pk_id");
// 欄位映射
Map(x => x.Content).Column("content");
Map(x => x.CreateTime).Column("create_time");
}
}
實現Repository與增查介面
Repository即是DDD中的倉儲,它封裝了數據對象的增刪改查操作。ABP的NhRepositoryBase
已經實現了常用的增刪改查功能,因此這裡只需要繼承一下就行了。
public interface ITweetRepository : IRepository<Tweet, string> { }
public class TweetRepository : NhRepositoryBase<Tweet, string>, ITweetRepository
{
public TweetRepository()
: base(IocManager.Instance.Resolve<LocalDbSessionProvider>())
{ }
}
最後,修改MyTweetAppService
,實現CreateTweet
介面和GetTweets
介面。
public class CreateTweetInput
{
public string Content { get; set; }
}
public class MyTweetAppService : ApplicationService, IMyTweetAppService
{
public ITweetRepository TweetRepository { get; set; }
public object GetTweets(string msg)
{
return TweetRepository.GetAll().OrderByDescending(x => x.CreateTime).ToList();
}
public object CreateTweet(CreateTweetInput input)
{
var tweet = new Tweet
{
Id = Guid.NewGuid().ToString("N"),
Content = input.Content,
CreateTime = DateTime.Now
};
var o = TweetRepository.Insert(tweet);
return o;
}
}
大功告成!測試一下。用Postman調用CreateTweet
介面插入一條tweet:
然後調用GetTweets
查詢:
ABP的依賴註入
可能有同學會疑惑,在MyTweetAppService
中只聲明瞭ITweetRepository
類型的屬性TweetRepository
,但是並沒有進行賦值,那麼這個屬性的對象實例是哪裡來的呢?這就涉及到ABP框架的依賴註入策略了。
ABP基於Castle Windsor框架實現自己的依賴註入功能。依賴註入最基本的功能無非是註冊(Register
)和解析(Resolve
)兩個,註冊功能將對象註冊到IoC容器,解析功能根據類名或介面名獲從IoC容器獲取已註冊的對象。我們可以直接通過IocManager
獲得Castle Windsor的IoC容器,直接進行註冊和解析操作。
// 以單例模式註冊類型T
IocManager.Register<T>(Abp.Dependency.DependencyLifeStyle.Singleton);
// 以臨時對象模式註冊類型T,解析的時候會生成T的一個新對象
IocManager.Register<T>(Abp.Dependency.DependencyLifeStyle.Transient);
// 從IoC容器解析已註冊的類型T的對象
var obj = IocManager.Resolve<T>();
還有一些其他方法可以做註冊和解析,具體可以參照ABP的文檔。不過一般都不需要使用這些方法。ABP框架有一套依賴註入的規則,通過編寫應用程式時遵循最佳實踐和一些約定,使得依賴註入對於開發者幾乎是透明的。
ABP的註冊
基本上每個模塊的初始化方法都會有這麼一行代碼:
IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
模塊初始化時,ABP會搜索這個模塊所在的程式集,自動註冊滿足常規註冊條件與實現幫助介面的類。
常規註冊
ABP自動註冊所有Repositories, Domain Services, Application Services, MVC 控制器和Web API控制器。ABP通過判斷是否實現了相應介面來判斷是不是上述幾類。例如下麵的MyAppService
:
public interface IMyAppService : IApplicationService { }
public class MyAppService : IMyAppService { }
由於它實現了介面IApplicationService
,ABP會自動註冊,我們就可以通過IMyAppService
解析出一個MyAppService
對象。
通過常規註冊的類的生命期都是transient(臨時的),每次解析時都會生成一個新的臨時對象。
幫助介面
ABP另外提供了ITransientDependency
和ISingletonDependency
兩個介面。這兩個介面前面也有用到過了。實現了ITransientDependency
的類會被註冊為transient。而實現了ISingletonDependency
的類則被註冊為單例。
ABP的解析
除了手工解析外,還可以通過構造函數和公共屬性註入來獲取類的依賴。這也是最常用的方法。例如:
public class MyAppService : IMyAppService
{
public ILogger Logger { get; set; }
private IMyRepository _repo;
public MyAppService(IMyRepository repo)
{
_repo = repo;
}
}
ILogger
從公共屬性註入,IMyRepository
從構造函數註入。註入過程對開發者是透明的,開發者不需要去寫註入的代碼。
QueryService - 使用SQL語句查詢數據
實際開發中,經常需要直接使用SQL進行數據訪問。查詢邏輯比較複雜時直接使用SQL可以避免複雜的Mapper。通常複雜的Mapper會導致低效率的查詢甚至會觸發NHibernate一些奇怪的bug。實際上,在開發中,對於單純的讀取數據的功能(即使查詢邏輯不複雜),我們建議直接使用SQL查詢實現。直接使用SQL查詢在調試時更為方便——直接拷貝SQL語句到SQL客戶端執行即可檢驗該語句是否正確。
下麵簡要介紹一下使用Dapper來實現資料庫查詢功能。封裝了sql查詢操作的類我們稱為QueryService。
首先,安裝dapper
包到MyTweet.Infrastructure
。在MyTweet.Infrastructure
實現QueryService的基類BaseQueryService
:
public class BaseQueryService : ITransientDependency
{
private ISessionProvider _sessionProvider;
protected BaseQueryService(ISessionProvider sessionProvider)
{
_sessionProvider = sessionProvider;
}
public IEnumerable<T> Query<T>(string sql, object param = null)
{
var conn = _sessionProvider.Session.Connection;
return conn.Query<T>(sql, param);
}
}
Dapper給System.Data.IDbConnection
介面擴展了Query<T>
方法,該方法執行SQL查詢並將查詢結構映射為IEnumerable<T>
類型的對象。為了使用這個擴展方法,還需在文件開頭加個using
語句。
using Dapper;
QueryService並不在ABP依賴註入的常規註冊規則里,所以讓BaseQueryService
實現了ITransientDependency
,這樣它的子類都會自動被註冊到IoC容器。
接下來在MyTweet.Domain
新建類TweetQueryService
,它負責實現具體的SQL查詢。方法SearchTweets
實現了查詢包含關鍵詞keyword
的所有tweet。
public interface ITweetQueryService
{
IList<Tweet> SearchTweets(string keyword);
}
public class TweetQueryService : BaseQueryService, ITweetQueryService
{
public TweetQueryService() : base(IocManager.Instance.Resolve<LocalDbSessionProvider>())
{ }
public IList<Tweet> SearchTweets(string keyword)
{
var sql = @"select
pk_id Id,
content Content,
create_time CreateTime
from tweet
where content like '%' || @Keyword || '%'";
return Query<Tweet>(sql, new { Keyword = keyword ?? "" }).ToList();
}
}
最後在MyTweetAppService
實現查詢tweet數據的介面GetTweetsFromQS
:
public ITweetQueryService TweetQueryService { get; set; }
public object GetTweetsFromQS(string keyword)
{
return TweetQueryService.SearchTweets(keyword);
}
測試一下:
結束
本文介紹了通過NHibernate以及Dapper進行資料庫訪問的方法,簡單說明瞭ABP依賴註入策略。現在資料庫連接部分的代碼只是單純為了演示的簡單實現,沒有做合理的資料庫Session管理,會有效率和多線程衝突的問題。後面會加上工作單元(Unit of Work)來解決這些問題。
最後,放上代碼鏈接:https://github.com/sKabYY/MyTweet-AbpDemo