微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Razor Pages using EF Core Code First - 使用部分视图更新相关实体

如何解决Razor Pages using EF Core Code First - 使用部分视图更新相关实体

我想以此作为序言,我对 Razor 页面和 EF 还很陌生,事实上这是我第一个使用这两者的主要项目,我已经完成了 Microsoft Docs 上的所有教程,并阅读了尽我所能,所以我将开始......

我已经搜索了很多关于如何执行此操作的正确解决方案,但似乎找不到我要找的东西(可能是因为我一直在寻找错误的东西?!!)。我有以下实体:

    public class Country
    {
        public int ID { get; set; }
        [required]
        public string Name { get; set; }
        public int displayOrder { get; set; }

        public virtual ICollection<Address> Addresses { get; set; }
    }

    [Index(nameof(NameOrNumber),nameof(PostCode),IsUnique = true)]
    public class Address
    {
        public int ID { get; set; }
        [required]
        public string NameOrNumber { get; set; }
        [required]
        public string Street { get; set; }
        public string AddressLine2 { get; set; }
        public string AddressLine3 { get; set; }
        [required]
        public string City { get; set; }
        public string County { get; set; }
        [required]
        public string PostCode { get; set; }
        [required]
        public int CountryID { get; set; }
    }

    public class Site
    {
        public int ID { get; set; }
        [required]
        public string Name { get; set; }
        [required]
        public int? AddressID { get; set; }
        public string MainPhone { get; set; }
        public bool isDeleted { get; set; }

        public virtual Address Address { get; set; }
        public virtual ICollection<Building> Buildings { get; set; }
    }

