【问题标题】:Adding a new child entity to database also inserts a new empty parent entity向数据库添加新的子实体也会插入一个新的空父实体
【发布时间】:2026-01-20 04:55:02
【问题描述】:

我最近将我的项目从 ASP.Net Core 2.1 更新到 2.2(并遵循 Microsoft 的迁移指南),在更新了一些用于向数据库添加子对象的 razor 页面之后,还开始将新的空父对象添加到同时数据库。奇怪的是,这种行为在所有子父实体中并不一致,并且一些 razor 页面继续正常工作。

我挖掘了以下与我的问题有些相关的链接,但没有提供解决方案:

这个github issue 准确地描述了发生在我身上的事情,但我没有使用自定义 ModelBinder。

这里是 Create.cshtml:

<h2>Create</h2>

<h4>Branch</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <div class="form-group">
                <label asp-for="Branch.BranchName" class="control-label"></label>
                <input asp-for="Branch.BranchName" class="form-control" />
                <span asp-validation-for="Branch.BranchName" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Branch.Name" class="control-label"></label>
                <input asp-for="Branch.Name" class="form-control" />
                <span asp-validation-for="Branch.Name" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Branch.Address" class="control-label"></label>
                <input asp-for="Branch.Address" class="form-control" />
                <span asp-validation-for="Branch.Address" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Branch.City" class="control-label"></label>
                <input asp-for="Branch.City" class="form-control" />
                <span asp-validation-for="Branch.City" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Branch.Province" class="control-label"></label>
                <input asp-for="Branch.Province" class="form-control" />
                <span asp-validation-for="Branch.Province" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Branch.Country" class="control-label"></label>
                <input asp-for="Branch.Country" class="form-control" />
                <span asp-validation-for="Branch.Country" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Branch.PostalCode" class="control-label"></label>
                <input asp-for="Branch.PostalCode" class="form-control" />
                <span asp-validation-for="Branch.PostalCode" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Branch.Status" class="control-label"></label>
                <input asp-for="Branch.Status" class="form-control" />
                <span asp-validation-for="Branch.Status" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Branch.Phone" class="control-label"></label>
                <input asp-for="Branch.Phone" class="form-control" />
                <span asp-validation-for="Branch.Phone" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Branch.Fax" class="control-label"></label>
                <input asp-for="Branch.Fax" class="form-control" />
                <span asp-validation-for="Branch.Fax" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Branch.Comments" class="control-label"></label>
                <textarea asp-for="Branch.Comments" class="form-control"></textarea>
                <span asp-validation-for="Branch.Comments" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Branch.DealerId" class="control-label"></label>
                <select asp-for="Branch.DealerId" class ="form-control" asp-items="ViewBag.DealerId"></select>
            </div>
            <div class="form-group">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </form>
    </div>
</div>

这里是 Create.cshtml.cs:

public class CreateModel : PageModel
    {
        private readonly ApplicationDbContext _context;

        public CreateModel(ApplicationDbContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            Branch = new Branch();
            //Usually the DealerId would be passed in the URL, this is just for testing
            ViewData["DealerId"] = new SelectList(_context.Dealer, "DealerId", "DealerId");
            return Page();
        }

        //This is weird for debugging purposes
        private Branch _branch;

        [BindProperty]
        public Branch Branch {
            get
            {
                return _branch;
            }
            set
            {
                var t = value;
                _branch = value;
            }}

        public async Task<IActionResult> OnPostAsync()
        {
            if (!ModelState.IsValid)
            {
                return Page();
            }

            _context.Branches.Add(Branch);
            await _context.SaveChangesAsync();

            return RedirectToPage("./Index");
        }
    }

Branch.cs:

public class Branch
    {
        [Key]
        public string BranchId { get; set; }

        [Display(Name = "Branch Name")]
        public string BranchName { get; set; }

        public string Name { get; set; }
        public string Address { get; set; }
        public string City { get; set; }
        public string Province { get; set; }
        public string Country { get; set; }

        [Display(Name = "Postal Code")]
        [DataType(DataType.PostalCode)]
        public string PostalCode { get; set; }


        public string Status { get; set; }

        [DataType(DataType.PhoneNumber)]
        public string Phone { get; set; }

        [DataType(DataType.PhoneNumber)]
        public string Fax { get; set; }

        [DataType(DataType.MultilineText)]
        public string Comments { get; set; }


        public string DealerId { get; set; }

        [ForeignKey("DealerId")]
        public Dealer Dealer { get; set; }

        public List<ApplicationUser> Users { get; set; }
    }

当页面回发时,Branch 对象应该有一个空 Dealer 属性(在 ASP.Net Core 2.1 中也是如此),但有一个新实例化的 Dealer 对象(带有空字段)here's a screenshot of the breakpoint on the Branch setter。当分支对象被添加到数据库时,它应该使用 DealerId 来查找相关对象并将它们链接起来(在这些类/表之间建立了适当的功能性 FK 关系),但是因为 Dealer 导航属性不为空,Branch 和 Empty Dealer 都被添加到数据库中,并在它们之间建立了 FK 关系。这可以通过在将分支添加到数据库 _context 之前将 Dealer 属性设置为 null 来解决,但这显然不是预期的行为,对于具有类似关系的其他几个数据库实体来说,这样做不是必需的。

我一直在努力找出这个错误的根源。我什至创建了一个新的 ASP.Net Core 项目,复制了必要的模型,并从中构建了一个新的数据库,结果却遇到了同样的问题。

据我所知,这是 ModelBinder 无法很好地处理这些类和其他相关类的问题。 Binder 应该在回发时将 Dealer 属性设置为 null,但由于某种原因,它改为调用默认构造函数(然后为 Dealer 的父类调用默认构造函数)。

【问题讨论】:

  • 查看您的 Branch 类定义会很有帮助。
  • @ChrisPratt 添加了分支代码

标签: c# sql-server asp.net-core asp.net-core-2.2


【解决方案1】:

经过大量搜索,我发现此行为是由与模型绑定处理具有 IFormFile 属性的实体的方式相关的问题引起的。

见:

在我的例子中,Dealer 类有一个IFormFile 属性,这导致模型绑定器调用Dealer 的默认构造函数,从而导致Branch.Dealer 属性填充有默认Dealer 对象,而不是保持为空。根据https://github.com/aspnet/AspNetCore/issues/8917,此问题已在ASP.NET Core 3.0 中修复。

【讨论】: