如何借助MVC、JS分片及Entity Framework在SQL存储超5GB大视频文件
没问题,我帮你把这个分片上传方案从存文件系统改成用Entity Framework存入SQL数据库。下面是具体的修改步骤和关键细节,你可以一步步来调整:
1. 设计数据库实体模型
首先需要创建两个实体类,分别存储文件的元数据(比如文件名、总大小、分片数)和每个分片的数据。这样既方便管理分片,也能支持断点续传:
public class FileMetadata { public int Id { get; set; } public string FileName { get; set; } public long TotalSize { get; set; } public int TotalChunks { get; set; } public bool IsUploadComplete { get; set; } // 关联所有分片 public ICollection<FileChunk> Chunks { get; set; } = new List<FileChunk>(); } public class FileChunk { public int Id { get; set; } public int FileMetadataId { get; set; } public int ChunkIndex { get; set; } // 分片的顺序索引,从0开始 public byte[] ChunkData { get; set; } // 分片的二进制数据 // 关联元数据 public FileMetadata FileMetadata { get; set; } }
接着在你的DbContext里添加对应的DbSet:
public class YourDbContext : DbContext { public DbSet<FileMetadata> FileMetadatas { get; set; } public DbSet<FileChunk> FileChunks { get; set; } // 其他DbContext配置... }
2. 修改MVC控制器的分片接收逻辑
原来的控制器是把分片写入文件系统,现在改成存入数据库。这里需要处理首次上传(创建元数据)和后续分片上传(关联已有元数据),还要检查是否所有分片都已上传完成:
[HttpPost] public async Task<IActionResult> UploadChunk() { // 从FormData中获取分片和参数 var chunkFile = Request.Form.Files["chunk"]; var fileId = int.TryParse(Request.Form["fileId"], out var id) ? id : 0; var chunkIndex = int.Parse(Request.Form["chunkIndex"]); var totalChunks = int.Parse(Request.Form["totalChunks"]); var fileName = Request.Form["fileName"]; var totalSize = long.Parse(Request.Form["totalSize"]); using var context = new YourDbContext(); FileMetadata fileMetadata = null; if (fileId == 0) { // 首次上传,创建文件元数据 fileMetadata = new FileMetadata { FileName = fileName, TotalSize = totalSize, TotalChunks = totalChunks, IsUploadComplete = false }; context.FileMetadatas.Add(fileMetadata); await context.SaveChangesAsync(); } else { // 续传,获取已有的元数据 fileMetadata = await context.FileMetadatas.FindAsync(fileId); if (fileMetadata == null) { return BadRequest("无效的文件ID"); } } // 读取分片二进制数据 using var ms = new MemoryStream(); await chunkFile.CopyToAsync(ms); var chunkData = ms.ToArray(); // 检查该分片是否已上传(避免重复存储) var existingChunk = await context.FileChunks .FirstOrDefaultAsync(c => c.FileMetadataId == fileMetadata.Id && c.ChunkIndex == chunkIndex); if (existingChunk != null) { return Json(new { success = true, fileId = fileMetadata.Id }); } // 存入分片数据 var newChunk = new FileChunk { FileMetadataId = fileMetadata.Id, ChunkIndex = chunkIndex, ChunkData = chunkData }; context.FileChunks.Add(newChunk); await context.SaveChangesAsync(); // 检查是否所有分片都已上传完成 var uploadedChunkCount = await context.FileChunks .CountAsync(c => c.FileMetadataId == fileMetadata.Id); if (uploadedChunkCount == totalChunks) { fileMetadata.IsUploadComplete = true; context.FileMetadatas.Update(fileMetadata); await context.SaveChangesAsync(); } return Json(new { success = true, fileId = fileMetadata.Id }); }
3. 关键注意事项
- 数据库存储限制:SQL Server的
VARBINARY(MAX)类型最大只能存储2GB数据,所以你的分片大小必须设置在2GB以内(比如每个分片1GB),这样单个分片才能存入数据库。如果要直接存储完整的5GB文件,需要启用SQL Server的FILESTREAM功能,这时候可以把ChunkData字段的类型改成varbinary(max) FILESTREAM,并在数据库中配置FILESTREAM容器。 - 性能优化:对于大分片,直接用EF的
Add方法可能会有内存开销,你可以改用ADO.NET直接写入数据库,减少EF的封装 overhead:// 替换EF的Add和SaveChanges部分 using var conn = context.Database.GetDbConnection(); await conn.OpenAsync(); using var cmd = conn.CreateCommand(); cmd.CommandText = @"INSERT INTO FileChunks (FileMetadataId, ChunkIndex, ChunkData) VALUES (@FileId, @ChunkIndex, @ChunkData)"; cmd.Parameters.Add(new SqlParameter("@FileId", fileMetadata.Id)); cmd.Parameters.Add(new SqlParameter("@ChunkIndex", chunkIndex)); cmd.Parameters.Add(new SqlParameter("@ChunkData", chunkData)); await cmd.ExecuteNonQueryAsync(); - 断点续传:前端JS逻辑要保留分片索引和文件ID的传递,后端通过检查
FileChunk表中是否存在对应索引的分片,来跳过已上传的部分,实现断点续传。
4. 文件读取的实现
当需要从数据库下载完整文件时,需要把所有分片按索引顺序合并,再返回给客户端:
[HttpGet] public async Task<IActionResult> DownloadFile(int fileId) { using var context = new YourDbContext(); var fileMetadata = await context.FileMetadatas .Include(f => f.Chunks) .FirstOrDefaultAsync(f => f.Id == fileId && f.IsUploadComplete); if (fileMetadata == null) { return NotFound("文件不存在或未上传完成"); } // 按分片索引排序 var sortedChunks = fileMetadata.Chunks.OrderBy(c => c.ChunkIndex).ToList(); // 合并所有分片到内存流 using var ms = new MemoryStream(); foreach (var chunk in sortedChunks) { await ms.WriteAsync(chunk.ChunkData, 0, chunk.ChunkData.Length); } ms.Position = 0; // 返回文件 return File(ms, "application/octet-stream", fileMetadata.FileName); }
内容的提问来源于stack exchange,提问作者Jon Smith
相关产品推荐
相关产品推荐

