18

我们在使用 NHibernate 时遇到了一个间歇性问题,它偶尔会在 SQL 上生成带有错误列的查询。如果我们重新启动应用程序,问题就不会发生(有时需要多次重新启动)。当问题发生时,在该进程的生命周期内,它总是为受影响的实体生成错误的 SQL。它并不总是同一个受影响的实体。

它是一个 ASP.NET 应用程序,其中 SessionFactory 在 Application_Start 事件期间创建。所有的配置和映射都是通过代码完成的。

我们没有更多关于如何测试或调试应用程序的想法,我开始假设 NHibernate 中存在一些错误,因为应用程序在重新启动时会自行修复。任何想法/提示将不胜感激!

这是一个例子:

实体

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; }
    }
}

映射

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

例外

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;

会话工厂配置

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;
4

3 回答 3

4

我在 NHibernate Users Google Groups 论坛上问了同样的问题,有人认为他们已经找出了根本原因(并且还提出了解决方案):

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

问题代码位于 PropertyPath.Equals(PropertyPath) 中,它尝试仅使用哈希码来确定相等性。这适用于较小的代码库,因为默认的 Object.GetHashCode() 返回一个顺序对象索引。然而,在垃圾回收之后,这些索引会被重用,因为最终对象被删除并创建新对象......这导致多个对象获得相同的哈希码......一旦垃圾回收开始,属性路径就有机会共享相同的哈希码,这意味着他们最终会混淆他们的定制器来处理冲突的属性,从而导致错误的列名......

如果你想修复这个错误,你可以修补 NH 源代码:

如果您有自己的 NH 源代码副本,则可以通过更改 NHibernate/Mapping/ByCode/PropertyPath.cs 行 #66 来修复错误:

return hashCode == other.GetHashCode();

至:

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

有关该问题的完整详细信息,请查看 Google 群组。

于 2014-07-15T09:54:46.827 回答
1

检查您的查询日志以查看其运行的查询类型,在您的 sql 中,您可以发现问题。

于 2014-01-31T09:51:48.523 回答
1

看起来“信用卡付款”FATURADEPARCELAMENTO 是您的“贷方”对象 PRESTADOR 上的一个属性,如果是这种情况,它需要是一个引用而不是映射中的一个属性。希望能帮助或至少让你指出正确的方向

该参考将取代您的行 Property(x => x.HandlePrestador, map => map.Column("PRESTADOR")); 并且会接近 References(x => x.HandlePrestador)

于 2014-02-24T17:22:22.057 回答