我想知道一种在没有AutoMapper
或类似东西的情况下将Model
转换为ViewModel
和ViewModel
转换为Model
的好方法,因为我想了解背后的原因并学习如何自己做。当然,我所说的模型是指 EF 生成的类。
到目前为止,我做了这样的事情,但是在涉及嵌套类时遇到了一些问题:
// to VM
public static Author ToViewModel(EntityAuthor author)
{
if (author == null)
return null;
Author result = new Author();
result.Id = author.ATH_ID;
result.FirstName = author.ATH_FirstName;
result.LastName = author.ATH_LastName;
return result;
}
public static BlogPost ToViewModel(EntityBlogPost post)
{
if (post == null)
return null;
Experiment result = new Experiment();
result.Id = post.BP_ID;
result.Title = post.BP_Title;
result.Url = post.BP_Url;
result.Description = post.BP_Description;
result.Author = ToViewModel(post.Author);
return result;
}
// from VM
public static EntityAuthor ToModel(Author author)
{
if (author == null)
return null;
EntityAuthor result = new EntityAuthor();
result.ATH_ID= author.Id;
result.ATH_FirstName = author.FirstName;
result.ATH_LastName = author.LastName;
return result;
}
public static EntityBlogPost ToModel(BlogPost post)
{
if (post == null)
return null;
EntityBlogPost result = new EntityBlogPost();
result.BP_ID = post.Id;
result.BP_Title = post.Title;
result.BP_Url = post.Url;
result.BP_Description = post.Description;
result.Author = ToModel(post.Author);
return result;
}
注意:EntityBlogPost
保存EntityAuthor
的外键。我现在面临的一个问题是,当我想编辑一个 BlogPost 时,它的相应实体需要作者的外键:"BP_ATH_ID"来设置,但这是"0",因为编辑帖子的作者是"null",因为我不想 http-post 作者。尽管如此,作者仍然需要在视图模型中,因为我想显示它(在http-get期间)。这是我的控制器,以便更好地理解(视图并不重要):
// GET: I make use of Author for this
public ActionResult Edit(int id)
{
return View(VMConverter.ToViewModel(new BlogPostService().GetByID(id)));
}
//
// POST: I don't make use of Author for this
[HttpPost]
public ActionResult Edit(BlogPost input)
{
if (ModelState.IsValid)
{
new BlogPostService().Update(VMConverter.ToModel(input));
return RedirectToAction("List");
}
return View(input);
}
目前,我的控制器后面有一些服务,它们只能在Model
上运行(正如您在我的代码中看到的那样)。目的是将这个"服务层"也重用于其他应用程序。
public void Update(EntityBlogPost post)
{
// let's keep it simple for now
this.dbContext.Entry(post).State = EntityState.Modified;
this.dbContext.SaveChanges();
}
好的,回到我的问题。处理这种转换模型>视图模型并返回的好方法是什么?
在我看来,这种方法在两个方向上都是有问题的。
- 模型
到视图模型(GET 请求)
如果您使用的是这样的方法...
public static Author ToViewModel(EntityAuthor author)
。问题是:你从哪里得到
EntityAuthor author
?当然,您可以使用Find
或Single
或其他东西从数据库中加载它。这将具体化具有所有属性的整个EntityAuthor
实体。您是否需要在视图中使用它们?也许是的,在这个简单的例子中。但是,假设一个大型Order
实体,其中包含大量对其他实体(客户,交货地址,订单项,联系人,发票地址等)的引用,并且您希望显示仅包含一些属性的视图:截止日期,客户名称,联系人电子邮件地址。要应用
ToViewModel
方法,您必须使用视图不需要的一大堆属性加载EntityOrder
,甚至必须为相关实体应用Include
。这将再次加载这些实体的所有属性,但您只需要在视图中选择它们。仅加载视图所需属性的常用方法是投影,例如:
var dto = context.Orders.Where(o => o.Id == someOrderId) .Select(o => new MyViewDto { DueDate = o.DueDate, CustomerName = o.Customer.Name, ContactPersonEmailAddress = o.ContactPerson.EmailAddress }) .Single();
如您所见,我在
MyViewDto
引入了一个新的帮助程序类。现在,您可以创建特定的ToViewModel
方法:public static OrderViewModel ToMyViewModel(MyViewDto dto)
dto 和 viewModel 之间的映射是 AutoMap per 的一个很好的候选者。(不能将自动映射程序用于上述投影步骤。
另一种方法是直接投影到 ViewModel 中,即将上面的
MyViewDto
替换为OrderViewModel
。您必须向视图层公开IQueryable<Order>
,尽管视图模型位于其中。有些人不喜欢它,我个人正在使用这种方法。缺点是你需要很多不同的
ToMyViewModel
类型的方法,基本上每个视图都是另一种方法。视图模型到模型(开机自检请求)
正如您在示例中已经注意到的那样,这是一个更大的问题:许多视图不显示完整的实体或显示本应"仅查看"的实体数据,并且不会回发到服务器。
如果您使用该方法(是否使用自动映射器)...
public static EntityAuthor ToModel(Author author)
。在大多数情况下,您显然不会创建完整的
EntityAuthor
对象,因为视图模型Author author
表示的视图不会显示所有属性,并且至少不会将它们全部提交回去。使用如下所示的Update
方法:this.dbContext.Entry(post).State = EntityState.Modified;
。将部分销毁数据库中的实体(或在最佳情况下引发异常,因为某些必需的 FK 或属性设置不正确)。要实现正确的更新,您实际上必须合并存储在数据库中的值,并且通过从视图回发的更改值保持不变。
您可以使用针对视图定制的特定
Update
方法:public void UpdateForMyView1(EntityBlogPost post) { this.dbContext.EntityBlogPosts.Attach(post); this.dbContext.Entry(post).Property(p => p.Title).IsModified = true; this.dbContext.Entry(post).Property(p => p.Description).IsModified = true; this.dbContext.SaveChanges(); }
这将是仅允许编辑
EntityBlogPost
的Title
和Description
的视图的方法。通过将特定属性标记为Modified
EF 将仅更新数据库中的这些列。另一种方法是再次引入 DTO 以及视图模型和这些 DTO 之间的映射方法:
public static MyUpdateAuthorDto ToMyUpdateAuthorDto(Author author)
这只是属性复制或自动映射程序。更新可通过以下方式完成:
public void UpdateForMyView1(MyUpdateAuthorDto dto) { var entityAuthor = this.dbContext.EntityAuthors.Find(dto.AuthorId); this.dbContext.Entry(entityAuthor).CurrentValues.SetValues(dto); this.dbContext.SaveChanges(); }
这将仅更新在
EntityAuthor
和dto
中匹配的属性,并将它们标记为Modified
(如果它们确实发生了更改)。这将解决缺少外键的问题,因为它不是 dto 的一部分,不会更新。数据库中的原始值保持不变。请注意,
SetValues
将object
作为参数。因此,您可以使用某种可重用的更新方法:public void UpdateScalarAuthorProperties(int authorId, object dto) { var entityAuthor = this.dbContext.EntityAuthors.Find(authorId); this.dbContext.Entry(entityAuthor).CurrentValues.SetValues(dto); this.dbContext.SaveChanges(); }
此方法仅适用于标量和复杂属性的更新。如果允许您的视图更改相关实体或实体之间的关系,则该过程并不容易。对于这种情况,除了为每种更新编写特定方法之外,我不知道另一种方法。
处理这些转换的一个好方法是使用AutoMapper。这就是创建自动映射器的目的,真的。
如果你想了解它是如何工作的,请使用assemlby反编译器(ILSpy就是其中之一)并在AutoMapper.dll上使用它。
这里的神奇词是反射。
入手:
foreach (PropertyInfo prop in typeof(EntityAuthor).GetProperties())
{
...
}
反射机制允许您列出源类和目标类的所有属性,比较它们的名称,当您匹配这些名称时,您可以使用SetValue
方法根据源对象的值设置目标对象的属性。