【问题标题】:Reading SQL Varbinary Blob from Database从数据库中读取 SQL Varbinary Blob
【发布时间】:2017-01-24 11:08:16
【问题描述】:

我正在将文件保存到 sql blob 到 varbinary(max) 列,并且现在保存的东西正在工作(我相信)。

我不知道如何读取数据,因为我正在使用存储过程检索我的数据库值,我应该能够访问列数据,如 ds.Tables[0].Rows[ 0]["blobData"];所以我有必要拥有一个 SQLCommand 等,就像我在下面的示例中看到的那样:

private void OpenFile(string selectedValue)
{
    String connStr = "...connStr";
    fileName = ddlFiles.GetItemText(ddlFiles.SelectedItem);

    using (SqlConnection conn = new SqlConnection(connStr))
    {
        conn.Open();
        using (SqlCommand cmd = conn.CreateCommand())
        {
            cmd.CommandText = "SELECT BLOBData FROM BLOBTest WHERE testid = " + selectedValue;

            using (SqlDataReader dr = cmd.ExecuteReader())
            {
                while (dr.Read())
                {
                    int size = 1024 * 1024;
                    byte[] buffer = new byte[size];
                    int readBytes = 0;
                    int index = 0;

                    using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write, FileShare.None))
                    {
                        while ((readBytes = (int)dr.GetBytes(0, index, buffer, 0, size)) > 0)
                        {
                            fs.Write(buffer, 0, readBytes);
                            index += readBytes;
                        }
                    }
                }
            }
        }
    }

当我可以在没有 sql 命令的情况下访问我需要的列时,是否有更简单的方法来执行此操作?

希望我的问题足够清楚,如果没有,请询​​问,我会详细说明!

更新:

现在的情况是这样的——我有我的存储过程返回的 blobData 列的值,并且可以将它传递到内存流中并调用 'LoadDocument(memStream);但是,这会导致乱码文本而不是我的实际文件显示。

我现在的问题是有没有办法获取完整路径,包括存储在 SQL Blob 中的文件的文件扩展名?我目前正在考虑为此使用 Filetable,希望能够获得完整路径。

更新 2:

我尝试创建一个临时文件并阅读它无济于事(仍然是胡言乱语)

                string fileName = System.IO.Path.GetTempFileName().ToString().Replace(".tmp", fileExt);

            using (MemoryStream myMemoryStream = new MemoryStream(blobData, 0, (int)blobData.Length, false, true))
            {
                using (FileStream myFileStream1 = File.Create(fileName))
                {
                    myMemoryStream.WriteTo(myFileStream1);

                    myMemoryStream.Flush();
                    myMemoryStream.Close();

                    myFileStream1.Flush();
                    myFileStream1.Close();

                    FileInfo fi = new FileInfo(fileName);

                    Process prc = new Process();
                    prc.StartInfo.FileName = fi.FullName;
                    prc.Start();
                }
            }

干杯, H

【问题讨论】:

  • 我建议不要使用"...WHERE testid = " + selectedValue,因为它不是kosher并导致SQL Injection。而是使用参数,例如"... WHERE testid = @id"cmd.Parameters.AddWithValue("@id", testid).
  • 我会修改这个,谢谢。至于我的问题中的更新,您知道可以做什么吗?我想我需要从存储的 blob 中恢复原始文件路径
  • @bjjrolls,blob 列将仅包含您写入的内容。因此,要从数据库中获取文件路径和扩展名,您需要将其存储在那里。我会添加一个单独的列来存储文件名。将问题分解为更小的步骤。首先验证您是否可以通过将 blob 数据 (byte[]) 保存到文件并将原始文件与从数据库中检索到的结果进行比较来正确地从数据库中读取文件。只有在您确认这一点后,您才能继续在查看器中加载您的文档,问题就变成了“如何使用查看器/库 XYZ”。
  • 您是否考虑过使用 SQL Server FileStream 对象,然后您可以像访问文件一样访问数据。建议将 FileStream 用于 1.) 正在存储的对象平均大于 1 MB。 2.) 快速读取访问很重要。 3.) 您正在开发使用中间层作为应用程序逻辑的应用程序。
  • 感谢@VladimirBaranov 和 SteveFord 的回复,但我现在使用文件流文件表来存储用户文件,因为它们通常大于 2mb

标签: c# sql .net sql-server blob


【解决方案1】:

