NHibernate: не удается присвоить значение свойства в конструкторе объекта

#nhibernate #fluent-nhibernate

#nhibernate #свободно-nhibernate

Вопрос:

Когда я запускаю следующий код в конструкторе B, я получаю исключение NullReferenceException. Я не могу присвоить значение свойству B Number.

Модели:

 public class A
{
    public A()
    {
        this.Number = 1;
        if (this.Number != 1)
        {
            throw new Exception("Failed to assign value in A's constructor");
        }
    }
    public virtual int Id { get; private set; }
    public virtual int Number { get; set; }
    public virtual B B { get; set; }
}

public class B
{
    public B()
    {
        this.Number = 1;
        // Throws NullReferenceException: Object reference not set to an instance of an object.
        if (this.Number != 1)
        {
            throw new Exception("Failed to assign value in B's constructor");
        }
    }
    public virtual int Id { get; private set; }
    public virtual int Number { get; set; }
}
  

Сопоставления:

 public class AMappings : ClassMap<A>
{
    public AMappings()
    {
        Id(x => x.Id);
        Map(x => x.Number);
        References(x => x.B).Cascade.All();
    }
}

public class BMappings : ClassMap<B>
{
    public BMappings()
    {
        Id(x => x.Id);
        Map(x => x.Number);
    }
}
  

Основной метод:

 class Program
{
    static void Main(string[] args)
    {
        // Create connection string
        string connectionString = new System.Data.SqlClient.SqlConnectionStringBuilder()
                                      {
                                          DataSource = @".r2",
                                          InitialCatalog = "TestNHibernateMappings",
                                          IntegratedSecurity = true
                                      }.ConnectionString;

        // Create SessionFactory
        ISessionFactory sessionFactory = Fluently.Configure()
        .Database(MsSqlConfiguration
                      .MsSql2008.ConnectionString(connectionString)
                      .ShowSql())
        .Mappings(m => m.FluentMappings
            .Add(typeof(AMappings))
            .Add(typeof(BMappings)))
        .ExposeConfiguration(BuildSchema)
        .BuildConfiguration()
        .BuildSessionFactory();

        // Create test object in DB
        using (var session = sessionFactory.OpenSession())
        {
            using (var trans = session.BeginTransaction())
            {
                var a = new A();
                a.B = new B();
                session.Save(a);
                trans.Commit();
            }
        }

        // Read test object from DB
        using (var session = sessionFactory.OpenSession())
        {
            using (var trans = session.BeginTransaction())
            {
                var a = session.Get<A>(1);
            }
        }
    }

    static void BuildSchema(Configuration cfg)
    {
        new SchemaExport(cfg).Create(false, true);
    }
}
  

Я использую NHibernate 3.1.0.4000, FluentNHibernate 1.2.0.712.

Есть идеи? Спасибо.

Ответ №1:

Ключевым моментом здесь является то, что Number является виртуальным.

A.B выполняется отложенная загрузка. NHibernate создает прокси для B , который переопределяет каждое виртуальное свойство в классе. При первом обращении к одному из не Id свойств NHibernate загрузит данные из базы данных для заполнения объекта.

Поскольку этот прокси-класс является подклассом B , B конструктор будет вызываться перед конструктором прокси. Когда B конструктор устанавливает виртуальное свойство Number , он вызывает Number свойство, определенное в подклассе прокси, который еще не был инициализирован.

Более подробное обсуждение конструкторов и наследования см. http://www.yoda.arachsys.com/csharp/constructors.html

Чтобы исправить это, преобразуйте любые свойства, которые вы хотите установить в конструкторе, для использования резервных полей вместо автоматических свойств, затем установите поле вместо свойства в конструкторе.

 public class B
{
    public B()
    {
        _number = 1;
    }

    public virtual int Id { get; private set; }

    private int _number;

    public virtual int Number
    {
        get { return _number; }
        set { _number = value; }
    }
}
  

Это немного более подробное, но эффективно избегает прикосновения к виртуальным методам или свойствам в конструкторе.