NHibernate - неправильные столбцы по запросам

Я получаю прерывистую проблему с NHibernate, где он генерирует запрос для сущности, но заменяет один из столбцов столбцом из совершенно другого (и несвязанного) объекта.

Он только заменяет один столбец и обычно решается перезапуском приложения (хотя иногда это требует нескольких попыток).

  • Приложение ASP.NET(.NET 4.0)
  • SessionFactory, созданная во время Application_Start
  • NHibernate 3.3.1- Все сопоставления/конфигурация, выполненные с помощью сопоставления по коду
  • Использование критериев Nhibernate

Любые данные по этому поводу будут высоко оценены!

Entity

public class LiquiditySourceItem : RunDataEntity, IEntity<int>
    {
        public virtual int Id { get; protected internal set; }
        public virtual int IdentID { get; protected internal set; }
        public virtual string Portfolio { get; protected internal set; }
        public virtual string ProfitCentre { get; protected internal set; }
        public virtual DateTime? MaturityDate { get; protected internal set; }
        public virtual string Curr1 { get; protected internal set; }
        public virtual string Curr2 { get; protected internal set; }
        public virtual decimal Reval { get; protected internal set; }
        public virtual string ContractType { get; protected internal set; }
        public virtual string ContractType2 { get; protected internal set; }
        public virtual string ContractCode { get; protected internal set; }
        public virtual decimal AmountSignedTradeUnit { get; protected internal set; }
        public virtual decimal Amount2Signed { get; protected internal set; }
        public virtual decimal SpotDelta { get; protected internal set; }
        public virtual string TradeRevalCurr { get; protected internal set; }
    }

Отображение сущностей

public LiquiditySourceItemMap()
{
    Id(x => x.Id, map => map.Column("RowId"));
    Property(x => x.IdentID, map => map.Column("IdentID"));
    Property(x => x.Portfolio, map => map.Column("Portfolio"));
    Property(x => x.ProfitCentre, map => map.Column("ProfitCentre"));
    Property(x => x.MaturityDate, map => map.Column("Con_Expiry"));
    Property(x => x.BuySell, map => map.Column("BS"));
    Property(x => x.Curr1, map => map.Column("Curr1"));
    Property(x => x.Curr2, map => map.Column("Curr2"));
    Property(x => x.Reval, map => map.Column("Reval"));
    Property(x => x.ContractType, map => map.Column("ContractType"));
    Property(x => x.ContractType2, map => map.Column("ContractType2"));
    Property(x => x.ContractCode, map => map.Column("ContractCode"));
    Property(x => x.AmountSignedTradeUnit, map => map.Column("AmountSignedTradeUnit"));
    Property(x => x.Amount2Signed, map => map.Column("Amount2Signed"));
    Property(x => x.ValSpot, map => map.Column("Val_Spot"));
    Property(x => x.SpotDelta, map => map.Column("SpotDelta"));
    Property(x => x.TradeRevalCurr, map => map.Column("Traderevalcurr"));
    Property(x => x.SourceReport, map => map.Column("SourceReport"));
    ManyToOne(x => x.RunContext, map => map.Column("RunContextID"));
    Table("Staging.vw_Liquidity");
}

Объект отчета

public class BusinessBreakdownStandardPosition : ReportRunDataEntity, IEntity<long>
    {
        public virtual long Id { get; set; }
        public virtual decimal FinalNettingAmountUSD { get; set; }
        public virtual decimal InitialChargeAmountUSD { get; set; }
        public virtual BusinessBreakdownInitialPrr InitialPrr { get; set; }
        public virtual IEnumerable<FinalInstrumentPosition> FinalInstrumentPositions { get; set; }
        public virtual decimal CreditEventPaymentUSD { get; set; }
        public virtual decimal ValuationChangeIncreaseUSD { get; set; }
        public virtual decimal ValuationChangeDecreaseUSD { get; set; }
        public virtual string ReportKey { get; set; }
        public virtual decimal USDCharge { get; set; }
        public virtual decimal USDChargeICG { get; set; }
        public virtual string InstrumentType { get; set; } 
}

