Repository
Repository(仓储、资源库)模式定义:
一种用来封装存储,读取和查找行为的机制,它模拟了一个对象集合(《领域驱动设计:软件核心复杂性应对之道》);
通过用来访问领域对象的一个类似集合的接口,在领域与数据映射层之间进行协调(《企业应用架构模式》)。
使用Repository模式的最大好处就是将领域模型从客户代码和数据映射层之间解耦出来。
NHibernate 3.2
NHibernate 3.2 GA 正式版于 2011-07-30 发布,这一版本对 CodeFirst 的支持性更好了,因为它提供了 Conformist 取代之前第三方的 ConfOrm。
NHibernate 3.2的mapping by code有三种方式:
直接为每个entity类调用ModelMapper.Class方法;
Conformist映射,也就是class by class ;
Convention,当然约定是可以被explicit mapping所覆盖的;
本方法实现Repository特点:
1、实体类与仓储类真正实现分离;
2、可以支持多种数据库;
3、通过.Net反射技术,每个实体对象自动产生映射文件;
4、单元测试实现简单。
先看一下使用NUnit进行单元测试的代码,使用SQLite数据库作为测试数据库
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using NHibernate.Mapping.ByCode;
using MVCQuick.Framework.Repository.NHibernate;
using MVCQuick.Models;
using MVCQuick.Framework.Repository;
using NHibernate;
using System.Diagnostics;
using System.Reflection;
using MVCQuick.Framework;
using NHibernate.Cfg.MappingSchema;
namespace MVCQuick.Tests
{
[TestFixture]
public class RepositoryTests
{
[Test]
public void SaveEntity()
{
ModelMapper mapper = new ModelMapper(new EntityModelInspector());
mapper.AddEntityMappings(null, typeof(EntityBase).Assembly);
var hbmMappings = mapper.CompileMappingForAllExplicitlyAddedEntities();
Debug.WriteLine(hbmMappings.AsString());
using (SQLiteDatabaseProvider provider = new SQLiteDatabaseProvider())
{
provider.AddMappings(hbmMappings, "Repository.Tests");
provider.BuildSchema();
ISession session = provider.OpenSession();
IRepository<int, Genre> genreRepository =
new NHibernateRepository<int, Genre>(session);
Genre genre = new Genre{ Name = "Genre-aa", Description="aaaa" };
genreRepository.Save(genre);
IList<Genre> genreList = genreRepository.FindAll();
Assert.AreEqual(genreList.Count, 1);
Assert.AreEqual(genreList[0].Name, "Genre-aa");
Assert.AreEqual(genreList[0].Description, "aaaa");
IRepository<int, Artist> artistRepository =
new NHibernateRepository<int, Artist>(session);
Artist artist = new Artist{ Name = "Artist-bb"} ;
artistRepository.Save(artist);
IList<Artist> artistList = artistRepository.FindAll();
Assert.AreEqual(artistList.Count, 1);
Assert.AreEqual(artistList[0].Name, "Artist-bb");
Debug.WriteLine("genre Id:" + genre.Id);
Debug.WriteLine("genre HashCode:" + genre.GetHashCode());
Debug.WriteLine("artist Id:" + artist.Id);
Debug.WriteLine("artist HashCode:" + artist.GetHashCode());
Assert.AreNotEqual(genre, artist);
IRepository<int, Album> albumRepository =
new NHibernateRepository<int, Album>(session);
Album album = new Album { Title = "Album-CC", Genre = genre, Artist = artist };
albumRepository.Save(album);
album = new Album { Title = "Album-DD", Genre = genre, Artist = artist };
albumRepository.Save(album);
IList<Album> albumtList = albumRepository.FindAll();
Assert.AreEqual(albumtList.Count, 2);
Assert.AreEqual(albumtList[0].Title, "Album-CC");
Assert.AreEqual(albumtList[1].Title, "Album-DD");
Assert.AreEqual(albumtList[0].Genre.Name, "Genre-aa");
Assert.AreEqual(albumtList[1].Genre.Name, "Genre-aa");
IList<Album> albumtList2 = albumRepository.Find("Title", "Album-DD");
Assert.AreEqual(albumtList2.Count, 1);
Debug.WriteLine("genre Version:" + genre.Version);
Assert.AreEqual(genre.Albums, null);
genre.Albums = new List<Album>();
((List<Album>)genre.Albums).Add(albumtList[0]);
((List<Album>)genre.Albums).Add(albumtList[1]);
genreRepository.Save(genre);
Debug.WriteLine("genre Version:" + genre.Version);
genreList = genreRepository.FindAll();
Assert.AreEqual(genreList[0].Albums.Count<Album>(), 2);
genreList = genreRepository.FindAll();
Assert.AreEqual(genreList.Count, 1);
genreRepository.Delete(genre);
genreList = genreRepository.FindAll();
Assert.AreEqual(genreList.Count, 0);
albumtList = albumRepository.FindAll();
Assert.AreEqual(albumtList.Count, 0);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SQLite;
using NHibernate.Cfg;
using NHibernate;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Tool.hbm2ddl;
using System.Data;
namespace MVCQuick.Tests
{
public class SQLiteDatabaseProvider : IDisposable
{
private const string CONNECTION_STRING = "Data Source=:memory:;Version=3;New=True;";
//private const string CONNECTION_STRING = "Data Source=c:\\test.db;Version=3;New=True;";
private Configuration configuration;
private ISessionFactory sessionFactory;
private IDbConnection dbConnection;
public ISessionFactory SessionFactory
{
get
{
if (this.sessionFactory == null)
{
this.sessionFactory = this.configuration.BuildSessionFactory();
}
return this.sessionFactory;
}
}
public IDbConnection DbConnection
{
get
{
if (this.dbConnection == null)
{
this.dbConnection = new SQLiteConnection(CONNECTION_STRING);
this.dbConnection.Open();
}
return this.dbConnection;
}
}
public SQLiteDatabaseProvider()
{
BuildConfiguration();
}
private void BuildConfiguration()
{
configuration = new Configuration();
configuration.SetProperty("connection.provider", "NHibernate.Connection.DriverConnectionProvider");
configuration.SetProperty("connection.driver_class", "NHibernate.Driver.SQLite20Driver");
configuration.SetProperty("dialect", "NHibernate.Dialect.SQLiteDialect");
configuration.SetProperty("connection.connection_string", CONNECTION_STRING);
configuration.SetProperty("connection.release_mode", "on_close");
configuration.SetProperty("show_sql", "true");
}
public void AddMappings(HbmMapping mappingDocument, string documentFileName)
{
configuration.AddDeserializedMapping(mappingDocument, documentFileName);
}
public void BuildSchema()
{
SchemaExport se = new SchemaExport(configuration);
se.Execute(false, true, false, DbConnection, Console.Out);
}
public ISession OpenSession()
{
return SessionFactory.OpenSession(DbConnection);
}
private bool disposedValue = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposedValue)
{
if (disposing)
{
if (this.dbConnection != null)
{
this.dbConnection.Close();
}
this.dbConnection = null;
}
}
this.disposedValue = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
}
测试结果
测试输出:
***** MVCQuick.Tests.RepositoryTests.SaveEntity
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" namespace="MVCQuick.Models" assembly="MVCQuick" xmlns="urn:nhibernate-mapping-2.2">
<class name="Artist" table="Artists">
<id name="Id" column="ArtistID" type="Int32">
<generator class="identity" />
</id>
<version name="Version" />
<property name="Name" />
<component class="Address" name="Address">
<property name="City" />
<property name="Country" />
<property name="State" />
<property name="Street" />
<property name="Zip" />
</component>
<bag name="Albums" inverse="true" cascade="all">
<key column="Artist" />
<one-to-many class="Album" />
</bag>
</class>
<class name="Album" table="Albums">
<id name="Id" column="AlbumID" type="Int32">
<generator class="identity" />
</id>
<version name="Version" />
<property name="Title" />
<property name="Price" />
<property name="AlbumArtUrl" />
<many-to-one name="Genre" />
<many-to-one name="Artist" />
</class>
<class name="Genre" table="Genres">
<id name="Id" column="GenreID" type="Int32">
<generator class="identity" />
</id>
<version name="Version" />
<property name="Name" />
<property name="Description" />
<bag name="Albums" inverse="true" cascade="all">
<key column="Genre" />
<one-to-many class="Album" />
</bag>
</class>
</hibernate-mapping>
PRAGMA foreign_keys = OFF
drop table if exists Artists
drop table if exists Albums
drop table if exists Genres
PRAGMA foreign_keys = ON
create table Artists (
ArtistID integer primary key autoincrement,
Version INT not null,
Name TEXT,
City TEXT,
Country TEXT,
State TEXT,
Street TEXT,
Zip TEXT
)
create table Albums (
AlbumID integer primary key autoincrement,
Version INT not null,
Title TEXT,
Price NUMERIC,
AlbumArtUrl TEXT,
Genre INT,
Artist INT,
constraint FKA0BD20AAE3A37A45 foreign key (Genre) references Genres,
constraint FKA0BD20AAA67656D9 foreign key (Artist) references Artists
)
create table Genres (
GenreID integer primary key autoincrement,
Version INT not null,
Name TEXT,
Description TEXT
)
NHibernate: INSERT INTO Genres (Version, Name, Description) VALUES (@p0, @p1, @p2); select last_insert_rowid();@p0 = 1 [Type: Int32 (0)], @p1 = 'Genre-aa' [Type: String (0)], @p2 = 'aaaa' [Type: String (0)]
NHibernate: SELECT this_.GenreID as GenreID2_0_, this_.Version as Version2_0_, this_.Name as Name2_0_, this_.Description as Descript4_2_0_ FROM Genres this_
NHibernate: INSERT INTO Artists (Version, Name, City, Country, State, Street, Zip) VALUES (@p0, @p1, @p2, @p3, @p4, @p5, @p6); select last_insert_rowid();@p0 = 1 [Type: Int32 (0)], @p1 = 'Artist-bb' [Type: String (0)], @p2 = NULL [Type: String (0)], @p3 = NULL [Type: String (0)], @p4 = NULL [Type: String (0)], @p5 = NULL [Type: String (0)], @p6 = NULL [Type: String (0)]
NHibernate: SELECT this_.ArtistID as ArtistID0_0_, this_.Version as Version0_0_, this_.Name as Name0_0_, this_.City as City0_0_, this_.Country as Country0_0_, this_.State as State0_0_, this_.Street as Street0_0_, this_.Zip as Zip0_0_ FROM Artists this_
genre Id:1
genre HashCode:949684925
artist Id:1
artist HashCode:349072624
NHibernate: INSERT INTO Albums (Version, Title, Price, AlbumArtUrl, Genre, Artist) VALUES (@p0, @p1, @p2, @p3, @p4, @p5); select last_insert_rowid();@p0 = 1 [Type: Int32 (0)], @p1 = 'Album-CC' [Type: String (0)], @p2 = 0 [Type: Decimal (0)], @p3 = NULL [Type: String (0)], @p4 = 1 [Type: Int32 (0)], @p5 = 1 [Type: Int32 (0)]
NHibernate: INSERT INTO Albums (Version, Title, Price, AlbumArtUrl, Genre, Artist) VALUES (@p0, @p1, @p2, @p3, @p4, @p5); select last_insert_rowid();@p0 = 1 [Type: Int32 (0)], @p1 = 'Album-DD' [Type: String (0)], @p2 = 0 [Type: Decimal (0)], @p3 = NULL [Type: String (0)], @p4 = 1 [Type: Int32 (0)], @p5 = 1 [Type: Int32 (0)]
NHibernate: SELECT this_.AlbumID as AlbumID1_0_, this_.Version as Version1_0_, this_.Title as Title1_0_, this_.Price as Price1_0_, this_.AlbumArtUrl as AlbumArt5_1_0_, this_.Genre as Genre1_0_, this_.Artist as Artist1_0_ FROM Albums this_
NHibernate: SELECT this_.AlbumID as AlbumID1_0_, this_.Version as Version1_0_, this_.Title as Title1_0_, this_.Price as Price1_0_, this_.AlbumArtUrl as AlbumArt5_1_0_, this_.Genre as Genre1_0_, this_.Artist as Artist1_0_ FROM Albums this_ WHERE this_.Title like @p0;@p0 = 'Album-DD' [Type: String (0)]
genre Version:1
NHibernate: UPDATE Genres SET Version = @p0, Name = @p1, Description = @p2 WHERE GenreID = @p3 AND Version = @p4;@p0 = 2 [Type: Int32 (0)], @p1 = 'Genre-aa' [Type: String (0)], @p2 = 'aaaa' [Type: String (0)], @p3 = 1 [Type: Int32 (0)], @p4 = 1 [Type: Int32 (0)]
genre Version:2
NHibernate: SELECT this_.GenreID as GenreID2_0_, this_.Version as Version2_0_, this_.Name as Name2_0_, this_.Description as Descript4_2_0_ FROM Genres this_
NHibernate: SELECT this_.GenreID as GenreID2_0_, this_.Version as Version2_0_, this_.Name as Name2_0_, this_.Description as Descript4_2_0_ FROM Genres this_
NHibernate: DELETE FROM Albums WHERE AlbumID = @p0 AND Version = @p1;@p0 = 1 [Type: Int32 (0)], @p1 = 1 [Type: Int32 (0)]
NHibernate: DELETE FROM Albums WHERE AlbumID = @p0 AND Version = @p1;@p0 = 2 [Type: Int32 (0)], @p1 = 1 [Type: Int32 (0)]
NHibernate: DELETE FROM Genres WHERE GenreID = @p0 AND Version = @p1;@p0 = 1 [Type: Int32 (0)], @p1 = 2 [Type: Int32 (0)]
NHibernate: SELECT this_.GenreID as GenreID2_0_, this_.Version as Version2_0_, this_.Name as Name2_0_, this_.Description as Descript4_2_0_ FROM Genres this_
NHibernate: SELECT this_.AlbumID as AlbumID1_0_, this_.Version as Version1_0_, this_.Title as Title1_0_, this_.Price as Price1_0_, this_.AlbumArtUrl as AlbumArt5_1_0_, this_.Genre as Genre1_0_, this_.Artist as Artist1_0_ FROM Albums this_