J86*_*J86 9 c# entity-framework automapper asp.net-mvc-5
我有一个Supplier.cs实体及其ViewModel SupplierVm.cs.我正在尝试更新现有供应商,但我收到黄色死亡屏幕(YSOD),并显示错误消息:
操作失败:无法更改关系,因为一个或多个外键属性不可为空.当对关系进行更改时,相关的外键属性将设置为空值.如果外键不支持空值,则必须定义新关系,必须为外键属性分配另一个非空值,或者必须删除不相关的对象.
我想我知道它为什么会发生,但我不知道如何解决它.这是一个关于正在发生的事情的截屏视频.我认为我得到错误的原因是因为当AutoMapper做它的事情时,这种关系就会丢失.
码
以下是我认为相关的实体:
public abstract class Business : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string TaxNumber { get; set; }
public string Description { get; set; }
public string Phone { get; set; }
public string Website { get; set; }
public string Email { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public virtual ICollection<Address> Addresses { get; set; } = new List<Address>();
public virtual ICollection<Contact> Contacts { get; set; } = new List<Contact>();
}
public class Supplier : Business
{
public virtual ICollection<PurchaseOrder> PurchaseOrders { get; set; }
}
public class Address : IEntity
{
public Address()
{
CreatedOn = DateTime.UtcNow;
}
public int Id { get; set; }
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string Area { get; set; }
public string City { get; set; }
public string County { get; set; }
public string PostCode { get; set; }
public string Country { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public int BusinessId { get; set; }
public virtual Business Business { get; set; }
}
public class Contact : IEntity
{
public Contact()
{
CreatedOn = DateTime.UtcNow;
}
public int Id { get; set; }
public string Title { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Phone { get; set; }
public string Email { get; set; }
public string Department { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public int BusinessId { get; set; }
public virtual Business Business { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
这是我的ViewModel:
public class SupplierVm
{
public SupplierVm()
{
Addresses = new List<AddressVm>();
Contacts = new List<ContactVm>();
PurchaseOrders = new List<PurchaseOrderVm>();
}
public int Id { get; set; }
[Required]
[Display(Name = "Company Name")]
public string Name { get; set; }
[Display(Name = "Tax Number")]
public string TaxNumber { get; set; }
public string Description { get; set; }
public string Phone { get; set; }
public string Website { get; set; }
public string Email { get; set; }
[Display(Name = "Status")]
public bool IsDeleted { get; set; }
public IList<AddressVm> Addresses { get; set; }
public IList<ContactVm> Contacts { get; set; }
public IList<PurchaseOrderVm> PurchaseOrders { get; set; }
public string ButtonText => Id != 0 ? "Update Supplier" : "Add Supplier";
}
Run Code Online (Sandbox Code Playgroud)
我的AutoMapper映射配置如下:
cfg.CreateMap<Supplier, SupplierVm>();
cfg.CreateMap<SupplierVm, Supplier>()
.ForMember(d => d.Addresses, o => o.UseDestinationValue())
.ForMember(d => d.Contacts, o => o.UseDestinationValue());
cfg.CreateMap<Contact, ContactVm>();
cfg.CreateMap<ContactVm, Contact>()
.Ignore(c => c.Business)
.Ignore(c => c.CreatedOn);
cfg.CreateMap<Address, AddressVm>();
cfg.CreateMap<AddressVm, Address>()
.Ignore(a => a.Business)
.Ignore(a => a.CreatedOn);
Run Code Online (Sandbox Code Playgroud)
最后,这是我的SupplierController编辑方法:
[HttpPost]
public ActionResult Edit(SupplierVm supplier)
{
if (!ModelState.IsValid) return View(supplier);
_supplierService.UpdateSupplier(supplier);
return RedirectToAction("Index");
}
Run Code Online (Sandbox Code Playgroud)
以下是UpdateSupplier方法SupplierService.cs:
public void UpdateSupplier(SupplierVm supplier)
{
var updatedSupplier = _supplierRepository.Find(supplier.Id);
Mapper.Map(supplier, updatedSupplier); // I lose navigational property here
_supplierRepository.Update(updatedSupplier);
_supplierRepository.Save();
}
Run Code Online (Sandbox Code Playgroud)
我已经做了很多阅读,根据这篇博文,我的工作应该有效!我也读过这样的东西,但我想在放弃使用AutoMapper更新实体之前先与读者联系.
这条线......
Mapper.Map(supplier, updatedSupplier);
Run Code Online (Sandbox Code Playgroud)
......做的远不止眼睛.
updatedSupplier加载其集合(Addresses等),因为AutoMapper(AM)访问它们.您可以通过监视SQL语句来验证这一点.UseDestinationValue设置如此,但仍会发生 (就个人而言,我认为这个设置是不可理解的.)这种替换有一些意想不到的后果
Local集合中(如context.Addresses.Local)但现在被剥夺了它们的父项,因为EF已经执行了关系修复.他们的状态是Modified.Added状态中的上下文.毕竟,他们是新的背景.如果此时您希望1 Address英寸context.Addresses.Local,您会看到2.但您只能在调试器中看到添加的项目.这些导致异常的父级"修改"项目就是这样.如果没有,那么下一个惊喜就是你只需要更新就可以在数据库中添加新项目.
那你怎么解决这个问题呢?
答:我试图尽可能地重播你的场景.对我来说,一个可能的修复包括两个修改:
禁用延迟加载.我不知道你会如何安排你的存储库,但在某个地方应该有一个像这样的行
context.Configuration.LazyLoadingEnabled = false;
Run Code Online (Sandbox Code Playgroud)
这样做,你只会有Added物品,而不是隐藏的Modified物品.
将Added项目标记为Modified.再一次,"某处",放线条
foreach (var addr in updatedSupplier.Addresses)
{
context.Entry(addr).State = System.Data.Entity.EntityState.Modified;
}
Run Code Online (Sandbox Code Playgroud)
... 等等.
B.另一种选择是将视图模型映射到新的实体对象......
var updatedSupplier = Mapper.Map<Supplier>(supplier);
Run Code Online (Sandbox Code Playgroud)
......并将其及其所有孩子标记为Modified.这在更新方面相当"昂贵",请参阅下一点.
C.在我看来,更好的解决方法是将AM从等式中完全取出并手动绘制状态.我总是担心在复杂的映射场景中使用AM.首先,因为映射本身的定义距离使用它的代码很远,所以使代码难以检查.但主要是因为它带来了自己的做事方式.并不总是清楚它是如何与其他微妙的操作相互作用的 - 比如变化跟踪.
绘画状态是一个艰苦的过程.基础可能是......如......
context.Entry(updatedSupplier).CurrentValues.SetValues(supplier);
Run Code Online (Sandbox Code Playgroud)
... 如果名称匹配supplier,updatedSupplier则复制标量属性.或者你可以使用AM(毕竟)将各个视图模型映射到它们的实体对应物,但忽略导航属性.
选项C为您提供了对最初预期更新内容的细粒度控制,而不是对选项B的全面更新.如有疑问,这可以帮助您确定使用哪个选项.
| 归档时间: |
|
| 查看次数: |
4343 次 |
| 最近记录: |