还有一个建筑实体,它有一个地址(链接到地址实体,它可能与站点的地址(和 ID)相同也可能不同。还有其他实体使用地址并可能共享地址。因此,为什么地址是一个单独的实体,而不仅仅是站点的一部分(我没有显示建筑物,因为它不直接相关)。

本质上,我创建了地址的部分视图,我将其用作地址的创建/更新的一部分,因此在创建站点时,地址是新输入的,但是当我来到一栋楼,可能地址是一样的,所以只能二选一了。

这是地址 (_Address.cshtml) 的部分内容

@model MyProject.Models.Address
@{
    SelectList CountryNames = (SelectList)ViewData["CountryNamesSL"];
}

<hr />
<div class="form-group">
    <h4>Address</h4>
    <div class="form-group">
        <label asp-for="NameOrNumber" class="control-label"></label>
        <input asp-for="NameOrNumber" class="form-control" />
        <span asp-validation-for="NameOrNumber" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Street" class="control-label"></label>
        <input asp-for="Street" class="form-control" />
        <span asp-validation-for="Street" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="AddressLine2" class="control-label"></label>
        <input asp-for="AddressLine2" class="form-control" />
        <span asp-validation-for="AddressLine2" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="AddressLine3" class="control-label"></label>
        <input asp-for="AddressLine3" class="form-control" />
        <span asp-validation-for="AddressLine3" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="City" class="control-label"></label>
        <input asp-for="City" class="form-control" />
        <span asp-validation-for="City" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="County" class="control-label"></label>
        <input asp-for="County" class="form-control" />
        <span asp-validation-for="County" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="PostCode" class="control-label"></label>
        <input asp-for="PostCode" class="form-control" />
        <span asp-validation-for="PostCode" class="text-danger"></span>
    </div>
    <div class="form-group">
        <label asp-for="Country" class="control-label"></label>
        <select asp-for="ID" class="form-control" asp-items="@CountryNames" name="CountryID"></select>
        @*<partial name="_Country" model="@Model.Country" />*@
        <span asp-validation-for="Country" class="text-danger"></span>
    </div>
</div>
<hr />

所以在 Create.cshtmlSite 中,我引用了部分视图,如下所示:

\\Other form elements removed for brevity

<partial name="_Address" model="@Model.Site.Address" />

\\rest of form + submit button

以及使用相关地址 (Create.cshtml.cs) 创建网站的代码

    public class CreateModel : SelectListsPageModel //NOTE: The parent class provides methods for populating the country and region dropdowns
    {
        private readonly MyProject.Data.MyProjectContext _context;

        public CreateModel(MyProject.Data.MyProjectContext context)
        {
            _context = context;
        }

        public IActionResult OnGet()
        {
            PopulateRegionsDropDownList(_context);
            PopulateCountriesDropDownList(_context);

            Site = new Site();
            Site.Address = new Address();
            Site.Address.Country = new Country();

            return Page();
        }

        [BindProperty]
        public Site Site { get; set; }

        [BindProperty]
        public Address Address { get; set; }

        public async Task<IActionResult> OnPostAsync(int countryID)
        {
            var newSite = new Site();
            newSite.Address = Address;
            var Country = await _context.Countries.FindAsync(countryID);
            newSite.Address.Country = Country;

            if (await TryUpdateModelAsync<Site>(
                newSite,"Site",s => s.RegionID,s => s.Name,s => s.Address,s => s.MainPhone)
            )
            {
                try
                {
                    _context.Sites.Add(newSite);
                    await _context.SaveChangesAsync();
                    return RedirectToPage("./Index");
                }
                catch (dbupdateException dbEx)
                {
                    //We have a duplicate key exception on the Address
                    if (dbEx.InnerException.Message.Contains("duplicate key row") &&
                        dbEx.InnerException.Message.Contains("IX_Address_NameOrNumber_PostCode"))
                    {
                        //Address duplicateAddress = _context.Addresses.First(a => a.NameOrNumber.Equals(Address.NameOrNumber) && a.PostCode.Equals(Address.PostCode));
                        //Site.AddressID = duplicateAddress.ID;

                        var errorMessage = "This Name or Number and Post Code combination already exists,please check and try again";
                        ModelState.AddModelError("NameOrNumber",errorMessage);
                        ModelState.AddModelError("PostCode",errorMessage);

                        PopulateRegionsDropDownList(_context,newSite.RegionID);
                        PopulateCountriesDropDownList(_context,newSite.Address.Country);

                        return Page();
                    }
                }
                catch (Exception ex)
                {
                    throw new ApplicationException("Error saving Site details",ex);
                }
            }

            Console.WriteLine(ModelState.ErrorCount);

            PopulateRegionsDropDownList(_context,newSite.RegionID);
            PopulateCountriesDropDownList(_context,newSite.Address.Country);
            return Page();
        }
    }

这一切似乎都运行良好,我可以愉快地创建一个站点及其相关地址,并且设置区域没有问题。一切都很好地绑定,减去国家,但 ID 很好地传递进来,所以我可以设置值,一切都很好。

现在我尝试对 Update 执行相同的操作,但效果不太好,我可以很高兴地更新 Site 实体,但更改地址就没有那么幸运了。

这是 Edit.cshtmlSite 页面,其中部分是 Address

@page "{id:int}"
@model MyProject.Areas.Admin.Pages.Sites.EditModel

@{
    ViewData["Title"] = "Edit Site";
}

<h1>Edit</h1>

<h4>Site - @Model.Site.Name</h4>
<hr />
<div class="row">
    <div class="col-md-4">
        <form method="post">
            <div asp-validation-summary="ModelOnly" class="text-danger"></div>
            <input type="hidden" asp-for="Site.ID" />
            <div class="form-group">
                <label asp-for="Site.Name" class="control-label"></label>
                <input asp-for="Site.Name" class="form-control" />
                <span asp-validation-for="Site.Name" class="text-danger"></span>
            </div>
            <div class="form-group">
                <label asp-for="Site.Region" class="control-label"></label>
                <select asp-for="Site.RegionID" class="form-control" asp-items="@Model.RegionNamesSL">
                    <option value="">-- Select Region --</option>
                </select>
                <span asp-validation-for="Site.Region" class="text-danger"></span>
            </div>
            <input asp-for="Site.AddressID" type="hidden" />
            <partial name="_Address" model="@Model.Site.Address" />
            <div class="form-group">
                <label asp-for="Site.MainPhone" class="control-label"></label>
                <input asp-for="Site.MainPhone" class="form-control" />
                <span asp-validation-for="Site.MainPhone" class="text-danger"></span>
            </div>
            <div class="form-group">
                <button type="submit" title="Save" class="btn btn-primary">
                    <span class="mobile-hidden">Save</span> <i class="ms-Icon ms-Icon--Save" aria-hidden="true"></i>
                </button>
                @*<input type="submit" value="Save" class="btn btn-primary" />*@
            </div>
        </form>
    </div>
</div>

以及相应编辑页面Edit.cshtml.cs

    public class EditModel : SelectListsPageModel
    {
        private readonly MyProject.Data.MyProjectContext _context;

        public EditModel(MyProject.Data.MyProjectContext context)
        {
            _context = context;
        }

        [BindProperty]
        public Site Site { get; set; }

        public async Task<IActionResult> OnGetAsync(int? id)
        {
            if (id == null)
            {
                return NotFound();
            }

            Site = await _context.Sites
                .Include(s => s.Address)
                    .ThenInclude(a => a.Country)
                .FirstOrDefaultAsync(m => m.ID == id);

            if (Site == null)
            {
                return NotFound();
            }

            PopulateRegionsDropDownList(_context,Site.RegionID);
            PopulateCountriesDropDownList(_context,Site.Address.Country); //Todo:  Fix Setting wrong country

            return Page();
        }

        public async Task<IActionResult> OnPostAsync(int? id,int countryID)
        {
            if (!ModelState.IsValid)
            {
                PopulateRegionsDropDownList(_context,Site.RegionID);
                PopulateCountriesDropDownList(_context,Site.Address.Country);

                return Page();
            }

            var sitetoUpdate = await _context.Sites
                .Include(s => s.Address)
                    .ThenInclude(a => a.Country)
                .FirstOrDefaultAsync(m => m.ID == id);

            if (sitetoUpdate == null)
            {
                return NotFound($"Failed to find Site with ID {id}");
            }

            if (await TryUpdateModelAsync<Site>(
                sitetoUpdate,s => s.MainPhone,s => s.Address))
            {

                try
                {
                    await _context.SaveChangesAsync();
                }
                catch (dbupdateConcurrencyException)
                {
                    if (!SiteExists(Site.ID))
                    {
                        return NotFound();
                    }
                    else
                    {
                        throw;
                    }
                }
            }
            return RedirectToPage("./Index");
        }

        private bool SiteExists(int id)
        {
            return _context.Sites.Any(e => e.ID == id);
        }
    }

我已经尝试了上面的多次迭代,包括使用 Address 对象来绑定属性,但我似乎无法让它工作。

我想我的问题是:

  1. 我做错了什么?为什么“编辑”页面没有更新地址?
  2. 我是否以正确的方式解决这个问题,还是有更好的方法解决这个问题?

解决方法

我找到了一种方法来完成这项工作,我不确定这是否是最好的方法,但它允许您使用地址的部分视图,并更新地址和站点对象在一起。

我最初不得不对模型进行一些更改,我发现代码因所有 ID 被称为 ID 而变得有点混乱,因此我更新了各种 { {1}} 个字段以包含实体名称:

ID

然后,我在绑定的 PageModel 中包含了一个 public class Country { [Column("ID")] public int CountryID { get; set; } // Other props here } [Index(nameof(NameOrNumber),nameof(PostCode),IsUnique = true)] public class Address { [Column("ID")] public int AddressID { get; set; } // Other props here } public class Site { [Column("ID")] public int SiteID { get; set; } // Other props here } 属性,以确保它填充了更新的地址数据。之后,我所要做的就是编写一个小的映射器方法,用 Address 中的值更新 Site.Address 中的值,EF 负责其余的工作。我实际上将所有这些代码移到了我自己的 Address 类中,因为地址将在多个地方使用。

所以最终的 PageModel 代码如下所示:

Edit.cshtml.cs

而名为 public class EditModel : RegionWithAddressPageModel { private readonly MyProject.Data.MyProjectContext _context; public EditModel(MyProject.Data.MyProjectContext context) { _context = context; } [BindProperty] public Site Site { get; set; } public async Task<IActionResult> OnGetAsync(int? id) { if (id == null) return NotFound(); Site = await _context.Sites .Include(s => s.Address) .ThenInclude(a => a.Country) .FirstOrDefaultAsync(m => m.ID == id); if (Site == null) return NotFound(); PopulateRegionsDropDownList(_context,Site.RegionID); PopulateCountriesDropDownList(_context,Site.Address.Country); return Page(); } public async Task<IActionResult> OnPostAsync(int? id) { if (!ModelState.IsValid) { PopulateRegionsDropDownList(_context,Site.RegionID); PopulateCountriesDropDownList(_context,Site.Address.Country); return Page(); } var siteToUpdate = await _context.Sites .Include(s => s.Address) .FirstOrDefaultAsync(m => m.ID == id); if (siteToUpdate == null) { return NotFound(); } MapAddress(siteToUpdate.Address); if (await TryUpdateModelAsync<Site>( siteToUpdate,"Site",s => s.Name,s => s.RegionID,s => s.MainPhone)) { try { await _context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException) { if (!SiteExists(Site.ID)) { return NotFound(); } else { throw; } } } return RedirectToPage("./Index"); } private bool SiteExists(int id) { return _context.Sites.Any(e => e.ID == id); } } 的自定义 PageModel 类实际上继承自 RegionWithAddressPageModel。区域版本包括在需要的地方填充和管理区域下拉列表的方法。所以更新地址的最终代码在 AddressPageModel 类中:

AddressPageModel

希望它可以帮助其他想要做类似事情的人。正如我之前所说,这可能不是最好的解决方案,可能还有其他方法可以实现这一点,但这对我来说效果很好。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。