使用NHibernate 3.2实现Repository(ORuM)(四)NHibernate、Mapping、Mapping-By-Code、AutoMapping
使用NHibernate 3.2实现Repository(ORuM)(二)、使用NHibernate 3.2实现Repository(ORuM)(三)介绍了使用NHibernate 3.2 Mapping-By-Code实现Repository的方法,Mapping-By-Code相比手工编写xml映射文件HBM的方法更为简便、流畅。
但本实现方法的核心是“ORuM(Object Relational un-Mapping))”,即利用实体、值对象定义实现自动生成HbmMapping,使仓储对象更像个真正意义上的实体对象仓库。只要把实体对象放进仓库,就能拿得出来,无须关注更多细节,只需定义一个仓储对象而已。
IRepository<int, Genre> genreRepository = new NHibernateRepository<int, Genre>(session);
genreRepository.Save(genre);
IList<Genre> genreList = genreRepository.FindAll();
NHibernate对Mapping的处理过程,就是将XML文件反序列化为HbmMapping类,再将HbmMapping类Bind为Mappings,最后Bind SessionFactory。本实现方法就是利用实体、值对象定义实现自动生成HbmMapping类,完全避免了编写XML文件和反序列化。
本方法的核心是定义一个EntityMapping泛型类,做为NHibernate.Mapping.ByCode.Conformist.ClassMapping子类
public class EntityMapping<TEntity> : ClassMapping<TEntity> where TEntity : EntityBase
{
public EntityMapping()
{
}
}
EntityMapping类继承了ClassMapping所有的属性和方法。
ClassMapping类的部分方法
public void Property<TProperty>(Expression<Func<TEntity, TProperty>> property, Action<IPropertyMapper> mapping);
public void Id<TProperty>(Expression<Func<TEntity, TProperty>> idProperty, Action<IIdMapper> idMapper);
public void Version<TProperty>(Expression<Func<TEntity, TProperty>> versionProperty, Action<IVersionMapper> versionMapping);
public void ManyToOne<TProperty>(Expression<Func<TEntity, TProperty>> property, Action<IManyToOneMapper> mapping) where TProperty : class;
public void Bag<TElement>(Expression<Func<TEntity, IEnumerable<TElement>>> property, Action<IBagPropertiesMapper<TEntity, TElement>> collectionMapping, Action<ICollectionElementRelation<TElement>> mapping);
public void Component<TComponent>(Expression<Func<TEntity, TComponent>> property, Action<IComponentMapper<TComponent>> mapping) where TComponent : class;
这些方法都是泛型方法,参数为Lambda表达式。
使用System.Type GetProperties方法获取实体类属性信息
Type type = typeof(TEntity);
PropertyInfo[] props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo prop in props)
{
}
动态生成Lambda表达式参数,动态调用泛型方法
var target = Expression.Parameter(type);
var getPropertyValue = Expression.Property(target, prop);
this.GetType().GetMethod("Property2")
.MakeGenericMethod(prop.PropertyType)
.Invoke(this, new object[] { Expression.Lambda(getPropertyValue, target), });
一般属性 public virtual string Name { get; set; }
//Property(x => x.Name);
var target = Expression.Parameter(type);
var getPropertyValue = Expression.Property(target, prop);
this.GetType().GetMethod("Property2")
.MakeGenericMethod(prop.PropertyType)
.Invoke(this, new object[] { Expression.Lambda(getPropertyValue, target), });
ID属性 public virtual int Id { get; set; }
if (propName == "Id")
{
//Id(x => x.Id)
var target = Expression.Parameter(type);
var getPropertyValue = Expression.Property(target, prop);
var o = this.GetType().GetMethod("Id2")
.MakeGenericMethod(prop.PropertyType)
.Invoke(this, new object[] { Expression.Lambda(getPropertyValue, target), });
continue;
}
Version属性 public virtual int Version{ get; set; }
if (propName == "Version")
{
//Version(x => x.Version)
var target = Expression.Parameter(type);
var getPropertyValue = Expression.Property(target, prop);
var o = this.GetType().GetMethod("Version2")
.MakeGenericMethod(prop.PropertyType)
.Invoke(this, new object[] { Expression.Lambda(getPropertyValue, target), });
continue;
}
ManyToOne属性 public virtual Genre Genre { get; set; }
if (prop.PropertyType.IsSubclassOf(typeof(EntityBase)))
{
//ManyToOne(x => x.Genre);
var target = Expression.Parameter(type);
var getPropertyValue = Expression.Property(target, prop);
this.GetType().GetMethod("ManyToOne2")
.MakeGenericMethod(prop.PropertyType)
.Invoke(this, new object[] { Expression.Lambda(getPropertyValue, target) });
continue;
}
OneToMany属性 public virtual IEnumerable<Album> Albums { get; set; }
f (prop.PropertyType.IsGenericCollection()
&& prop.PropertyType.DetermineCollectionElementType().IsSubclassOf(typeof(EntityBase)))
{
//Bag(x => x.Genre);
var target = Expression.Parameter(type);
var getPropertyValue = Expression.Property(target, prop);
this.GetType().GetMethod("Bag2")
.MakeGenericMethod(prop.PropertyType.DetermineCollectionElementType())
.Invoke(this, new object[] { Expression.Lambda(getPropertyValue, target) });
continue;
}
Component属性 public virtual Address Address { get; set; }
if (prop.PropertyType.GetInterfaces().Contains<Type>(typeof(IValueObject)))
{
//Component(x => x.Address);
var target = Expression.Parameter(type);
var getPropertyValue = Expression.Property(target, prop);
this.GetType().GetMethod("Component2")
.MakeGenericMethod(prop.PropertyType)
.Invoke(this, new object[] { Expression.Lambda(getPropertyValue, target) });
continue;
}
完整代码如下
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using NHibernate.Mapping.ByCode.Conformist;
using NHibernate.Mapping.ByCode;
using System.Reflection;
using System.Linq.Expressions;
using System.Diagnostics;
namespace MVCQuick.Framework.Repository.NHibernate
{
public class EntityMapping<TEntity> : ClassMapping<TEntity> where TEntity : EntityBase
{
public EntityMapping()
{
try
{
Type type = typeof(TEntity);
PropertyInfo[] props = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (PropertyInfo prop in props)
{
string propName = prop.Name;
if (propName == "Id")
{
//Id(x => x.Id)
var target = Expression.Parameter(type);
var getPropertyValue = Expression.Property(target, prop);
var o = this.GetType().GetMethod("Id2")
.MakeGenericMethod(prop.PropertyType)
.Invoke(this, new object[] { Expression.Lambda(getPropertyValue, target), });
continue;
}
if (propName == "Version")
{
//Version(x => x.Version)
var target = Expression.Parameter(type);
var getPropertyValue = Expression.Property(target, prop);
var o = this.GetType().GetMethod("Version2")
.MakeGenericMethod(prop.PropertyType)
.Invoke(this, new object[] { Expression.Lambda(getPropertyValue, target), });
continue;
}
if (prop.PropertyType.IsSubclassOf(typeof(EntityBase)))
{
//ManyToOne(x => x.Genre);
var target = Expression.Parameter(type);
var getPropertyValue = Expression.Property(target, prop);
this.GetType().GetMethod("ManyToOne2").MakeGenericMethod(prop.PropertyType)
.Invoke(this, new object[] { Expression.Lambda(getPropertyValue, target) });
continue;
}
if (prop.PropertyType.IsGenericCollection()
&& prop.PropertyType.DetermineCollectionElementType().IsSubclassOf(typeof(EntityBase)))
{
//Bag(x => x.Genre);
var target = Expression.Parameter(type);
var getPropertyValue = Expression.Property(target, prop);
this.GetType().GetMethod("Bag2")
.MakeGenericMethod(prop.PropertyType.DetermineCollectionElementType())
.Invoke(this, new object[] { Expression.Lambda(getPropertyValue, target) });
continue;
}
if (prop.PropertyType.GetInterfaces().Contains<Type>(typeof(IValueObject)))
{
//Component(x => x.Address);
var target = Expression.Parameter(type);
var getPropertyValue = Expression.Property(target, prop);
this.GetType().GetMethod("Component2").MakeGenericMethod(prop.PropertyType)
.Invoke(this, new object[] { Expression.Lambda(getPropertyValue, target) });
continue;
}
if (true)
{
//Property(x => x.Name);
var target = Expression.Parameter(type);
var getPropertyValue = Expression.Property(target, prop);
this.GetType().GetMethod("Property2")
.MakeGenericMethod(prop.PropertyType)
.Invoke(this, new object[] { Expression.Lambda(getPropertyValue, target), });
}
}
}
catch (Exception ex)
{
throw ex;
}
}
public void Id2<TProperty>(Expression property)
{
Id((Expression<Func<TEntity, TProperty>>)property,
x => { });
}
public void Version2<TProperty>(Expression property)
{
Version((Expression<Func<TEntity, TProperty>>)property,
x => { });
}
public void Property2<TProperty>(Expression property)
{
Property((Expression<Func<TEntity, TProperty>>)property,
x => { });
}
public void Component2<TComponent>(Expression property) where TComponent : class
{
Component((Expression<Func<TEntity, TComponent>>)property, ComponentMap<TComponent>.Mapping());
}
public void ManyToOne2<TProperty>(Expression property) where TProperty : class
{
ManyToOne((Expression<Func<TEntity, TProperty>>)property,
x => { });
}
public void Bag2<TElement>(Expression property)
{
Bag((Expression<Func<TEntity, IEnumerable<TElement>>>)property,
cm => { cm.Cascade(Cascade.All); cm.Inverse(true); },
x => { });
}
}
internal class ComponentMap<TComponent> where TComponent : class
{
public static void Property<TProperty>(IComponentMapper<TComponent> cm, string propName)
{
var target = Expression.Parameter(typeof(TComponent));
cm.Property<TProperty>((Expression<Func<TComponent, TProperty>>)Expression.Lambda(
Expression.Property(target, propName), target));
}
public static Action<IComponentMapper<TComponent>> Mapping()
{
return cm =>
{
PropertyInfo[] props = typeof(TComponent).GetProperties(BindingFlags.Public | BindingFlags.Instance);
foreach (var prop in props)
{
typeof(ComponentMap<TComponent>).GetMethod("Property")
.MakeGenericMethod(prop.PropertyType)
.Invoke(null, new object[] { cm, prop .Name});
}
};
}
}
}
扩展ModelMapper
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using NHibernate.Mapping.ByCode;
using NHibernate;
using System.Linq.Expressions;
using System.Reflection;
namespace MVCQuick.Framework.Repository.NHibernate
{
public static class ModelMapperExtensions
{
public static void AddEntityMappings(this ModelMapper mapper, string prefix = "", params Assembly[] assemblies)
{
mapper.BeforeMapClass += (mi, t, cam) =>
{
cam.Id(x =>
{
x.Column(t.Name + "ID");
x.Generator(Generators.Identity);
});
cam.Table(prefix+t.Name + "s");
};
foreach (var assembly in assemblies)
{
Type[] types = assembly.GetTypes();
List<Type> entityMappingTypes = new List<Type>();
foreach (var type in types)
{
if (type.IsSubclassOf(typeof(EntityBase)))
{
Type entityMappingType = typeof(EntityMapping<>);
Type genericType = entityMappingType.MakeGenericType(new Type[] { type });
object mappingInstance;
try
{
mappingInstance = Activator.CreateInstance(genericType);
}
catch (Exception e)
{
throw new MappingException("Unable to instantiate mapping class (see InnerException): " + genericType, e);
}
var mapping = mappingInstance as IConformistHoldersProvider;
if (mapping == null)
{
throw new ArgumentOutOfRangeException("type", "The mapping class must be an implementation of IConformistHoldersProvider.");
}
mapper.AddMapping(mapping);
}
}
}
}
}
}
将(三)中单元测试代码
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());
修改为
ModelMapper mapper = new ModelMapper(new EntityModelInspector());
mapper.AddEntityMappings(null, typeof(EntityBase).Assembly);
单元测试代码
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");
}
}
}
}
测试结果及测试输出同(三),但(三)中ClassMappings.cs不需要,已实现AutoMapping。