查询大字节[]的NHibernate OutOfMemoryException

时间:2015-10-28 13:53:02

标签: c# nhibernate fluent-nhibernate

我正在尝试使用Fluent NHibernate来迁移需要某些数据库“按摩”的数据库。源数据库是MS Access数据库,我粘贴的当前表是一个带有OLE对象字段的表。目标数据库是MS SQL Server Express数据库。

在实体中,我只是将此字段定义为byte[]但是在加载时,即使只为单个记录加载单个字段,我也遇到了System.OutOfMemoryException

byte[] test = aSession.Query<Entities.Access.Revision>().Where(x => x.Id == 5590).Select(x => x.FileData).SingleOrDefault<byte[]>();

然后我尝试实现blob type listed here,但现在运行时我收到错误:

  

“无法将'System.Byte []'类型的对象强制转换为类型   'TestProg.DatabaseConverter.Entities.Blob'。“}

我无法想象Ole对象是否大于100mb但无法检查。有没有什么好方法可以使用Fluent NHibernate从一个数据库中复制出来并保存到另一个数据库中,还是需要查看其他选项?

我处理这些的正常循环是:

IList<Entities.Access.Revision> result;
IList<int> recordIds = aSession.Query<Entities.Access.Revision>().Select(x => x.Id).ToList<int>();

foreach (int recordId in recordIds)
{
  result = aSession.Query<Entities.Access.Revision>().Where(x => x.Id == recordId).ToList<Entities.Access.Revision>();
  Save(sqlDb, result);
}

保存功能只是将属性从一个复制到另一个,并且对于某些实体用于操纵数据或向用户提供与数据问题相关的反馈。我正在为两个数据库使用无状态会话。

-

通过进一步测试,它似乎挂在上面的物体大约是60-70mb。我目前正在测试使用GetBytes使用OleDbDataReader获取数据。

-

更新(11月24日):我还没有找到一种方法让它与NHibernate一起使用。我确实使用常规db命令对象。我把以下制作的函数的代码放在任何好奇的人身上。这是我的数据库转换器中的代码,因此前缀为“a”的对象是访问数据库对象,而“s”是sql版本。

public void MigrateBinaryField(int id, string tableName, string fieldName)
{
   var aCmd = new OleDbCommand(String.Format(@"SELECT ID, {0} FROM {1} WHERE ID = {2}", fieldName, tableName, id), aConn);

   using (var reader = aCmd.ExecuteReader(System.Data.CommandBehavior.SequentialAccess))
   {
       while (reader.Read())
       {
           if (reader[fieldName] == DBNull.Value)
               return;

           long read = 0;
           long offset = 0;

           // Can't .WRITE a NULL column so need to set an initial value
           var sCmd = new SqlCommand(string.Format(@"UPDATE {0} SET {1} = @data WHERE OldId = @OldId", tableName, fieldName), sConn);
           sCmd.Parameters.AddWithValue("@data", new byte[0]);
           sCmd.Parameters.AddWithValue("@OldId", id);
           sCmd.ExecuteNonQuery();

           // Incrementally store binary field to avoid OutOfMemoryException from having entire field loaded in memory
           sCmd = new SqlCommand(string.Format(@"UPDATE {0} SET {1}.WRITE(@data, @offset, @len) WHERE OldId = @OldId", tableName, fieldName), sConn);
           while ((read = reader.GetBytes(reader.GetOrdinal(fieldName), offset, buffer, 0, buffer.Length)) > 0)
           {
               sCmd.Parameters.Clear();
               sCmd.Parameters.AddWithValue("@data", buffer);
               sCmd.Parameters.AddWithValue("@offset", offset);
               sCmd.Parameters.AddWithValue("@len", read);
               sCmd.Parameters.AddWithValue("@OldId", id);

               sCmd.ExecuteNonQuery();

               offset += read;
           }                    
       }
   }
}

2 个答案:

答案 0 :(得分:0)

这听起来像我在其他框架上使用.NET时看到的结果。

NHibernate下面的ADO.NET下面的本机数据库驱动程序(这里有两个“底层”)将需要一个固定的目标内存块,在驱动程序填充它时无法在内存中移动。由于.NET垃圾收集器可以在一个单独的线程上随机移动内存块以压缩堆,NHibernate的底层.NET数据库层必须创建一个非托管内存块来接收数据,这有效地使内存量翻倍需要加载记录。

另外,我还没有验证下一点,但NHibernate应该尝试缓存记录块,因为它绕过了一些关系数据库查询操作。这允许NHibernate减少数据库请求,这对于较小的记录大小是最佳的,但是一次需要许多记录(包括许多blob)才能适合内存。

作为解决方案的第一步,请确保该进程确实在内存中运行机器(或者如果它是32位,请确保它达到2GB限制)。如果是这样,尝试确定基线 - 如果它正在处理具有各种blob大小的记录,它使用的最小和最大内存是多少?从那里,您可以估计该大记录(或包含该记录的缓存块)需要多少内存!)

如果你还没有运行64位,64位和更多的物理内存可能是一个强力解决方案,如果更大的硬件甚至是一个选项。

另一种可能的解决方案是检查NHibernate是否具有可缓存数据的可配置设置或属性。例如,检查是否可以设置一个属性来限制一次加载的记录数,或者告诉它将其缓存限制为特定大小(以字节为单位)。

更有效的解决方案是将您的ADO.NET代码用于blob;这可能是最好的解决方案,特别是如果你期望甚至比这个特定的60-70MB blob更大的blob。 MS Access通常允许多个只读连接,因此只要NHibernate没有将数据库设置为阻止其他连接,这就应该有效。

答案 1 :(得分:0)

我强烈怀疑这是由于NHibernate会话缓存造成的。

尝试在单独的会话中读取每个Blob,或者至少定期刷新/清除它,并在循环中添加一个计数器“ i”和类似条件

if (i % 10 == 0)
{
    aSession.Flush();
    aSession.Clear();
}