生成错误的列上查询错误

2023-09-03 09:01:01 作者:白天不懂爷dē黑

我们有与NHibernate的间歇性问题,即有时会生成具有对SQL错误的列的查询。如果我们重新启动的问题不再出现在应用程序(有时它需要一个以上的启动)。当问题出现时,这个过程的生命周期中,它总是产生错误的SQL受影响的实体。 It's的不可以总是相同的受影响的实体。

It's其中Application_Start事件期间创建的SessionFactory的ASP.NET应用程序。所有的配置和映射由code完成。

我们鸵鸟政策有什么更多的想法如何测试和调试应用程序,并I'm开始承担there's在NHibernate的一些bug,因为应用程序本身的修复后重新启动。任何想法/提示将更加AP preciated!

Here's一个例子:

实体

 命名空间Example.Clinicas
{
    公共部分类CLINICA:Entidade //抽象基类,有一​​个属性Handle
    {
        公共虚拟字符串DDD {获得;组; }
        公共虚拟字符串DDD2 {获得;组; }
        公共虚拟多久? Duracao {获得;组; }
        公共虚拟字符串NUMERO {获得;组; }
        公共虚拟字符串Numero2 {获得;组; }
        公共虚拟字符串prefixo {获得;组; }
        公共虚拟字符串prefixo2 {获得;组; }
        公共虚拟多久?处理prestador {获得;组; }
        。公共的虚拟实例prestadores prestador prestador {获得;组; }
    }
}
 
用友Tong操作工资管理运行时错误 ―2147217887 80040e21 1 多步操作产生错误请检查每一步状态值关

映射

 命名空间Example.Clinicas.Mappings
{
    公共类ClinicaMapping:ClassMapping< CLINICA>
    {
        公共ClinicaMapping()
        {
            表(CLI_CLINICA);

            ID(X => x.Handle,地图=>
            {
                map.Column(处理);
                map.Generator(Generators.Sequence,G => g.Params(新{序列=SEQ_AUTO1816}));
            });
            物业(X => x.Ddd,地图=> map.Column(C =>
            {
                c.Name(DDD1);
                c.Length(4);
            }));
            物业(X => x.Ddd2,地图=> map.Column(C =>
            {
                c.Name(DDD2);
                c.Length(4);
            }));
            物业(X => x.Duracao,地图=> map.Column(INTERVALOAGENDA));
            物业(X => x.Numero,地图=> map.Column(C =>
            {
                c.Name(NUMERO1);
                c.Length(5);
            }));
            物业(X => x.Numero2,地图=> map.Column(C =>
            {
                c.Name(NUMERO2);
                c.Length(5);
            }));
            物业(X =&GT,X prefixo,地图=> map.Column(C =>
            {
                c.Name(preFIXO1);
                c.Length(5);
            }));
            物业(X =&GT,X prefixo2,地图=> map.Column(C =>
            {
                c.Name(preFIXO2);
                c.Length(5);
            }));
            物业(X => x.Handle prestador,地图=> map.Column(preSTADOR));
            多对一(X =>,X prestador,地图=>
            {
                map.Column(preSTADOR);
                map.Insert(假);
                map.Update(假);
            });
        }
    }
}
 

命令

  Session.Query< CLINICA>()FirstOrDefault()。
 

生成的SQL

 选择HANDLE489_,
       DDD2_489_,
       DDD3_489_,
       INTERVAL4_489_,
       NUMERO5_489_,
       NUMERO6_489_,
       preFIXO7_489_,
       FATURADE8_489_,
       preSTADOR489_
  从(选择clinica0_.HANDLE为HANDLE489_,
               clinica0_.DDD1为DDD2_489_,
               clinica0_.DDD2为DDD3_489_,
               clinica0_.INTERVALOAGENDA为INTERVAL4_489_,
               clinica0_.NUMERO1为NUMERO5_489_,
               clinica0_.NUMERO2为NUMERO6_489_,
               clinica0 _。preFIXO1为preFIXO7_489_,
               clinica0_.FATURADEPARCELAMENTO为FATURADE8_489_,
               clinica0 _。preSTADOR为preSTADOR489_
          从CLI_CLINICA clinica0_)
 其中,ROWNUM< = 1
 

异常

  ORA-00904:CLINICA0 _FATURADEPARCELAMENTO:无效的标识符
 

有趣的特点:

这是更可能影响更大的实体(也有一些性能更高),而且还影响较小的实体偶尔; 在生成的SQL总是有相同的列数为映射特性; 上的SQL的列是在相同的顺序对映射类中的映射特性; 在错误的列将取代现有的; 在错误的列具有不同映射实体的有效列; 有受影响的实体和一个有错误的列之间没有任何关系;

其他联系方式:

.NET版本: 4.0 NHibernate的版本: 3.3.3.400 按code映射: NHibernate.Mapping.By code 配置由code: NHibernate.Cfg

加载映射

  VAR映射器=新ModelMapper();

的foreach(在resolver.GetAssemblies()VAR组装)//解析器是获取所有的组件当前应用程序的类
    mapper.AddMappings(assembly.GetExportedTypes());