Отображение объектов объявлений

public class BusinessBreakdownStandardPositionMap : ClassMapping<BusinessBreakdownStandardPosition>
    {
        public BusinessBreakdownStandardPositionMap()
        {
            Id(x => x.Id,
               m =>
                   {
                       m.Column("BusinessBreakdownStandardPositionID");
                       m.Generator(Generators.HighLow,
                                   g =>
                                   g.Params(
                                       new
                                           {
                                               table = "dbo.HiValue",
                                               max_lo = 10000,
                                               Where = string.Format("EntityName = 'BusinessBreakdownStandardPosition'")
                                           }));
                   });
            Property(x => x.FinalNettingAmountUSD, map => map.Column("FinalNettingAmountUSD"));
            Property(x => x.InitialChargeAmountUSD, map => map.Column("InitialAmountUSD"));
            Property(x => x.CreditEventPaymentUSD);
            Property(x => x.ValuationChangeDecreaseUSD);
            Property(x => x.ValuationChangeIncreaseUSD);
            Property(x => x.USDCharge);
            Property(x => x.USDChargeICG);
            Property(x=>x.InstrumentType);
            ManyToOne(p => p.RunContext, map => map.Column("ReportRunContextID"));
            ManyToOne(p => p.InitialPrr, m =>
                {
                    m.Column("InitialPrrID");
                    m.Cascade(Cascade.All);
                });
            Property(x => x.ReportKey);
            Bag(x => x.FinalInstrumentPositions, collectionMapping =>
                {
                    collectionMapping.Table("Reporting.BusinessBreakdownFinalInstrumentPositionStandardPositionMap");
                    collectionMapping.Cascade(Cascade.All);
                    collectionMapping.Key(k => k.Column("StandardPositionID"));
                }, mapping => mapping.ManyToMany(y => y.Column("FinalInstrumentPositionID")));
            Table("Reporting.BusinessBreakdownStandardPosition");
        }
    }

SQL-запрос, сгенерированный NHibernate

SELECT
    this_.RowId AS RowId47_0_,
    this_.IdentID AS IdentID47_0_,
    this_.Portfolio AS Portfolio47_0_,
    this_.ProfitCentre AS ProfitCe4_47_0_,
    this_.Con_Expiry AS Con5_47_0_,
    this_.BS AS BS47_0_,
    this_.Curr1 AS Curr7_47_0_,
    this_.Curr2 AS Curr8_47_0_,
    this_.Reval AS Reval47_0_,
    this_.ContractType AS Contrac10_47_0_,
    this_.ContractType2 AS Contrac11_47_0_,
    this_.ContractCode AS Contrac12_47_0_,
    this_.AmountSignedTradeUnit AS AmountS13_47_0_,
    this_.Amount2Signed AS Amount14_47_0_,
    this_.Val_Spot AS Val15_47_0_,
    this_.SpotDelta AS SpotDelta47_0_,
    this_.InitialAmountUSD AS Initial17_47_0_,
    this_.RunContextID AS RunCont18_47_0_,
    this_.SourceReport AS Sou19_47_0_
FROM Staging.vw_Liquidity this_

Exception

System.Data.SqlClient.SqlException (0x80131904): Invalid column name 'InitialAmountUSD'.

Как вы можете видеть, nhibernate заменил столбец LiquiditySourceItem 'Traderevalcurr' на 'InitialAmountUSD', который принадлежит объекту BusinessBreakdownStandardPosition. Эти сущности не имеют никакой связи. В противном случае SQL точно так же, как вы ожидали (включая порядок столбцов).

Наблюдения

  • Неправильный столбец всегда является допустимым столбцом в другой отображаемой сущности
  • Неправильный столбец заменит существующий
  • Проблема иногда возникает между другими объектами. Опять же, нет никакой связи между этими

Любые мысли?

Ответ 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.