1

我想知道一种将ModeltoViewModelViewModelto ModelwithoutAutoMapper或类似东西转换的好方法,因为我想了解背后的内容并学习如何自己做。当然,模型是指由 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. 我现在面临的一个问题是,当我想编辑一篇博客文章时,它的对应实体需要设置作者的外键:“BP_ATH_ID”,但这是“0”,因为编辑后的帖子的作者是“空”,因为我不想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();
    }

好的,回到我的问题。处理这种转换模型->视图模型并返回的好方法是什么?

4

2 回答 2

5

在我看来,这种方法在两个方向上都是有问题的。

  • 模型到 ViewModel(GET 请求)

    如果您使用这样的方法...

    public static Author ToViewModel(EntityAuthor author)
    

    ...问题是:你从哪里得到的EntityAuthor author?当然,您可以使用FindorSingle或其他东西从数据库中加载它。这将具有所有属性的整个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 之间的映射是 AutoMapper 的一个很好的候选。(您不能将 AutoMapper 用于上述投影步骤。)

    另一种方法是直接投影到 ViewModel 中,即将MyViewDto上面替换为OrderViewModel. 尽管 ViewModel 所在的位置,但您必须向视图层公开IQueryable<Order>。有些人不喜欢它,我个人正在使用这种方法。

    缺点是你需要很多不同的 type 方法ToMyViewModel,基本上每个视图都需要另一种方法。

  • ViewModel 到模型(POST 请求)

    这是更大的问题,正如您在示例中已经注意到的那样:许多视图不显示完整的实体或显示应该是“仅查看”并且不回发到服务器的实体数据。

    如果您使用该方法(是否使用 AutoMapper)...

    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();
    }
    

    这将是一种视图方法,它只允许编辑TitleDescription. EntityBlogPost通过将特定属性标记为ModifiedEF 只会更新数据库中的那些列。

    另一种方法是再次引入 DTO 以及视图模型和那些 DTO 之间的映射方法:

    public static MyUpdateAuthorDto ToMyUpdateAuthorDto(Author author)
    

    这只是属性复制或 AutoMapper。更新可以通过以下方式完成:

    public void UpdateForMyView1(MyUpdateAuthorDto dto)
    {
        var entityAuthor = this.dbContext.EntityAuthors.Find(dto.AuthorId);
        this.dbContext.Entry(entityAuthor).CurrentValues.SetValues(dto);
        this.dbContext.SaveChanges();
    }
    

    这只会更新匹配 inEntityAuthor和 in的属性,dto并将它们标记为Modified它们确实发生了变化。这将解决您缺少外键的问题,因为它不是 dto 的一部分并且不会被更新。数据库中的原始值保持不变。

    请注意,它SetValues需要一个objectas 参数。因此,您可以使用某种可重复使用的更新方法:

    public void UpdateScalarAuthorProperties(int authorId, object dto)
    {
        var entityAuthor = this.dbContext.EntityAuthors.Find(authorId);
        this.dbContext.Entry(entityAuthor).CurrentValues.SetValues(dto);
        this.dbContext.SaveChanges();
    }
    

    此方法仅适用于标量和复杂属性的更新。如果允许您的视图更改相关实体或实体之间的关系,则该过程并不容易。对于这种情况,除了为每种更新编写特定方法之外,我不知道另一种方法。

于 2012-06-15T14:49:12.560 回答
1

处理这些转换的一个好方法是使用AutoMapper。这就是创建 automapper 的目的,真的

如果您想了解它是如何工作的,请使用 assemlby 反编译器(ILSpy就是其中之一)并在AutoMapper.dll上使用它。

这里的神奇词是反射

从...开始:

foreach (PropertyInfo prop in typeof(EntityAuthor).GetProperties())
{
   ...
}

反射机制允许您列出源类和目标类的所有属性,比较它们的名称,当您匹配这些名称时,您可以使用SetValue方法设置目标对象的属性,基于源对象的值。

于 2012-06-14T23:48:38.390 回答