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

基于C# VS库存管理系统:SQL单表存储多商品发票方案咨询

嗨,我来帮你梳理下这个问题。首先得说,从数据库设计的范式规范来讲,通常发票主信息和发票明细(不同商品)是分开存主表和明细表的,但既然你明确要求用单个SQL表来存储,我给你两种可行的实现思路,同时也会提一下常规方案的好处,方便你权衡。

方案1:单表存储(利用JSON列)

假设你用的是SQL Server(毕竟和Visual Studio搭配最常用),它原生支持JSON数据类型的存储和查询,这是单表存储多商品明细最灵活的方式。

表结构设计

你可以创建这样的表:

CREATE TABLE Invoices (
    InvoiceId VARCHAR(20) PRIMARY KEY, -- 比如用"INV20240520001"这种格式的发票号
    InvoiceDate DATETIME NOT NULL,
    CustomerName NVARCHAR(100) NOT NULL,
    TotalAmount DECIMAL(18,2) NOT NULL,
    Items NVARCHAR(MAX) NOT NULL -- 存储商品明细的JSON字符串
)

商品明细的JSON格式示例

Items列里可以存类似这样的数组JSON,每个元素对应一个商品:

[
  {"ProductId":"P001", "ProductName":"无线鼠标", "Quantity":3, "UnitPrice":49.99, "SubTotal":149.97},
  {"ProductId":"P003", "ProductName":"机械键盘", "Quantity":1, "UnitPrice":299.00, "SubTotal":299.00}
]

C#代码处理

在C#里,你可以先定义一个商品明细的实体类:

public class InvoiceItem
{
    public string ProductId { get; set; }
    public string ProductName { get; set; }
    public int Quantity { get; set; }
    public decimal UnitPrice { get; set; }
    public decimal SubTotal { get; set; }
}

开单时,把商品列表序列化为JSON字符串再存入数据库;读取发票时,把JSON字符串反序列化为List<InvoiceItem>即可。用.NET自带的System.Text.Json就能搞定:

// 序列化商品列表为JSON
var invoiceItems = new List<InvoiceItem> { /* 你的商品数据 */ };
var itemsJson = JsonSerializer.Serialize(invoiceItems);

// 反序列化回商品列表
var retrievedItems = JsonSerializer.Deserialize<List<InvoiceItem>>(itemsJsonFromDb);

方案1的优缺点

  • 👍 优点:完全符合你“单个表”的要求,表结构简单,新增商品属性不用修改表结构,开发初期快速上手
  • 👎 缺点:查询单个商品的销售数据(比如统计某款鼠标卖了多少)需要用SQL的JSON函数解析,效率比结构化表低;无法通过外键关联商品主表(如果有的话),难以保证数据一致性;索引优化难度大
方案2:符合数据库范式的主从表设计(推荐长期项目使用)

虽然你明确要单表,但还是得提这个行业常规方案——如果你的系统未来有较多的统计查询需求(比如按商品、按客户统计销售),这种设计会更健壮。

表结构设计

  • 发票主表Invoices:存储发票的核心信息
CREATE TABLE Invoices (
    InvoiceId VARCHAR(20) PRIMARY KEY,
    InvoiceDate DATETIME NOT NULL,
    CustomerName NVARCHAR(100) NOT NULL,
    TotalAmount DECIMAL(18,2) NOT NULL
)
  • 发票明细表InvoiceItems:存储每一条商品明细,通过外键关联主表
CREATE TABLE InvoiceItems (
    InvoiceItemId INT IDENTITY(1,1) PRIMARY KEY,
    InvoiceId VARCHAR(20) NOT NULL FOREIGN KEY REFERENCES Invoices(InvoiceId),
    ProductId VARCHAR(20) NOT NULL,
    ProductName NVARCHAR(100) NOT NULL,
    Quantity INT NOT NULL,
    UnitPrice DECIMAL(18,2) NOT NULL,
    SubTotal DECIMAL(18,2) NOT NULL
)

C#代码处理

开单时,先插入主表拿到发票号,再批量插入明细表的商品记录,记得用事务保证操作的原子性(要么全成功,要么全回滚):

using (var transaction = dbContext.Database.BeginTransaction())
{
    try
    {
        // 插入发票主表
        var invoice = new Invoice { /* 主表数据 */ };
        dbContext.Invoices.Add(invoice);
        await dbContext.SaveChangesAsync();

        // 插入发票明细
        var items = new List<InvoiceItem> { /* 商品明细数据,每个Item的InvoiceId设为刚生成的发票号 */ };
        dbContext.InvoiceItems.AddRange(items);
        await dbContext.SaveChangesAsync();

        transaction.Commit();
    }
    catch (Exception ex)
    {
        transaction.Rollback();
        // 处理异常
    }
}

方案2的优缺点

  • 👍 优点:数据结构化清晰,查询统计效率高,能通过外键保证数据一致性,方便索引优化,长期维护成本低
  • 👎 缺点:需要操作两个表,开发初期多一点代码量

总结一下:如果只是小体量的系统,或者暂时没有复杂查询需求,方案1可以快速满足你的单表需求;如果是长期维护的项目,方案2的扩展性和可维护性会好很多。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 07:59:00