查询大字节[]的NHibernate OutOfMemoryException

Mar*_*k E 16 c# nhibernate fluent-nhibernate

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

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

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

然后我尝试实现此处列出blob类型,但现在运行时我收到错误:

"无法将'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);
}
Run Code Online (Sandbox Code Playgroud)

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

-

通过进一步测试,它似乎挂在上面的物体大约是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;
           }                    
       }
   }
}
Run Code Online (Sandbox Code Playgroud)

小智 1

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

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

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

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

如果您还没有运行 64 位,并且甚至可以选择更大的硬件,则 64 位和更多物理内存可能是一种强力解决方案。

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

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