多表增删改查示例
本章介绍学习多张表增、删、改、查功能如何实现,下面以销货出库单作为示例,该业务栏位如下:
销货出库单栏位
销货出库单明细栏位
- 商品编码、商品名称、规格型号、数量、单位、单价、金额
该示例适用于出货明细数量较小情况,单据表头和表体组合查询和提交。
对于出货明细数量较大的情况,建议表头与表体分开查询和提交,表体采用分页查询。
1. 前后端共用
1.1. 创建实体类
- 在KIMS项目Entities文件夹下创建KmBill和KmBillList实体类
- 该类继承EntityBase类
- 属性使用Column特性描述,用于生成页面字段和数据校验
| |
| public class KmBill : EntityBase |
| { |
| [Column("销货单号", "", true, "1", "50")] |
| public string? BillNo { get; set; } |
| ...... |
| [Column("客户", "", true, "1", "50")] |
| public string? BillDate { get; set; } |
| [Column("备注", "", false)] |
| public string? Note { get; set; } |
| |
| |
| |
| public virtual List? Lists { get; set; } |
| } |
| |
| public class KmBillList : EntityBase |
| { |
| [Column("销货单ID", "", true, "1", "50")] |
| public string? HeadId { get; set; } |
| [Column("商品编码", "", true, "1", "50")] |
| public string? Code { get; set; } |
| [Column("数量", "", false)] |
| public decimal? Qty { get; set; } |
| [Column("单价", "", false)] |
| public decimal? Price { get; set; } |
| [Column("金额", "", false)] |
| public decimal? Amount { get; set; } |
| |
| |
| public virtual string? Name { get; set; } |
| public virtual string? Model { get; set; } |
| public virtual string? Unit { get; set; } |
| } |
1.2. 创建Client类
- 在KIMS项目Clients文件夹下创建BillClient类
- 该类是前后端数据交互接口,继承ClientBase类
- 该类只需提供分页查询、删除和保存,导入功能由框架统一异步处理
| public class BillClient : ClientBase |
| { |
| public BillClient(Context context) : base(context) { } |
| |
| public Task> QueryBillsAsync(PagingCriteria criteria) => Context.QueryAsync("Bill/QueryBills", criteria); |
| public Task DeleteBillsAsync(List models) => Context.PostAsync("Bill/DeleteBills", models); |
| public Task GetBillAsync(string id) => Context.GetAsync($"Bill/GetBill?id={id}"); |
| public Task SaveBillAsync(KmBill model) => Context.PostAsync("Bill/SaveBill", model); |
| } |
2. 前端
2.1. 创建List页面
- 在KIMS.Razor项目BillData文件夹下创建BillList类
- 该类是数据列表页面,继承WebGridView类
- 列表页面按钮和栏位在框架模块管理中配置
| class BillList : WebGridView<KmBill, BillForm> |
| { |
| protected override Task InitPageAsync() |
| { |
| |
| |
| Column(c => c.BillNo).Template((b, r) => b.Link(r.BillNo, Callback(() => View(r)))); |
| Column(c => c.Status).Template(BillStatusCell); |
| return base.InitPageAsync(); |
| } |
| |
| |
| protected override Task> OnQueryData(PagingCriteria criteria) |
| { |
| return Client.Bill.QueryBillsAsync(criteria); |
| } |
| |
| public void New() => ShowForm(); |
| public void DeleteM() => DeleteRows(Client.Bill.DeleteBillsAsync); |
| public void Edit(KmBill row) => ShowForm(row); |
| public void Delete(KmBill row) => DeleteRow(row, Client.Bill.DeleteBillsAsync); |
| } |
- 在KIMS.Razor项目BillData\Forms文件夹下创建BillForm类
- 该类是数据编辑和查看明细页面,继承WebForm类
| [Dialog(980, 580)] |
| class BillForm : WebForm<KmBill> |
| { |
| private KmBill? head; |
| |
| protected override async Task InitFormAsync() |
| { |
| var model = TModel; |
| head = await Client.Bill.GetBillAsync(model.Id); |
| } |
| |
| protected override void BuildFields(FieldBuilder builder) |
| { |
| builder.Hidden(f => f.Id); |
| builder.Table(table => |
| { |
| table.ColGroup(11, 25, 11, 25, 11, 17); |
| table.Tr(attr => |
| { |
| |
| table.Field(f => f.BillNo).Enabled(false).Build(); |
| table.Field(f => f.BillDate).Build(); |
| table.Field(f => f.Status).Enabled(false).Build(); |
| }); |
| table.Tr(attr => table.Field |
| builder.FormList("商品明细", "", attr => |
| { |
| attr.Set(c => c.ReadOnly, ReadOnly) |
| .Set(c => c.Data, head?.Lists); |
| }); |
| }); |
| } |
| |
| protected override void BuildButtons(RenderTreeBuilder builder) |
| { |
| builder.Button(FormButton.Save, Callback(OnSave), !ReadOnly); |
| base.BuildButtons(builder); |
| } |
| |
| private void OnSave() |
| { |
| SubmitAsync(data => |
| { |
| head?.FillModel(data); |
| return Client.Bill.SaveBillAsync(head); |
| }); |
| } |
| } |
2.3. 创建表体页面
- 在KIMS.Razor项目BillData\Forms文件夹下创建BillListGrid类
- 该类是表体数据编辑表格,继承EditGrid类
| |
| class BillListGrid : EditGrid<KmBillList> |
| { |
| public BillListGrid() |
| { |
| OrderBy = nameof(KmBillList.ItemNo); |
| Name = "商品明细"; |
| } |
| |
| protected override Task OnInitializedAsync() |
| { |
| |
| var builder = new ColumnBuilder(); |
| |
| builder.Field(r => r.Code).Edit(new GoodsStock(), OnCodeChanged); |
| builder.Field(r => r.Name); |
| builder.Field(r => r.Model); |
| builder.Field(r => r.Qty).Edit(OnQtyChanged); |
| builder.Field(r => r.Unit); |
| builder.Field(r => r.Price).Edit(OnPriceChanged); |
| builder.Field(r => r.Amount).IsSum().Edit(OnAmountChanged); |
| builder.Field(r => r.Note).Edit(); |
| Columns = builder.ToColumns(); |
| return base.OnInitializedAsync(); |
| } |
| |
| private void OnCodeChanged(KmBillList row, object value) |
| { |
| var g = value as StockInfo; |
| row.Type = g?.Type; |
| row.Code = g?.Code; |
| row.Name = g?.Name; |
| row.Model = g?.Model; |
| row.Unit = g?.Unit; |
| } |
| |
| private void OnQtyChanged(KmBillList row, object value) |
| { |
| var qty = Utils.ConvertTo<decimal>(value); |
| row.Amount = Utils.Round(qty * (row.Price ?? 0), 2); |
| } |
| |
| private void OnPriceChanged(KmBillList row, object value) |
| { |
| var price = Utils.ConvertTo<decimal>(value); |
| row.Amount = Utils.Round(price * row.Qty, 2); |
| } |
| |
| private void OnAmountChanged(KmBillList row, object value) |
| { |
| var amount = Utils.ConvertTo<decimal>(value); |
| row.Price = row.Qty == 0 ? 0 : Utils.Round(amount / row.Qty, 2); |
| } |
| } |
3. 后端
3.1. 创建Controller类
- 在KIMS.Core项目Controllers文件夹下创建BillController类
- 该类为服务端WebApi,继承BaseController类
| [Route("[controller]")] |
| public class BillController : BaseController |
| { |
| private BillService Service => new(Context); |
| |
| [HttpPost("[action]")] |
| public PagingResult QueryBills([FromBody] PagingCriteria criteria) => Service.QueryBills(criteria); |
| |
| [HttpPost("[action]")] |
| public Result DeleteBills([FromBody] List models) => Service.DeleteBills(models); |
| |
| [HttpGet("[action]")] |
| public KmBill GetBill([FromQuery] string id) => Service.GetBill(id); |
| |
| [HttpPost("[action]")] |
| public Result SaveBill([FromBody] KmBill model) => Service.SaveBill(model); |
| } |
3.2. 创建Service类
- 在KIMS.Core项目Services文件夹下创建BillService类
- 该类为业务逻辑服务类,继承ServiceBase类
| class BillService : ServiceBase |
| { |
| internal BillService(Context context) : base(context) { } |
| |
| internal PagingResult QueryBills(PagingCriteria criteria) |
| { |
| return BillRepository.QueryBills(Database, criteria); |
| } |
| |
| internal Result DeleteBills(List models) |
| { |
| if (models == null || models.Count == 0) |
| return Result.Error(Language.SelectOneAtLeast); |
| |
| |
| return Database.Transaction(Language.Delete, db => |
| { |
| foreach (var item in models) |
| { |
| |
| BillRepository.DeleteBillLists(db, item.Id); |
| db.Delete(item); |
| } |
| }); |
| } |
| |
| internal KmBill GetBill(string id) |
| { |
| if (string.IsNullOrEmpty(id)) |
| return GetDefaultBill(); |
| |
| var entity = Database.QueryById(id); |
| if (entity == null) |
| entity = GetDefaultBill(); |
| else |
| entity.Lists = BillRepository.GetBillLists(Database, id); |
| return entity; |
| } |
| |
| internal Result SaveBill(KmBill model) |
| { |
| if (model == null) |
| return Result.Error("不能提交空数据!"); |
| |
| var vr = model.Validate(); |
| if (!vr.IsValid) |
| return vr; |
| |
| return Database.Transaction(Language.Save, db => |
| { |
| if (model.IsNew) |
| model.BillNo = GetBillMaxNo(db); |
| |
| |
| model.TotalAmount = 0; |
| model.GoodsName = string.Empty; |
| |
| BillRepository.DeleteBillLists(db, model.Id); |
| if (model.Lists != null && model.Lists.Count > 0) |
| { |
| var index = 0; |
| var lists = new List(); |
| foreach (var item in model.Lists) |
| { |
| item.HeadId = model.Id; |
| item.ItemNo = ++index; |
| db.Insert(item); |
| lists.Add(item); |
| } |
| |
| model.TotalAmount = lists.Sum(l => l.Amount); |
| model.GoodsName = string.Join(",", lists.Select(l => l.Name)); |
| } |
| db.Save(model); |
| }, model); |
| } |
| |
| private KmBill GetDefaultBill() |
| { |
| return new KmBill |
| { |
| BillNo = GetBillMaxNo(Database), |
| BillDate = DateTime.Now, |
| Status = "暂存", |
| Lists = new List() |
| }; |
| } |
| |
| private static string GetBillMaxNo(Database db) |
| { |
| var prefix = $"S{DateTime.Now:yyyy}"; |
| var maxNo = BillRepository.GetBillMaxNo(db, prefix); |
| if (string.IsNullOrWhiteSpace(maxNo)) |
| maxNo = $"{prefix}00000"; |
| return GetMaxFormNo(prefix, maxNo); |
| } |
| } |
3.3. 创建Repository类
- 在KIMS.Core项目Repositories文件夹下创建BillRepository类
- 该类为数据访问类
| class BillRepository |
| { |
| |
| |
| internal static PagingResult QueryBills(Database db, PagingCriteria criteria) |
| { |
| var sql = "select * from KmBill where CompNo=@CompNo"; |
| return db.QueryPage(sql, criteria); |
| } |
| |
| internal static string GetBillMaxNo(Database db, string prefix) |
| { |
| var sql = $"select max(BillNo) from KmBill where CompNo=@CompNo and BillNo like '{prefix}%'"; |
| return db.Scalar<string>(sql, new { db.User.CompNo }); |
| } |
| |
| |
| internal static List GetBillLists(Database db, string headId) |
| { |
| |
| var sql = "select a.*,b.Name,b.Model,b.Unit from KmBillList a,KmGoods b where a.Code=b.Code and HeadId=@headId"; |
| return db.QueryList(sql, new { headId }); |
| } |
| |
| internal static void DeleteBillLists(Database db, string headId) |
| { |
| var sql = "delete from KmBillList where HeadId=@headId"; |
| db.Execute(sql, new { headId }); |
| } |
| } |
4. 运行测试

