使用NHibernate 3.2实现Repository(ORuM)(三)NHibernate、Mapping、Mapping-By-Code

NHibernate使用时通常是先定义实体类,再为实体类映射数据库关系,NHibernate映射是使用XML文件形式的。

当我们Confrontation NHibernate时Mapping的过程:
  解析读取Mapping文件
  将Mapping文件反序列化(Deserialize)为XmlDeserialized(就是NHibernate.Cfg.MappingSchema.HbmMapping类)
  将HbmMapping类Bind为元数据映射(Mapping-MetaDate),(就是NHibernate.Cfg.Mappings类)
  Build SessionFactory

NHibernate映射文件生成方式:
  手动编写XML方式
  工具软件生成
  NHibernate.Mapping.Attributes

  
NHibernate3.2版本集成Mapping-By-Code(代码映射),其设计思想来源于ConfORM,代码整体构思基于"Loquacious"模式,而不是"fluent-interface"。在NHibernate.Mapping.ByCode命名空间下。


Mapping-By-Code(代码映射)与其他映射方式不同,Mapping-By-Code是手动配置实体映射,编译映射并转换为HbmMapping对象,调用Configuration类的void AddDeserializedMapping(HbmMapping mappingDocument, string documentFileName)方法配置Configuration,其他映射(例如Fluent NHibernate、MyGeneration、Visual NHibernate等)实际上是调用AddXmlReader()方法后,反序列化为HbmMapping对象,然后再调用AddDeserializedMapping方法配置Configuration。最后BuildSessionFactory。

ModelMapper核心是配置领域模型映射,编译并转换为HbmMapping对象。按照其功能ModelMapper类有四个部分,分别是:
  特定映射(Specific Mapper),对特定的类或者特定的某个组件设置定制化映射。由Class、Subclass、JoinedSubclass、UnionSubclass、Component方法提供。
  Conformist映射(Conformist Mapping),是以Class-By-Class方式映射,然后加入ModelMapper类。
事件拦截器(Events Interceptor),在每种映射行为前后,都定义了事件拦截器,可以通过事件拦截器定义约束。从设计角度上看是对ModelMapper类提供了扩展性。
  编译映射(CompileMapping),把程序中的领域实体的映射编译并转换为NHibernate使用的HbmMapping对象。CompileMappingFor、      CompileMappingForEach方法指定特定领域实体,CompileMappingForAllExplicitAddedEntities和CompileMappingForEachExplicitAddedEntity方法已经显式指定提供映射的实体。

Mapping-By-Code使用方法:
1、基本映射
ModelMapper提供一种基本映射方式:使用Class方法对实体类特定映射。

var mapper = new ModelMapper();
mapper.Class<Genre>(cm =>
{
cm.Id(x => x.Id, map =>
{
map.Column("GenreId");
map.Generator(Generators.Identity);
});
cm.Property(x => x.Name);
});

2、Conformist映射
ModelMapper提供另外一种Conformist映射方式:class-by-class方式,即每个类定义一个类去映射,然后调用AddMapping方法把映射加入ModelMapper对象。

public class GenreMapping : ClassMapping<Genre>
{
public GenreMapping()
{
Id(x => x.Id, map =>
{
map.Column("GenreId");
map.Generator(Generators.Identity);
});
Property(x => x.Name);
}
}

3.约定
ModelMapper提供了很多事件监听器,可以通过它扩展ModelMapper。其中就是自定义约定。其实上面定义的映射从设计思想上面说也是一种约定,暂时可以称作特定约定(Specific-Convetions)。以Before开头的事件监听称作前置约定(Pre-Conventions)。从人性化角度看前置约定(Pre-Conventions)比较民主(democratic),我们映射时可以使用特定约定(Specific-Convetions)。以After开头的事件监听称作后置约定(Post-Conventions)或者称作Hard-Conventions。从人性化角度看后置约定(Post-Conventions)就比较共和(republican),不管前面怎么特定,到最后一律使用后置约定(Post-Conventions)所规定的"条约"。

var mapper = new ModelMapper();
mapper.BeforeMapClass += (mi, t, cam) =>
{
cam.Id(x =>
{
x.Column(t.Name + "ID");
x.Generator(Generators.Identity);
});
cam.Table(prefix+t.Name + "s");
};


