Создание неправильных столбцов по запросам

Мы сталкиваемся с проблемой прерывания работы с NHibernate, где она иногда генерирует запрос с неправильным столбцом SQL. Если перезапустить приложение, проблема перестает появляться (иногда требуется более одного перезапуска). Когда возникает проблема, в течение всего жизненного цикла этого процесса он всегда создает неправильный SQL для затронутого объекта. Это не всегда один и тот же затронутый объект.

Это приложение ASP.NET, в котором SessionFactory создается во время события Application_Start. Вся конфигурация и сопоставление выполняются с помощью кода.

У нас больше нет идей, как тестировать или отлаживать приложение, и я предполагаю, что в NHibernate есть некоторая ошибка, так как приложение исправляет себя при перезагрузке. Любые идеи/советы будут высоко оценены!

Вот пример:

Entity

namespace Example.Clinicas
{
    public partial class Clinica : Entidade   // Abstract base class that has a property Handle
    {
        public virtual string Ddd { get; set; }
        public virtual string Ddd2 { get; set; }
        public virtual long? Duracao { get; set; }
        public virtual string Numero { get; set; }
        public virtual string Numero2 { get; set; }
        public virtual string Prefixo { get; set; }
        public virtual string Prefixo2 { get; set; }
        public virtual long? HandlePrestador { get; set; }
        public virtual Example.Prestadores.Prestador Prestador { get; set; }
    }
}

Mapping

namespace Example.Clinicas.Mappings
{
    public class ClinicaMapping : ClassMapping<Clinica>
    {
        public ClinicaMapping() 
        {
            Table("CLI_CLINICA");

            Id(x => x.Handle, map => 
            {
                map.Column("HANDLE");
                map.Generator(Generators.Sequence, g => g.Params(new { sequence = "SEQ_AUTO1816" }));
            });
            Property(x => x.Ddd, map => map.Column( c=> 
            {
                c.Name("DDD1");
                c.Length(4);
            }));
            Property(x => x.Ddd2, map => map.Column( c=> 
            {
                c.Name("DDD2");
                c.Length(4);
            }));
            Property(x => x.Duracao, map => map.Column("INTERVALOAGENDA"));
            Property(x => x.Numero, map => map.Column( c=> 
            {
                c.Name("NUMERO1");
                c.Length(5);
            }));
            Property(x => x.Numero2, map => map.Column( c=> 
            {
                c.Name("NUMERO2");
                c.Length(5);
            }));
            Property(x => x.Prefixo, map => map.Column( c=> 
            {
                c.Name("PREFIXO1");
                c.Length(5);
            }));
            Property(x => x.Prefixo2, map => map.Column( c=> 
            {
                c.Name("PREFIXO2");
                c.Length(5);
            }));
            Property(x => x.HandlePrestador, map => map.Column("PRESTADOR"));
            ManyToOne(x => x.Prestador, map => 
            { 
                map.Column("PRESTADOR");
                map.Insert(false);
                map.Update(false);
            });
        }
    }
}

Команда

Session.Query<Clinica>().FirstOrDefault();

Сгенерированный SQL

select HANDLE489_,
       DDD2_489_,
       DDD3_489_,
       INTERVAL4_489_,
       NUMERO5_489_,
       NUMERO6_489_,
       PREFIXO7_489_,
       FATURADE8_489_,
       PRESTADOR489_
  from (select clinica0_.HANDLE               as HANDLE489_,
               clinica0_.DDD1                 as DDD2_489_,
               clinica0_.DDD2                 as DDD3_489_,
               clinica0_.INTERVALOAGENDA      as INTERVAL4_489_,
               clinica0_.NUMERO1              as NUMERO5_489_,
               clinica0_.NUMERO2              as NUMERO6_489_,
               clinica0_.PREFIXO1             as PREFIXO7_489_,
               clinica0_.FATURADEPARCELAMENTO as FATURADE8_489_,
               clinica0_.PRESTADOR            as PRESTADOR489_
          from CLI_CLINICA clinica0_)
 where rownum <= 1

Exception

ORA-00904: "CLINICA0_"."FATURADEPARCELAMENTO": invalid identifier

Интересные наблюдения:

  • Скорее всего, это повлияет на более крупные объекты (которые имеют большее количество свойств), но также иногда затрагивает небольшие объекты;
  • Сгенерированный SQL всегда имеет такое же количество столбцов, что и отображаемые свойства;
  • Столбцы SQL находятся в том же порядке, что и сопоставленные свойства в классе сопоставления;
  • Неправильный столбец заменит существующий,
  • Неправильный столбец является допустимым столбцом в другой сопоставленной сущности;
  • Между затронутым объектом и тем, у кого неправильный столбец, нет никакой связи;

