You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于EF6.4向Oracle LONG RAW分段插入大二进制数据求助

分段插入大二进制数据到Oracle LONG RAW的解决方案

问题根源

直接通过OracleDbType.LongRaw参数传递大文件字节数组会触发Oracle的参数长度限制(默认单LongRaw参数最大支持32KB左右),导致报错。必须通过分块追加的方式逐步写入LONG RAW列,避免一次性传递过大数据。


一、Oracle存储过程实现(分块追加逻辑)

创建支持初始化记录、分块追加的存储过程,使用UTL_RAW.APPEND安全拼接LONG RAW数据:

CREATE OR REPLACE PROCEDURE PROC_DOCUMENT_UPLOAD(
    P_DOC_ID OUT NUMBER,          -- 新增时返回文档ID,更新时传入
    P_DATA_TYPE IN VARCHAR2,      -- 文档类型(如'.pdf')
    P_DATA_BLOCK IN RAW,          -- 二进制数据块(建议每块≤1900字节,留冗余)
    P_IS_LAST_BLOCK IN NUMBER,    -- 是否为最后一块(1=是,0=否)
    P_DOC_ID_IN IN NUMBER DEFAULT NULL -- 更新时传入的现有文档ID
) AS
    V_EXIST_FLAG NUMBER;
BEGIN
    -- 更新场景:检查文档是否存在
    IF P_DOC_ID_IN IS NOT NULL THEN
        SELECT COUNT(1) INTO V_EXIST_FLAG FROM YOUR_DOC_TABLE WHERE DOC_ID = P_DOC_ID_IN;
        IF V_EXIST_FLAG = 0 THEN
            RAISE_APPLICATION_ERROR(-20001, '目标文档不存在');
        END IF;
        P_DOC_ID := P_DOC_ID_IN;
        -- 追加数据块到LONG RAW列
        UPDATE YOUR_DOC_TABLE 
        SET LONG_RAW_COL = UTL_RAW.APPEND(LONG_RAW_COL, P_DATA_BLOCK)
        WHERE DOC_ID = P_DOC_ID;
    -- 新增场景:初始化空记录
    ELSE
        INSERT INTO YOUR_DOC_TABLE(DATA_TYPE, LONG_RAW_COL)
        VALUES(P_DATA_TYPE, P_DATA_BLOCK)
        RETURNING DOC_ID INTO P_DOC_ID;
    END IF;
    
    -- 最后一块提交事务(可选,根据你的事务管理调整)
    IF P_IS_LAST_BLOCK = 1 THEN
        COMMIT;
    END IF;
EXCEPTION
    WHEN OTHERS THEN
        ROLLBACK;
        RAISE;
END;
/

存储过程说明

  • 支持新增和更新两种场景:新增时生成文档ID,更新时传入现有ID
  • 每块数据用RAW类型传递,避免LongRaw参数的长度限制
  • 使用UTL_RAW.APPEND确保二进制数据拼接无编码损失

二、EF6.4 端C#调用代码

读取文件并分块调用存储过程,每块大小建议设为1900字节(Oracle RAW类型最大支持2000字节,留100字节冗余):

public int UploadLargeDocument(string filePath, string dataType)
{
    byte[] fileBytes = File.ReadAllBytes(filePath);
    const int blockSize = 1900; // 每块字节数
    int totalBlocks = (int)Math.Ceiling((double)fileBytes.Length / blockSize);
    int docId = 0;

    using (var dbContext = new YourDbContext())
    {
        // 初始化第一块(新增记录)
        byte[] firstBlock = fileBytes.Take(blockSize).ToArray();
        var firstParams = new[]
        {
            new StoredProcedureParameter { Name = "P_DOC_ID", Direction = ParameterDirection.Output, InputType = OracleDbType.Int32 },
            new StoredProcedureParameter { Name = "P_DATA_TYPE", Value = dataType, InputType = OracleDbType.Varchar2 },
            new StoredProcedureParameter { Name = "P_DATA_BLOCK", Value = firstBlock, InputType = OracleDbType.Raw },
            new StoredProcedureParameter { Name = "P_IS_LAST_BLOCK", Value = totalBlocks == 1 ? 1 : 0, InputType = OracleDbType.Int32 },
            new StoredProcedureParameter { Name = "P_DOC_ID_IN", Value = DBNull.Value, InputType = OracleDbType.Int32 }
        };
        dbContext.ExecuteStoredProcedure("PROC_DOCUMENT_UPLOAD", firstParams);
        docId = (int)firstParams[0].Value;

        // 处理剩余块
        for (int i = 1; i < totalBlocks; i++)
        {
            int startIndex = i * blockSize;
            int currentBlockSize = Math.Min(blockSize, fileBytes.Length - startIndex);
            byte[] currentBlock = new byte[currentBlockSize];
            Array.Copy(fileBytes, startIndex, currentBlock, 0, currentBlockSize);

            var blockParams = new[]
            {
                new StoredProcedureParameter { Name = "P_DOC_ID", Direction = ParameterDirection.Output, InputType = OracleDbType.Int32 },
                new StoredProcedureParameter { Name = "P_DATA_TYPE", Value = dataType, InputType = OracleDbType.Varchar2 },
                new StoredProcedureParameter { Name = "P_DATA_BLOCK", Value = currentBlock, InputType = OracleDbType.Raw },
                new StoredProcedureParameter { Name = "P_IS_LAST_BLOCK", Value = i == totalBlocks - 1 ? 1 : 0, InputType = OracleDbType.Int32 },
                new StoredProcedureParameter { Name = "P_DOC_ID_IN", Value = docId, InputType = OracleDbType.Int32 }
            };
            dbContext.ExecuteStoredProcedure("PROC_DOCUMENT_UPLOAD", blockParams);
        }
    }
    return docId;
}

代码说明

  • 替换YourDbContext和YOUR_DOC_TABLE为实际的上下文类和表名
  • 分块逻辑确保每块数据不超过Oracle RAW参数的限制
  • 第一块初始化记录,后续块通过文档ID追加数据
  • 最后一块标记提交事务(可根据你的EF事务策略调整)

关于CLOB的替代方案

不建议用CLOB存储二进制数据:CLOB是字符型容器,存储二进制会涉及编码转换,极易导致数据损坏。直接用RAW分块+UTL_RAW.APPEND的方式更安全,完全匹配LONG RAW的二进制存储特性。

内容的提问来源于stack exchange,提问作者Enthusiastic

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 13:51:23