如何解决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.cshtml
的 Site
中,我引用了部分视图,如下所示:
\\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.cshtml
的 Site
页面,其中部分是 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
对象来绑定属性,但我似乎无法让它工作。
我想我的问题是:
解决方法
我找到了一种方法来完成这项工作,我不确定这是否是最好的方法,但它允许您使用地址的部分视图,并更新地址和站点对象在一起。
我最初不得不对模型进行一些更改,我发现代码因所有 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 举报,一经查实,本站将立刻删除。