手工搭建基於ABP的框架(2) - 訪問數據庫

爲了防止不提供原網址的轉載,特在這裏加上原文連接:
http://www.cnblogs.com/skabyy/p/7517397.htmlhtml

本篇咱們實現數據庫的訪問。咱們將實現兩種數據庫訪問方法來訪問一個SQLite數據庫——使用NHibernate實現的ORM映射訪問和使用Dapper實現的SQL語句訪問。而後完成前一篇未完成的CreateTweetGetTweets接口。git

在開始以前,先作一些準備工做,新建Domain層的Module:github

public class MyTweetDomainModule : AbpModule
{
    public override void Initialize()
    {
        IocManager.RegisterAssemblyByConvention(Assembly.GetExecutingAssembly());
    }
}

同時MyTweetApplicationModule添加對MyTweetDomainModule的依賴:sql

[DependsOn(typeof(MyTweetDomainModule))]
public class MyTweetApplicationModule : AbpModule

安裝NuGet包Abp.NHibernateMyTweet.DomainMyTweet.Infrastructure數據庫

下面咱們將完成這些步驟來實現數據庫的訪問:session

  1. 配置數據庫鏈接
  2. 新建tweet表以及相應的Model類型
  3. 實現訪問數據的Repository
  4. 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)時會解決這個問題。app

爲了NHibernate能建立SQLite的鏈接,還須要安裝System.Data.SQLite.CoreMyTweet.Web(其餘數據庫的話要安裝其餘相應的包)。框架

新建tweet表以及相應的Model類型

咱們用tweet表保存tweet數據。tweet數據表接口以及對應Model屬性以下:ide

數據庫字段 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以及映射TweetMapperTweet繼承Entity<string>,其中的string表示Tweet的主鍵Idstring類型的。TweetMapper繼承ClassMap<Tweet>,上面LocalDbSessionProvider構造函數執行到.Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly()))這個方法時,會用反射的方式搜索程序集中ClassMap<T>的子類,創建Model和數據庫表的映射(Tweettweet表的映射)。

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另外提供了ITransientDependencyISingletonDependency兩個接口。這兩個接口前面也有用到過了。實現了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

相關文章
相關標籤/搜索