私が提案するソリューションにはかなりのコードが含まれますが、SqLiteがインストールされていれば、すべてをコピーしてVSテストソリューションに貼り付けることができ、自分でテストを実行できるはずです。
Nhibernateを使用してオブジェクトIDとオブジェクトの同等性およびデータベースIDの問題に苦労しているので、さまざまな投稿を読みました。ただし、コレクションと組み合わせてオブジェクトIDを適切に設定する方法を明確に把握することはできませんでした。基本的に、私が得た大きな問題は、オブジェクトがコレクションに追加されると、そのID(GetHashCodeによって派生)メソッドが変更できないことです。GetHasHCodeを実装するための推奨される方法は、ビジネスキーを使用することです。しかし、ビジネスキーが適切でない場合はどうなるでしょうか。そのエンティティを新しいビジネスキーで更新してもらいたいのですが。しかし、そのオブジェクトのIDの不変性に違反したため、コレクションが同期しなくなりました。
以下のコードは、この問題を解決するための提案です。ただし、私は確かに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_")));
}
}
}
重要な編集!正しいアプローチではないと指摘されているように、私は今、私が提案していたアプローチを検討します。私が直面していたジレンマに対して、私は別の答えを提供しました。