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

如何借助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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 12:06:21