我提出的解决方案涉及相当多的代码,但是假设您安装了 SqLite,您可以将其全部复制并粘贴到 VS 测试解决方案中,并且您应该能够自己运行测试。
由于我一直在努力解决使用 Nhibernate 的对象身份与对象相等和数据库身份问题,因此我阅读了各种帖子。但是,我无法清楚地了解如何结合集合正确设置对象标识。基本上,我得到的最大问题是,一旦将对象添加到集合中,它的标识(由 GetHashCode 派生)方法就不能改变。实现 GetHasHCode 的首选方法是使用业务密钥。但是,如果业务密钥不正确怎么办?我想用它的新业务密钥更新该实体。但是后来我的集合不同步,因为我违反了该对象身份的不变性。
以下代码是解决此问题的建议。但是,由于我当然不是 NHibernate 专家,也不是非常有经验的开发人员,因此我很乐意收到更多资深开发人员的评论,这是否是一种可行的方法。
using System;
using System.Collections.Generic;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Mapping;
using Iesi.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using NHibernate.Util;
namespace NHibernateTests
{
public class InMemoryDatabase : IDisposable
{
private static Configuration _configuration;
private static ISessionFactory _sessionFactory;
private ISession _session;
public ISession Session { get { return _session ?? (_session = _sessionFactory.OpenSession()); } }
public InMemoryDatabase()
{
// Uncomment this line if you do not use NHProfiler
HibernatingRhinos.Profiler.Appender.NHibernate.NHibernateProfiler.Initialize();
_sessionFactory = CreateSessionFactory();
BuildSchema(Session);
}
private static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(SQLiteConfiguration.Standard.InMemory().Raw("hbm2ddl.keywords", "none").ShowSql())
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Brand>())
.ExposeConfiguration(cfg => _configuration = cfg)
.BuildSessionFactory();
}
private static void BuildSchema(ISession Session)
{
SchemaExport export = new SchemaExport(_configuration);
export.Execute(true, true, false, Session.Connection, null);
}
public void Dispose()
{
Session.Dispose();
}
}
public abstract class Entity<T>
where T: Entity<T>
{
private readonly IEqualityComparer<T> _comparer;
protected Entity(IEqualityComparer<T> comparer)
{
_comparer = comparer;
}
public virtual Guid Id { get; protected set; }
public virtual bool IsTransient()
{
return Id == Guid.Empty;
}
public override bool Equals(object obj)
{
if (obj == null) return false;
return _comparer.Equals((T)this, (T)obj);
}
public override int GetHashCode()
{
return _comparer.GetHashCode((T)this);
}
}
public class Brand: Entity<Brand>
{
protected Brand() : base(new BrandComparer()) {}
public Brand(String name) : base (new BrandComparer())
{
SetName(name);
}
private void SetName(string name)
{
Name = name;
}
public virtual String Name { get; protected set; }
public virtual Manufactor Manufactor { get; set; }
public virtual void ChangeName(string name)
{
Name = name;
}
}
public class BrandComparer : IEqualityComparer<Brand>
{
public bool Equals(Brand x, Brand y)
{
return x.Name == y.Name;
}
public int GetHashCode(Brand obj)
{
return obj.Name.GetHashCode();
}
}
public class BrandMap : ClassMap<Brand>
{
public BrandMap()
{
Id(x => x.Id).GeneratedBy.GuidComb();
Map(x => x.Name).Not.Nullable().Unique();
References(x => x.Manufactor)
.Cascade.SaveUpdate();
}
}
public class Manufactor : Entity<Manufactor>
{
private Iesi.Collections.Generic.ISet<Brand> _brands = new HashedSet<Brand>();
protected Manufactor() : base(new ManufactorComparer()) {}
public Manufactor(String name) : base(new ManufactorComparer())
{
SetName(name);
}
private void SetName(string name)
{
Name = name;
}
public virtual String Name { get; protected set; }
public virtual Iesi.Collections.Generic.ISet<Brand> Brands
{
get { return _brands; }
protected set { _brands = value; }
}
public virtual void AddBrand(Brand brand)
{
if (_brands.Contains(brand)) return;
_brands.Add(brand);
brand.Manufactor = this;
}
}
public class ManufactorMap : ClassMap<Manufactor>
{
public ManufactorMap()
{
Id(x => x.Id);
Map(x => x.Name);
HasMany(x => x.Brands)
.AsSet()
.Cascade.AllDeleteOrphan().Inverse();
}
}
public class ManufactorComparer : IEqualityComparer<Manufactor>
{
public bool Equals(Manufactor x, Manufactor y)
{
return x.Name == y.Name;
}
public int GetHashCode(Manufactor obj)
{
return obj.Name.GetHashCode();
}
}
public static class IdentityChanger
{
public static void ChangeIdentity<T>(Action<T> changeIdentity, T newIdentity, ISession session)
{
changeIdentity.Invoke(newIdentity);
session.Flush();
session.Clear();
}
}
[TestClass]
public class BusinessIdentityTest
{
private InMemoryDatabase _db;
[TestInitialize]
public void SetUpInMemoryDb()
{
_db = new InMemoryDatabase();
}
[TestCleanup]
public void DisposeInMemoryDb()
{
_db.Dispose();
}
[TestMethod]
public void ThatBrandIsIdentifiedByBrandComparer()
{
var brand = new Brand("Dynatra");
Assert.AreEqual("Dynatra".GetHashCode(), new BrandComparer().GetHashCode(brand));
}
[TestMethod]
public void ThatSetOfBrandIsHashedByBrandComparer()
{
var brand = new Brand("Dynatra");
var manufactor = new Manufactor("Lily");
manufactor.AddBrand(brand);
Assert.IsTrue(manufactor.Brands.Contains(brand));
}
[TestMethod]
public void ThatHashOfBrandInSetIsThatOfComparer()
{
var brand = new Brand("Dynatra");
var manufactor = new Manufactor("Lily");
manufactor.AddBrand(brand);
Assert.AreEqual(manufactor.Brands.First().GetHashCode(), "Dynatra".GetHashCode());
}
[TestMethod]
public void ThatSameBrandCannotBeAddedTwice()
{
var brand = new Brand("Dynatra");
var duplicate = new Brand("Dynatra");
var manufactor = new Manufactor("Lily");
manufactor.AddBrand(brand);
manufactor.AddBrand(duplicate);
Assert.AreEqual(1, manufactor.Brands.Count);
}
[TestMethod]
public void ThatPersistedBrandIsSameAsLoadedBrandWithSameId()
{
var brand = new Brand("Dynatra");
var manufactor = new Manufactor("Lily");
manufactor.AddBrand(brand);
_db.Session.Transaction.Begin();
_db.Session.Save(brand);
var copy = _db.Session.Load<Brand>(brand.Id);
_db.Session.Transaction.Commit();
Assert.AreSame(brand, copy);
}
[TestMethod]
public void ThatLoadedBrandIsContainedByManufactor()
{
var brand = new Brand("Dynatra");
var manufactor = new Manufactor("Lily");
manufactor.AddBrand(brand);
_db.Session.Transaction.Begin();
_db.Session.Save(brand);
var copy = _db.Session.Load<Brand>(brand.Id);
_db.Session.Transaction.Commit();
Assert.IsTrue(brand.Manufactor.Brands.Contains(copy));
}
[TestMethod]
public void ThatAbrandThatIsLoadedUsesTheSameHash()
{
var brand = new Brand("Dynatra");
var manufactor = new Manufactor("Lily");
manufactor.AddBrand(brand);
_db.Session.Transaction.Begin();
_db.Session.Save(brand);
var id = brand.Id;
brand = _db.Session.Load<Brand>(brand.Id);
Assert.IsTrue(brand.Manufactor.Brands.Contains(new Brand("Dynatra")));
}
[TestMethod]
public void ThatBrandCannotBeFoundIfIdentityChanges()
{
var brand = new Brand("Dynatra");
var manufactor = new Manufactor("Lily");
manufactor.AddBrand(brand);
_db.Session.Transaction.Begin();
_db.Session.Save(brand);
Assert.IsTrue(brand.Manufactor.Brands.Contains(new Brand("Dynatra")));
brand.ChangeName("Dynatra_");
Assert.AreEqual("Dynatra_", brand.Name);
Assert.AreEqual("Dynatra_".GetHashCode(), brand.Manufactor.Brands.First().GetHashCode());
Assert.IsFalse(brand.Manufactor.Brands.Contains(brand));
// ToDo: I don't understand why this test fails
Assert.IsTrue(brand.Manufactor.Brands.Contains(new Brand("Dynatra")));
}
[TestMethod]
public void ThatSessionNeedsToBeClearedAfterIdentityChange()
{
var brand = new Brand("Dynatra");
var manufactor = new Manufactor("Lily");
manufactor.AddBrand(brand);
_db.Session.Transaction.Begin();
_db.Session.Save(brand);
var id = brand.Id;
brand = _db.Session.Load<Brand>(brand.Id);
// This makes the test pass
IdentityChanger.ChangeIdentity(brand.ChangeName, "Dynatra_", _db.Session);
brand = _db.Session.Load<Brand>(id);
Assert.IsFalse(brand.Manufactor.Brands.Contains(new Brand("Dynatra")));
Assert.IsTrue(brand.Manufactor.Brands.Contains(new Brand("Dynatra_")));
}
}
}
重要编辑!我现在考虑我建议的方法,正如已经指出的那样不是正确的方法。对于我面临的困境,我提供了不同的答案。