【问题标题】:File Corrupted when reading a BLOB object from an SQL Database从 SQL 数据库读取 BLOB 对象时文件损坏
【发布时间】:2018-07-05 16:02:17
【问题描述】:

大家,有人可以帮忙吗。

我在读取 SQL DB 中的 BLOB 对象时遇到问题。文件下载但由于文件损坏而无法打开。任何文档类型(pdf、docx、jpg 等)都会出现错误。

代码执行一个存储过程,它根据两个不同的参数获取二进制文件。

代码如下:

        protected void Page_Load(object sender, EventArgs e)
    {
        // Request.QueryString["docid"].ToString(); 
        //string DocumentID = Request.QueryString["DocumentID"].ToString();
        string DocumentID = "9163736c-8064-11e8-ab16-2c44fd826130";
        string SessionId = "91494483-8064-11e8-ab16-2c44fd826130";
        //Connection and Parameters
        SqlParameter param1 = null;
        SqlParameter param2 = null;
        SqlConnection conn = new SqlConnection(
           ConfigurationManager.ConnectionStrings["ProcessManagerConnectionString"].ToString());
        SqlCommand cmd = new SqlCommand("sp_getdoc", conn);
        cmd.CommandType = CommandType.StoredProcedure;
        param1 = new SqlParameter("@DocumentID", SqlDbType.NVarChar, 100);
        param2 = new SqlParameter("@SessionId", SqlDbType.NVarChar, 100);
        param1.Direction = ParameterDirection.Input;
        param2.Direction = ParameterDirection.Input;
        param1.Value = DocumentID;
        param2.Value = SessionId;
        cmd.Parameters.Add(param1);
        cmd.Parameters.Add(param2);
        //Open connection and fetch the data with reader
        conn.Open();
        SqlDataReader reader =
          cmd.ExecuteReader(CommandBehavior.CloseConnection);
        if (reader.HasRows)
        {
            reader.Read();
            //
            string doctype = reader["Extension"].ToString();
            string docname = reader["Docname"].ToString();
            //
            Response.Buffer = false;
            Response.ClearHeaders();
            Response.ContentType = doctype;
            Response.AddHeader("Content-Disposition",
                     "attachment; filename=" + docname);
            //
            //Code for streaming the object while writing
            const int ChunkSize = 1024;
            byte[] buffer = new byte[ChunkSize];
            byte[] binary = (reader["Data"]) as byte[];
            MemoryStream ms = new MemoryStream(binary);
            int SizeToWrite = ChunkSize;

            for (int i = 0; i < binary.GetUpperBound(0) - 1; i = i + ChunkSize)
            {
                if (!Response.IsClientConnected) return;
                if (i + ChunkSize >= binary.Length)
                    SizeToWrite = binary.Length - i;
                byte[] chunk = new byte[SizeToWrite];
                ms.Read(chunk, 0, SizeToWrite);
                Response.BinaryWrite(chunk);
                Response.Flush();
            }
            Response.Close();

        }

【问题讨论】:

    标签: c# asp.net webforms


    【解决方案1】:

    这里不需要做任何分块。您已经将二进制数据加载到内存中。只需拨打以下电话:

    Response.BinaryWrite(binary);
    

    避免创建MemoryStream 之类的。我感觉你的分块代码有错误。

    如果您希望在流式传输二进制数据时减少应用中的内存使用量,您应该考虑使用CommandBehavior.SequentialAccess

    最后,我更喜欢使用简单的 System.Web.IHttpHandler (ASHX) 而非 System.Web.UI.Page (ASPX) 来处理这类事情。

    尝试创建一个名为HectorsHandler.ashx 的文件,其内容如下:

    <%@ WebHandler Language="C#" Class="HectorsApp.HectorsHandler" %>
    
    using System;
    using System.Configuration;
    using System.Data;
    using System.Data.SqlClient;
    using System.Web;
    
    namespace HectorsApp
    {
        public class HectorsHandler : IHttpHandler
        {
            public void ProcessRequest(HttpContext ctxt)
            {
                // Request.QueryString["docid"].ToString(); 
                //string DocumentID = Request.QueryString["DocumentID"].ToString();
                string DocumentID = "9163736c-8064-11e8-ab16-2c44fd826130";
                string SessionId = "91494483-8064-11e8-ab16-2c44fd826130";
                //Connection and Parameters
                SqlParameter param1 = null;
                SqlParameter param2 = null;
                using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ProcessManagerConnectionString"].ToString()))
                {
                    SqlCommand cmd = new SqlCommand("sp_getdoc", conn);
                    cmd.CommandType = CommandType.StoredProcedure;
                    param1 = new SqlParameter("@DocumentID", SqlDbType.NVarChar, 100);
                    param2 = new SqlParameter("@SessionId", SqlDbType.NVarChar, 100);
                    param1.Direction = ParameterDirection.Input;
                    param2.Direction = ParameterDirection.Input;
                    param1.Value = DocumentID;
                    param2.Value = SessionId;
                    cmd.Parameters.Add(param1);
                    cmd.Parameters.Add(param2);
                    //Open connection and fetch the data with reader
                    conn.Open();
                    using (SqlDataReader reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess))
                    {
                        if (reader.Read())
                        {
                            //
                            string doctype = reader["Extension"].ToString();
                            string docname = reader["Docname"].ToString();
                            //
                            ctxt.Response.BufferOutput = false;
                            ctxt.Response.Buffer = false;
                            ctxt.Response.ContentType = doctype;
                            ctxt.Response.AddHeader("Content-Disposition", "attachment; filename=" + docname);
    
                            //Code for streaming the object while writing
                            byte[] buffer = new byte[8040];
                            long dataIndex = 0;
                            while (ctxt.Response.IsClientConnected)
                            {
                                long bytesRead = reader.GetBytes(reader.GetOrdinal("Data"), dataIndex, buffer, 0, buffer.Length);
                                if (bytesRead == 0)
                                {
                                    break;
                                }
                                ctxt.Response.OutputStream.Write(buffer, 0, (int)bytesRead);
                                ctxt.Response.OutputStream.Flush();
                                dataIndex += bytesRead;
                            }
                        }
                    }
                }
            }
    
            public bool IsReusable
            {
                get { return false; }
            }
        }
    }
    

    根据下面的 cmets,不再需要清除任何数据,因为您现在可以完全控制输出的内容。

    请注意,由于正在使用CommandBehavior.SequentialAccess,因此必须按顺序读取列。

    Is there any performance gain from CommandBehavior.SequentialAccess?

    【讨论】:

    • 嗨,达娜。我已经实现了您发布的代码,并且得到了相同的结果。它确实下载但文件已损坏并且无法打开。代码的一个问题是最后一行。 ApplicationInstance.CompleteRequest() 产生以下错误,我无法摆脱它:“名称'ApplicationInstance'在当前上下文中不存在”谢谢
    • 不确定为什么会出现该错误,但您可以改用 Context.ApplicationInstance.CompleteRequest()。您确定文件在上传过程中没有损坏吗?如果你在文本编辑器中打开下载,你能看到底部有多余的字符吗?
    • 嗨,达娜。语境。处理了错误。我正在组装的网络应用程序将取代许可证外的票务系统。因此,使用即将消失的界面可以很好地下载相同的文件,因此在上传过程中不会损坏。使用我或您建议的代码下载文件时,如果在文本编辑器中打开,您会看到各种字符和包含的文件名。唯一奇怪的是损坏的文件比工作文件小几 kb。下降过程中的一些压缩问题?示例:工作 PDF 文件 = 789 KB。损坏的 PDF 文件 = 611 KB
    • Dana,在我写完上面的内容后,我继续将文件重命名为 xxx.ZIP,双击它就可以了。所以文件在源头被压缩。然后我需要重命名文件 .zip 或在下载时解压缩...有什么想法吗?非常感谢您的帮助
    • Dana,暂时将 .zip 添加到下载文件时,您的代码可以正常工作。我会看看我的盒子上启用了哪种压缩软件。 Response.AddHeader("Content-Disposition", "attachment; filename=" + docname + ".zip");感谢您提供答案
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-08-20
    • 2013-12-11
    • 1970-01-01
    • 1970-01-01
    • 2011-01-22
    • 2017-01-24
    • 2017-07-16
    相关资源
    最近更新 更多