Есть ли способ прекратить ссылаться на дочерний объект в NHibernate без очистки сеанса или удаления его родителя из кэша сеанса? - PullRequest
2 голосов
/ 30 июня 2010

Я строю стандартный старый толстый клиент с 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 - это, как правило, опыт «Алисы в стране чудес в кроличьей норе».

1 Ответ

1 голос
/ 09 июля 2010

Я считаю, что для толстых клиентских или настольных приложений подход к управлению сессиями NHibernate подробно здесь является единственным «работоспособным» решением. Суть этого решения состоит в том, чтобы сохранить большинство объектов отсоединенными от сеанса, если они:

  1. активно редактируется пользователем
  2. были обновлены в другом месте в приложении. Используйте события, чтобы уведомить другое представление о том, что им нужно обновить свои данные

Обратите внимание, что я не мог заставить сеансы без сохранения состояния работать в соответствии со статьей MSDN, поэтому я просто использую обычный сеанс и затем очищаю его, когда моя транзакция завершается.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...