VAR映射= mapper.CompileMappingForAllExplicitlyAddedEntities();

返回的映射;
 

SessionFactory的配置

 无功配置=新配置();
configure.DataBaseIntegration(X =>
                                  {
                                      x.Dialect&其中; Oracle10gDialect>(); //自定义类
                                      x.ConnectionString = ConnectionString的;
                                      x.BatchSize = 100;
                                      x.Driver&其中; OracleMultiQueryDataClientDriver>(); //自定义类
                                      x.MaximumDepthOfOuterJoinFetching = 10;
                                      x.Timeout = 250;
                                      x prepareCommands = TRUE。
                                      x.HqlToSqlSubstitutions =真正的'S'假'N',是'S',没有'N';
                                      x.LogFormattedSql = TRUE;
                                      x.LogSqlInConsole = TRUE;
                                      x.AutoCommentSql = TRUE;
                                      x.IsolationLevel = IsolationLevel.ReadCommitted;
                                      x.ConnectionProvider<的ConnectionProvider>(); //自定义类
                                  });
configure.Properties.Add(新KeyValuePair<字符串,字符串>(hibernate.command_timeout,250));
configure.Proxy(X => x.ProxyFactoryFactory< NHibernate.Byte code.DefaultProxyFactoryFactory>());
configure.LinqToHqlGeneratorsRegistry&其中; LinqToHqlGeneratorsRegistry>();
configure.CurrentSessionContext&其中; NHibernate.Context.WebSessionContext>();
VAR映射= GetMappings(); //方法上面显示
mapping.autoimport = FALSE;
configure.AddMapping(映射);
VAR监听器=新AuditEventListener();
configure.EventListeners.PostInsertEventListeners =新IPostInsertEventListener [] {监听};
configure.EventListeners.PostUpdateEventListeners =新IPostUpdateEventListener [] {监听};
configure.SessionFactory()GenerateStatistics()。
返回配置;
 

解决方案

我问的NHibernate的用户谷歌论坛论坛同样的问题,有人认为他们已经摸索出的根本原因(并且还提出了一个解决方案):

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

  

问题code在PropertyPath.Equals(的PropertyPath),它试图通过只使用散列code,以确定相等。这很好地满足小code碱基作为默认Object.GetHash code()返回一个连续的对象索引。但是,垃圾收集后,这些指数得到重新用作定稿对象被删除,并创建新的对象...这导致多个对象得到相同的哈希code ...一旦垃圾收集踢,属性路径具有有机会分享相同的散列code,这意味着他们将最终混淆了自己的定制器的碰撞性能,从而错误的列名...

如果你想解决这个错误,你可以修补的NH源$ C ​​$ C:

  

如果你有自己的NH源副本,则可以通过更改的NHibernate /制图/由code修复bug / PropertyPath.cs从线#66:

     

返回哈希code == other.GetHash code();

     

要:

     

返回哈希code == other.GetHash code()及和放大器;的ToString()== other.ToString();

请检查出谷歌集团发行的全部细节。

We are having an intermittent problem with NHibernate where it will occasionally generate a query with a wrong column on the SQL. If we restart the application the problem ceases to happen (sometimes it requires more than one restart). When the problem occurs, during the lifetime of that process, it always produces the wrong SQL for the affected entity. It´s not always the same affected entity.

It´s an ASP.NET application where the SessionFactory is created during the Application_Start event. All the configuration and mapping are done by code.

We don´t have any more ideas how to test or debug the application, and I´m starting to assume there´s some bug in NHibernate, since the application fixes itself upon restart. Any ideas/tips will be much appreciated!

Here´s an example:

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

Command

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

Generated 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

Interesting Observations:

It is more likely to affect bigger entities (that has a higher number of properties), but also affects smaller entities occasionally; The generated SQL always have the same number of columns as mapped properties; The columns on the SQL are in the same order as the mapped properties on the mapping class; The wrong column will replace an existing one; The wrong column is a valid column in a different mapped entity; There is no relationship between the affected entity and the one that has the wrong column;

Other Details:

.NET Version: 4.0 NHibernate Version: 3.3.3.400 Mapping by Code: NHibernate.Mapping.ByCode Configuration by Code: NHibernate.Cfg

Load Mappings

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 Configuration

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;

解决方案

I asked the same question on the NHibernate Users Google Groups forum, and someone thinks they have worked out the root cause (and have also proposed a solution):

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

The problem code is in PropertyPath.Equals(PropertyPath) which attempts to determine equality by only using the hash code. This works fine for smaller code bases as the default Object.GetHashCode() returns a sequential object index. However, after garbage collection, these indices get reused as finalized objects are removed and new objects are created...which results in more than one object getting the same hashcode...Once garbage collection kicks in, property paths have a chance to share the same hashcode which means they will ultimately mix up their customizers for the colliding properties, thus the wrong column names...

If you want to fix this the bug, you can patch the NH source code:

If you have your own copy of the NH source, you can fix the bug by changing NHibernate/Mapping/ByCode/PropertyPath.cs line #66 from:

return hashCode == other.GetHashCode();

To:

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

Please check out the Google Group for full details of the issue.