为了防止不提供原网址的转载,特在这里加上原文连接:
http://www.cnblogs.com/skabyy/p/7517397.htmlhtml
本篇咱们实现数据库的访问。咱们将实现两种数据库访问方法来访问一个SQLite数据库——使用NHibernate实现的ORM映射访问和使用Dapper实现的SQL语句访问。而后完成前一篇未完成的CreateTweet
和GetTweets
接口。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.NHibernate
到MyTweet.Domain
和MyTweet.Infrastructure
。数据库
下面咱们将完成这些步骤来实现数据库的访问:session
tweet
表以及相应的Model类型咱们这里使用的数据库是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.Core
到MyTweet.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
以及映射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便是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
查询:
可能有同窗会疑惑,在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框架有一套依赖注入的规则,经过编写应用程序时遵循最佳实践和一些约定,使得依赖注入对于开发者几乎是透明的。
基本上每一个模块的初始化方法都会有这么一行代码:
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
的类则被注册为单例。
除了手工解析外,还能够经过构造函数和公共属性注入来获取类的依赖。这也是最经常使用的方法。例如:
public class MyAppService : IMyAppService { public ILogger Logger { get; set; } private IMyRepository _repo; public MyAppService(IMyRepository repo) { _repo = repo; } }
ILogger
从公共属性注入,IMyRepository
从构造函数注入。注入过程对开发者是透明的,开发者不须要去写注入的代码。
实际开发中,常常须要直接使用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)来解决这些问题。