常用方法:

//ID映射
Id(x => x.Id, map =>
{
map.Column("GenreId");
map.Generator(Generators.Identity);
});

//Version映射
Version(x => x.Version, map => { });

//Property映射:
Property(x => x.Name);

//ManyToOne映射
ManyToOne(x => x.Genre);

//OneToMany映射
Bag(x => x.Albums, map => { });

//Component映射
Component(x => x.Address, cm => {
cm.Property(p => p.City);
cm.Property(p => p.Country);
cm.Property(p => p.State);
cm.Property(p => p.Street);
cm.Property(p => p.Zip);
});

编译生成HbmMapping对象:

var mapper = new ModelMapper();
mapper.AddMappings(Assembly.GetExecutingAssembly().GetExportedTypes());
HbmMapping domainMapping = mapper.CompileMappingForAllExplicitlyAddedEntities();


使用Conformist映射结合约定的方式实现实体类与数据库的映射

完整代码

ClassMappings.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using NHibernate.Mapping.ByCode.Conformist;
using MVCQuick.Models;
using NHibernate.Mapping.ByCode;

namespace MVCQuick.Framework.Repository.NHibernate
{
public class EntityBaseMapping<TEntity> : ClassMapping<TEntity> where TEntity : EntityBase
{
public EntityBaseMapping()
{
Id(x => x.Id);

Version(x => x.Version, map => { });
}

}

public class GenreMapping : EntityBaseMapping<Genre>
{
public GenreMapping()
{
Property(x => x.Name);
Property(x => x.Description);
Bag(x => x.Albums, map => {
map.Cascade(Cascade.All);
map.Inverse(true);
});
}
}

public class ArtistMapping : EntityBaseMapping<Artist>
{
public ArtistMapping()
{
Property(x => x.Name);
Component(x => x.Address, cm => {
cm.Property(p => p.City);
cm.Property(p => p.Country);
cm.Property(p => p.State);
cm.Property(p => p.Street);
cm.Property(p => p.Zip);
});
Bag(x => x.Albums, map => { });
}
}

public class AlbumMapping : EntityBaseMapping<Album>
{
public AlbumMapping()
{
Property(x => x.Title);
Property(x => x.Price);
Property(x => x.AlbumArtUrl);
ManyToOne(x => x.Genre);
ManyToOne(x =>x.Artist);
}
}
}

在Bag映射是,直接使用ModelMapper会报错

var mapper = new ModelMapper();
mapper.AddMappings(Assembly.GetExecutingAssembly().GetExportedTypes());
HbmMapping domainMapping = mapper.CompileMappingForAllExplicitlyAddedEntities();

报错原因及解决方法如下(http://fabiomaulo.blogspot.com/2011/07/nhibernate-playing-with-mapping-by-code.html
We can’t define the one-to-many relation using the ModelMapper because the responsibility to define/discover all relation is delegated to the IModelInspector injected to the ModelMapper instance (note that I wrote instance). Out-of-the-box you can find some implementations of IModelInspector but if you want continue maintaining most of things under your control and you want learn the new mapping-by-code step-by-step you can simply use a class inherited from the “”default”” ExplicitlyDeclaredModel. In this case the implementation

需要提供一个IModelInspector

EntityModelInspector.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using NHibernate.Mapping.ByCode;
using System.Reflection;

namespace MVCQuick.Framework.Repository.NHibernate
{
public class EntityModelInspector : ExplicitlyDeclaredModel
{
public override bool IsOneToMany(MemberInfo member)
{
if (IsBag(member))
{
return true;
}
return base.IsOneToMany(member);
}
}
}

使用方法

ModelMapper mapper = new ModelMapper(new EntityModelInspector());

 

单元测试代码

RepositoryTests.cs
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.BeforeMapClass += (mi, t, cam) =>
{
cam.Id(x =>
{
x.Column(t.Name + "ID");
x.Generator(Generators.Identity);
});
cam.Table(t.Name + "s");
};

mapper.AddMappings(typeof(EntityBase).Assembly.GetExportedTypes());


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);


