实体和派生视图模型 - 仅更新公共属性

时间:2012-04-29 08:24:24

标签: asp.net-mvc-3 entity-framework ef-code-first

假设我有一个名为User的类,这是我的基本实体(我将它与DbContext用作DbSet用户),我用作数据访问层的基础级别。让我们说课程看起来像这样:

public class User
{
    [Key]
    public int Id { get; set; }
    public bool Active { get; set; }
    public string Description { get; set; }
    public string Username { get; set; }
    public string Password { get; set; }
    public byte[] Photo { get; set; }
    public DateTime Created { get; set; }
}

现在我想拥有纯视图,我只显示用户是否处于活动状态,以及允许我更改该值的简单复选框。我不想加载任何其他实体属性,特别是属性Photo,因为它只是疯了。我创建了这样的ActivateUserModel:

public class ActivateUserModel
{
    [Key]
    public int Id { get; set; }
    public bool Active { get; set; }
}

我有一个名为Activate的强类型视图,它接受ActivateUserModel并显示它(它只是一个复选框并为Id隐藏)然后我有[HttpPost]激活动作捕获ActivateUserModel,将其转换为User实体,然后保存更改数据库。这是POST动作:

    [HttpPost]
    public ActionResult Activate(ActivateUserModel model)
    {
        if (ModelState.IsValid)
        {
            User user = new User { Id = model.Id, Active = model.Active };
            db.Users.Attach(user);
            db.Entry(user).Property(u => u.Active).IsModified = true;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(model);
    }

这就像一个魅力。我已经监视了针对SQL服务器发出的查询,我加载的所有内容都是对Id / Active,我更新的所有内容都是基于id的Active更改。

但是我不喜欢代码看起来如何。假设我有50个属性的实体,并且视图有25个。我不想写25行,我说IsModified = true。

所以我的问题是:有没有更有效的方法来做同样的事情,而不需要深入研究任何基于反射的方法?我想将数据从任何视图模型传输到实体,然后只保存那些属性。

提前感谢您的回复,我希望我提出足够明确的问题:)

1 个答案:

答案 0 :(得分:6)

你可以这样做:

[HttpPost]
public ActionResult Activate(ActivateUserModel model)
{
    if (ModelState.IsValid)
    {
        User user = db.Users.Single(u => u.Id == model.Id);
        db.Entry(user).CurrentValues.SetValues(model);
        db.SaveChanges();

        return RedirectToAction("Index");
    }
    return View(model);
}

db.Entry(user).CurrentValues.SetValues(model)将检查user中的model中的属性是否也存在model中的相同名称,如果是,则将属性值从user复制到user }。如果没有,它会使user中的属性值保持不变。

我怀疑这不是基于反思的。但上面的代码是直截了当的方式,旨在完全支持您的场景。

修改

上面的代码加载了包含Photo属性的完整IsModified实体。如果您不想加载潜在的大二进制字段,我建议使用除Modified技巧之外的其他策略解决此问题。使用实体框架进行更新强烈依赖于更改跟踪,这需要您加载完整实体。当您尝试避免这种情况时,您将使代码复杂化并手动为特定属性设置Photo标志。

您可能知道,从数据库中获取实体时,不能排除单个标量属性的加载。我建议将UserPhoto属性移动到一个新实体Id,该实体只有PhotoUserPhoto属性,并将导航属性User放入User课程。然后,如果您想要将照片与User一起加载,您可以通过延迟,急切或显式加载来决定。

如果要将UserPhoto存储在单独的表中,可以在UserPhotoPhoto之间创建一对一映射。或者您甚至可以将User列留在User表格中,并通过Table Splitting将两个实体UserPhotomodel映射到同一张表。

修改2

请参阅您的评论,该方法会加载“不必要的东西”。我忘了提到以下内容:

实际上,在上面的代码中,您需要从数据库加载实体的成本。但是当您使用userSetValues(model)应用于加载的实体Modified时,EF只会将这些属性标记为IsModified,与数据库中的原始值相比,这些属性确实发生了变化。生成的UPDATE语句仅包含这些列。因此,编写UPDATE语句的成本最小化。

如果您不想加载实体,则不知道数据库中的当前列值,并且您不知道实际更改了什么。您唯一的机会是强制所有属性的UPDATE以确保数据库中的行得到正确更新。在您的考试中,您必须将ViewModel中包含的所有25个属性true设置为{{1}}。生成的SLQ UPDATE语句将包含所有25列。因此,UPDATE语句可能要贵得多,并且 - 借用你的话 - 不必要的东西。