你让它变得比它需要的更困难。之所以使用 MySQL,只是因为它很方便——提供者的工作方式几乎相同。有些东西需要调整以处理非常大的数据项(更多的是服务器而不是 DB Provider)。

保存图片

string sql = "INSERT INTO BlobDemo (filename, fileType, fileData) VALUES (@name, @type, @data)";
byte[] imgBytes;

using (MySqlConnection dbCon = new MySqlConnection(MySQLConnStr))
using (MySqlCommand cmd = new MySqlCommand(sql, dbCon))
{  
    string ext = Path.GetExtension(filename);

    dbCon.Open();
    cmd.Parameters.Add("@name", MySqlDbType.String).Value = "ziggy";
    cmd.Parameters.Add("@data", MySqlDbType.Blob).Value = File.ReadAllBytes(filename);
    cmd.Parameters.Add("@tyoe", MySqlDbType.String).Value = ext;
    int rows = cmd.ExecuteNonQuery();
}

文件数据直接提供给 DB Provider

有没有办法获得完整路径,包括存储在 SQL Blob 中的文件的文件扩展名?

没有。您的代码和上面的代码正在保存构成图像或任何文件的 bytes

回读图像数据

这将读回数据,将其保存到文件并启动关联的应用程序:

string SQL = "SELECT itemName, itemData, itemtype FROM BlobDemo WHERE Id = @id";

string ext = "";
string tempFile = Path.Combine(@"C:\Temp\Blobs\", 
    Path.GetFileNameWithoutExtension(Path.GetTempFileName())); 

using (MySqlConnection dbCon = new MySqlConnection(MySQLConnStr))
using (MySqlCommand cmd = new MySqlCommand(SQL, dbCon))
{
    cmd.Parameters.Add("@id", MySqlDbType.Int32).Value = 14;
    dbCon.Open();

    using (MySqlDataReader rdr =  cmd.ExecuteReader())
    {
        if (rdr.Read())
        {
            ext = rdr.GetString(2);
            File.WriteAllBytes(tempFile + ext, (byte[])rdr["itemData"]);
        }
    }
   
    // OS run test
    Process prc = new Process();
    prc.StartInfo.FileName = tempFile + ext;
    prc.Start();
}
  • 回读匹配的字节数
  • 相关应用与图片一起启动就好了
  • 图片框中显示的图片

在这两种情况下,File.ReadAllBytes()File.WriteAllBytes() 将为您完成大部分工作,无论文件类型如何。

没有必要一次挖出 1k 的数据。如果 blob 类似于您希望在应用中使用的图像:

using (MySqlDataReader rdr = cmd.ExecuteReader())
{
    if (rdr.Read())
    {
        ext = rdr.GetString(2);
        using (MemoryStream ms = new MemoryStream((byte[])rdr["imgData"]))
        {
            picBox.Image = Image.FromStream(ms);
        }
    }
}

可以将 blob 字节馈送到 memstream,甚至不需要创建临时 Image,除非您不需要显示它。

总而言之,Ceiling Cat 恢复得很好(图像为 1.4 MB,放大后;另一个 15.4 MB 图像的测试也有效 - 两者都比我愿意存储在数据库中的要大)。:

根据使用方式,考虑将图像存档到文件系统上的某个位置并仅保存文件名 - 可能添加 Id 以确保名称是唯一的,并有助于在视觉上将它们链接到记录。大块数据不仅会使 DB 膨胀,而且在转换字节和从字节转换时显然会产生一些开销,这是可以避免的。


如果您想/需要在关联应用程序完成后的某个时间点删除它们(不是问题的真正组成部分),然后使用特定目录中的临时文件,以便您可以删除其中的所有内容(有条件1) 当应用程序结束或启动时:

private string baseAppPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
                    "Company Name", "Product Name", "Temp Files");

附加一个临时文件名和单个文件的实际扩展名。或者,您可以维护一个List<string> trashCan 来存储您创建的每个文件的名称,以便以后删除。

1 每当您删除它们时,确实允许文件仍然可以在与扩展关联的应用中打开。

【讨论】:

  • 感谢您为您的回答付出如此多的努力,我很感激。我明白你的意思,但我担心的是,每次用户访问它时,这都会创建一个新文件,我想简单地从 blobData 列流式传输。如果需要创建此文件,有没有办法立即删除它?
  • 另一个问题是它并不总是图像类型,它可以是 .doc、.docx、.pdf、.jpeg、.png 等等,但在保存时我存储了文件在单独的列中扩展。这一切都可行吗?
  • 我不知道... my concern is that this will create a new file 中的“this”指的是什么。听起来 File.ReadAllBytes() 和 File.WriteAllBytes() 是您需要的。见编辑
