Я строю стандартный старый толстый клиент с NHibernate, где большинство моих сущностей (см. class A
) обезвоживаются при запуске приложения и остаются там в течение всего срока службы приложения. Это временные ссылки - пара очень тяжеловесных классов (class B
), которые инкапсулируют множество данных, которые являются лениво загруженными прокси, так что они действительно выбираются из базы данных только по требованию.
Это отлично работает. Проблема в том, что я хочу также явно выгружать эти типы B позже - с моей точки зрения, я хочу вернуться к состоянию, когда они были неинициализированными прокси и не занимали память.
Поскольку, похоже, не существует способа деинициализации прокси, я планировал просто удалить B
из кэша, удалив все существующие ссылки на B
(я понимаю, что должен их отслеживать Сам, это нормально), заменив их новым (неинициализированным) B-прокси, полученным из session.Load<B>
(то есть a.MyB = session.Load<B>(oldId);
)
Однако, похоже, что NHibernate хранит ссылку на старый B
в кэше, и B
не будет собирать мусор, пока я не удалю A
из кэша или не сброслю сеанс.
Я не уверен, почему. Я подозреваю, что он хранит ссылку для целей сравнения свойств, чтобы он мог увидеть, нужно ли ему возвращаться в базу данных, если я когда-нибудь запросю session.SaveOrUpdate(a)
. Но если установить отношения не каскадно, так ли это на самом деле имеет значение?
Беглое отображение и тесты ниже иллюстрируют этот вопрос. Используйте Make()
для создания базы данных и затем Load()
для запуска теста. Обратите внимание, что если вы раскомментируете сброс, вам нужно будет повторно Make()
.
public class A
{
internal int Id { get; private set; }
public B B { get; set; }
internal class Map : ClassMap<A>
{
public Map()
{
Id(x => x.Id);
Not.LazyLoad();
References(x => x.B)
.Cascade.None()
.LazyLoad(Laziness.Proxy);
}
}
}
public class B
{
protected internal virtual int Id { get; private set; }
public virtual A A { get; set; }
public virtual void Fizz()
{
}
internal class Map : ClassMap<B>
{
public Map()
{
Id(x => x.Id);
LazyLoad();
References(x => x.A)
.Cascade.None();
}
}
private static ISessionFactory BuildSF(bool rebuilddb)
{
return Fluently.Configure()
.Database(SQLiteConfiguration.Standard.UsingFile("v.sqldb"))
.Mappings(m =>
{
m.FluentMappings.Add<A.Map>();
m.FluentMappings.Add<B.Map>();
})
.ExposeConfiguration(cfg =>
{
//cfg.Interceptor = new Interceptor();
if (rebuilddb)
new SchemaExport(cfg).Create(false, true);
})
.BuildSessionFactory();
}
[Test]
public void Make()
{
var sessionFactory = BuildSF(true);
var sess = sessionFactory.OpenSession();
var a = new A();
var b = new B();
a.B = b;
b.A = a;
sess.Save(a);
sess.Save(b);
sess.Flush();
}
[Test]
public void Load()
{
var sessionFactory = BuildSF(false);
var sess = sessionFactory.OpenSession();
var a = sess.Load<A>(1);
// just loaded a, B should be a uninitialized proxy
Assert.That(! NHibernateUtil.IsInitialized(a.B));
a.B.Fizz();
// invoke on B, should now be initialized
Assert.That(NHibernateUtil.IsInitialized(a.B));
var weakB = new WeakReference(a.B);
sess.Evict(a.B);
//sess.Evict(a); // uncomment this to pass the final test
a.B = null;
//sess.Flush(); // or this
System.GC.Collect();
Console.WriteLine("Entities: " + sess.Statistics.EntityCount);
foreach (var ek in sess.Statistics.EntityKeys)
Console.WriteLine("\t" + ek.EntityName + " " + ek.Identifier);
if (sess.Contains(a))
Console.WriteLine("Session still contains a in cache");
else
Console.WriteLine("Session no longer holds a");
Assert.IsFalse(weakB.IsAlive);
}
На нижнем уровне мой вопрос: могу ли я каким-то образом удалить эту ссылку на старый B, не удаляя ни A, ни сброс?
На более высоком уровне мой вопрос: как я могу делать то, что я хочу? Моя непосредственная мысль обойти это было, возможно, просто отобразить и идентифицировать в классе A и вручную управлять постоянством / отложением B. Но, конечно, это немного вливает проблемы постоянства в мою модель предметной области, и мой опыт пытался это применить ». хаки для NHibernate - это, как правило, опыт «Алисы в стране чудес в кроличьей норе».