基于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
相关产品推荐
相关产品推荐