【解决方案2】:

通过 .NET SQL Server 提供程序,您可以使用一个鲜为人知但很酷的类 SqlBytes。它是专门为映射varbinary 字段而设计的,但是关于如何使用它的示例并不多。

您可以使用它保存到数据库中(您可以使用存储过程或直接 SQL,就像我在这里演示的那样,我们只是假设 MyBlobColumn 是 varbinary 之一)。

string inputPath = "YourInputFile";
using (var conn = new SqlConnection(YourConnectionString))
{
    conn.Open();
    using (var cmd = conn.CreateCommand())
    {
        // note we define a '@blob' parameter in the SQL text
        cmd.CommandText = "INSERT INTO MyTable (Id, MyBlobColumn) VALUES (1, @blob)";
        using (var inputStream = File.OpenRead(inputPath))
        {
            // open the file and map it to an SqlBytes instance
            // that we use as the parameter value.
            var bytes = new SqlBytes(inputStream);
            cmd.Parameters.AddWithValue("blob", bytes);

            // undercovers, the reader will suck the inputStream out through the SqlBytes parameter
            cmd.ExecuteNonQuery();
        }
    }
}

要将文件从数据库中读取到流中,您可以这样做。

string outputPath = "YourOutputFile";
using (var conn = new SqlConnection(YourConnectionString))
{
    conn.Open();
    using (var cmd = conn.CreateCommand())
    {
        // this is a regular direct SQL command, but you can use a stored procedure as well
        cmd.CommandText = "SELECT MyBlobColumn FROM MyTable WHERE Id = 1";

        // note the usage of SequentialAccess to lower memory consumption (read the docs for more)
        using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
        {
            if (reader.Read())
            {
                // again, we map the result to an SqlBytes instance
                var bytes = reader.GetSqlBytes(0); // column ordinal, here 1st column -> 0

                // I use a file stream, but that could be any stream (asp.net, memory, etc.)
                using (var file = File.OpenWrite(outputPath))
                {
                    bytes.Stream.CopyTo(file);
                }
            }
        }
    }
}

使用这些技术,我们从不分配任何 byte[] 或 MemoryStream 实例,只是在 SQL 或文件流中使用。

【讨论】:

【解决方案3】:

当数据存储在 varbinary(MAX) 列中时,您需要使用 SqlCommand 来检索数据,除非您使用 FileTable,它允许通过 UNC 路径访问内容,类似于存储在文件系统上的常规文件,但由 SQL Server 管理。

如果 blob 的大小可能很大,您当前使用的“块”技术将减少内存需求,但会以更冗长的代码为代价。对于合理大小的 blob 大小,您可以在不使用分块方法的情况下一次读取整个列内容。这是否可行取决于 blob 的大小和客户端可用内存。

var buffer = (byte[])cmd.ExecuteScalar();
fs.Write(buffer, 0, buffer.Length);

【讨论】:

  • 谢谢。我现在已经将缓冲区作为字节数组。例如,我现在如何在 documentViewer 对象中实际显示此文档?还是 rtfTextBox?
  • @bjjrolls,这取决于存储在 blob 中的内容。如果是 rtf 文档,您可以将 rtf 文本框控件的 RtfText 属性设置为转换为字符串的二进制值。我对 documentViewer 一无所知,因此无法对此发表评论。
  • 嗨@DanGuzman 希望你周末愉快。我试图弄清楚这一点。基本上我已经能够将某些文件类型保存到 SQL blob(即 .doc/.docx/pdf/jpeg...' 等),但现在需要能够通过单击按钮来显示它们。这对我的需求是否同样有效?我不知道在尝试打开时是否需要指定文件类型,这可能是个问题。谢谢
  • @bjjrolls,不确定我是否可以提供帮助,但您使用的是什么文档查看器?
  • @bjjrolls,你比较过原始文件和保存为文件的blob吗?它们的尺寸相同吗?我想,你可以计算这两个文件的 SHA1 并查看它们是否匹配,你会清楚地知道发生了什么。
猜你喜欢
  • 2017-05-28
  • 1970-01-01
  • 2012-07-16
  • 1970-01-01
  • 1970-01-01
  • 2021-06-24
  • 1970-01-01
  • 1970-01-01
  • 2014-10-02
相关资源
最近更新 更多