Другие сведения:

  • Версия .NET: 4.0
  • Версия NHibernate: 3.3.3.400
  • Отображение по коду: NHibernate.Mapping.ByCode
  • Конфигурация по коду: NHibernate.Cfg

Отображения нагрузки

var mapper = new ModelMapper();

foreach (var assembly in resolver.GetAssemblies()) // resolver is a class that gets all the assemblies for the current application
    mapper.AddMappings(assembly.GetExportedTypes());

var mapping = mapper.CompileMappingForAllExplicitlyAddedEntities();

return mapping;

Конфигурация SessionFactory

var configure = new Configuration();
configure.DataBaseIntegration(x =>
                                  {
                                      x.Dialect<Oracle10gDialect>();  // Custom class
                                      x.ConnectionString = ConnectionString;
                                      x.BatchSize = 100;
                                      x.Driver<OracleMultiQueryDataClientDriver>();  // Custom class
                                      x.MaximumDepthOfOuterJoinFetching = 10;
                                      x.Timeout = 250;
                                      x.PrepareCommands = true;
                                      x.HqlToSqlSubstitutions = "true 'S', false 'N', yes 'S', no 'N'";
                                      x.LogFormattedSql = true;
                                      x.LogSqlInConsole = true;
                                      x.AutoCommentSql = true;
                                      x.IsolationLevel = IsolationLevel.ReadCommitted;
                                      x.ConnectionProvider<ConnectionProvider>();  // Custom class
                                  });
configure.Properties.Add(new KeyValuePair<string, string>("hibernate.command_timeout", "250"));
configure.Proxy(x => x.ProxyFactoryFactory<NHibernate.Bytecode.DefaultProxyFactoryFactory>());
configure.LinqToHqlGeneratorsRegistry<LinqToHqlGeneratorsRegistry>();
configure.CurrentSessionContext<NHibernate.Context.WebSessionContext>();
var mapping = GetMappings(); // Method showed above
mapping.autoimport = false;
configure.AddMapping(mapping);
var listener = new AuditEventListener();
configure.EventListeners.PostInsertEventListeners = new IPostInsertEventListener[] { listener };
configure.EventListeners.PostUpdateEventListeners = new IPostUpdateEventListener[] { listener };
configure.SessionFactory().GenerateStatistics();
return configure;

Ответы

Ответ 1

Я задал тот же вопрос на форуме групп пользователей NHibernate Users Groups, и кто-то считает, что они выработали основную причину (а также предложили решение):

https://groups.google.com/forum/#!topic/nhusers/BZoBoyWQEvs

Код проблемы находится в PropertyPath.Equals(PropertyPath), который пытается определить равенство, используя только хэш-код. Это отлично подходит для небольших базовых кодов, поскольку по умолчанию Object.GetHashCode() возвращает индекс последовательного объекта. Тем не менее, после сбора мусора эти индексы повторно используются, поскольку финализированные объекты удаляются, и создаются новые объекты... что приводит к тому, что несколько объектов получают один и тот же хэш-код... После того, как сбор мусора начинается, пути собственности имеют шанс поделиться тот же hashcode, который означает, что они в конечном итоге будут смешивать свои настройки для сталкивающихся свойств, поэтому неправильные имена столбцов...

Если вы хотите исправить эту ошибку, вы можете исправить исходный код NH:

Если у вас есть собственная копия источника NH, вы можете исправить ошибку, изменив NHibernate/Mapping/ByCode/PropertyPath.cs строку # 66:

return hashCode == other.GetHashCode();

To:

return hashCode == other.GetHashCode() && ToString() == other.ToString();

Подробную информацию об этой проблеме можно найти в группе Google.

Ответ 2

Проверьте свой журнал запросов, чтобы узнать, какой тип запроса его runnig, в вашем sql оттуда вы можете обнаружить проблему.

Ответ 3

похоже, что "платежи по кредитным картам" FATURADEPARCELAMENTO являются собственностью объекта "кредитор" PRESTADOR, если это так, он должен быть ссылкой и НЕ являться свойством в сопоставлении. Надеюсь, что помогает или, по крайней мере, заставляет вас указывать в правильном направлении.

ссылка заменит вашу линию Свойство (x = > x.HandlePrestador, map = > map.Column( "PRESTADOR" )); и будет чем-то близким к Ссылки (x = > x.HandlePrestador)