artistList = artistRepository.FindAll();
Assert.IsNull(artistList[0].Address);
artist.Address = new Address();
artist.Address.City = "Beijing";
artist.Address.Country = "China";
artistRepository.Update(artist);
artistList = artistRepository.FindAll();
Assert.AreEqual(artistList[0].Address.City, "Beijing");
Assert.AreEqual(artistList[0].Address.Country, "China");


}
}
}
}

测试结果

 


测试输出:

***** 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="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>
  <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">
      <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>
</hibernate-mapping>

    PRAGMA foreign_keys = OFF

    drop table if exists Genres

    drop table if exists Artists

    drop table if exists Albums

    PRAGMA foreign_keys = ON

    create table Genres (
        GenreID  integer primary key autoincrement,
       Version INT not null,
       Name TEXT,
       Description TEXT
    )

    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
    )
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 GenreID0_0_, this_.Version as Version0_0_, this_.Name as Name0_0_, this_.Description as Descript4_0_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 ArtistID1_0_, this_.Version as Version1_0_, this_.Name as Name1_0_, this_.City as City1_0_, this_.Country as Country1_0_, this_.State as State1_0_, this_.Street as Street1_0_, this_.Zip as Zip1_0_ FROM Artists this_
genre Id:1
genre HashCode:430785402
artist Id:1
artist HashCode:1282642567
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 AlbumID2_0_, this_.Version as Version2_0_, this_.Title as Title2_0_, this_.Price as Price2_0_, this_.AlbumArtUrl as AlbumArt5_2_0_, this_.Genre as Genre2_0_, this_.Artist as Artist2_0_ FROM Albums this_
NHibernate: SELECT this_.AlbumID as AlbumID2_0_, this_.Version as Version2_0_, this_.Title as Title2_0_, this_.Price as Price2_0_, this_.AlbumArtUrl as AlbumArt5_2_0_, this_.Genre as Genre2_0_, this_.Artist as Artist2_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 GenreID0_0_, this_.Version as Version0_0_, this_.Name as Name0_0_, this_.Description as Descript4_0_0_ FROM Genres this_
NHibernate: SELECT this_.GenreID as GenreID0_0_, this_.Version as Version0_0_, this_.Name as Name0_0_, this_.Description as Descript4_0_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 GenreID0_0_, this_.Version as Version0_0_, this_.Name as Name0_0_, this_.Description as Descript4_0_0_ FROM Genres this_
NHibernate: SELECT this_.AlbumID as AlbumID2_0_, this_.Version as Version2_0_, this_.Title as Title2_0_, this_.Price as Price2_0_, this_.AlbumArtUrl as AlbumArt5_2_0_, this_.Genre as Genre2_0_, this_.Artist as Artist2_0_ FROM Albums this_
NHibernate: SELECT this_.ArtistID as ArtistID1_0_, this_.Version as Version1_0_, this_.Name as Name1_0_, this_.City as City1_0_, this_.Country as Country1_0_, this_.State as State1_0_, this_.Street as Street1_0_, this_.Zip as Zip1_0_ FROM Artists this_
NHibernate: UPDATE Artists SET Version = @p0, Name = @p1, City = @p2, Country = @p3, State = @p4, Street = @p5, Zip = @p6 WHERE ArtistID = @p7 AND Version = @p8;@p0 = 2 [Type: Int32 (0)], @p1 = 'Artist-bb' [Type: String (0)], @p2 = 'Beijing' [Type: String (0)], @p3 = 'China' [Type: String (0)], @p4 = NULL [Type: String (0)], @p5 = NULL [Type: String (0)], @p6 = NULL [Type: String (0)], @p7 = 1 [Type: Int32 (0)], @p8 = 1 [Type: Int32 (0)]
NHibernate: SELECT this_.ArtistID as ArtistID1_0_, this_.Version as Version1_0_, this_.Name as Name1_0_, this_.City as City1_0_, this_.Country as Country1_0_, this_.State as State1_0_, this_.Street as Street1_0_, this_.Zip as Zip1_0_ FROM Artists this_


文中部分内容转载自YJingLee's Blog(http://www.cnblogs.com/lyj/

posted @ 2011-10-06 16:42  GuYoung  阅读(2390)  评论(1编辑  收藏  举报