如何在不知道更新哪些字段的情况下处理更新实体?

时间:2015-12-11 02:27:12

标签: c# asp.net-mvc entity-framework

我在我的MVC应用程序中有一个视图,它有一些(但不是全部)我的实体属性公开进行编辑。还有其他未向用户公开的实体属性。现在,表单以我的实体类型的参数形式发布到我的控制器。实体具有视图中公开的字段的有效值,但其余未公开的字段为空。如果我转到我的存储库并尝试使用此存储库更新现有实体,则会使用空值覆盖许多字段。

如何仅使用用户在视图中更改的字段来更新我的实体?

我可以想到两种方法。还有更多吗?

  1. 您将模型的所有字段都包含在表单中的隐藏字段中。这样,当帖子发生时,整个模型就会更新。

  2. 您只发布用户可以更改的字段,并且您具有特定的路由,控制器方法和存储库方法来处理此方法。您可以使用updateEntityAddress等方法接收新的地址值,然后覆盖现有值。您知道此方法仅用于更新特定类型的字段,因此您知道哪些字段已更改,并且可以编写代码来更新这些字段。

4 个答案:

答案 0 :(得分:1)

正如Stephen Muecke在评论中已经提到的那样,你需要借用“ViewModel”的概念。我并不是说你需要一个真正的成熟的ViewModel,因为你已经声明你正在做MVC,你没有做MVVM,但这些概念是相关的,因为MVVM来自MVC。

因此,您需要清楚地了解您的实体的哪个子集向视图公开(并可由其编辑),以便您知道原始实体的哪个部分需要更新以及必须保留哪个部分“as-是”

快速而肮脏的解决方案可能是:

  • 在某处有一个表(可能是将每个实体类型映射到字段列表名称的字典),其中列出了要将实体的哪些字段发送到视图,因此预期哪些字段将被接收回来适用于实体。

但更好的方法是:

  • 介绍一大堆新类,每个实体一个,每个类:

    • 填充了构成实体的字段的子集
    • 被发送到视图并呈现给用户
    • 在POST请求中收到。
    • 然后将
    • 应用于原始实体。

从长远来看,拥有一组单独的ViewModel类是值得的,因为您将不可避免地发现,您的模型实体有时不能呈现给用户,也不能由用户以其原始形式编辑,但他们需要在用户看到它们之前进行转换,并且在保持更改之前可能需要反转这些转换。换句话说,用户看到的表单不需要(不应该限于)与模型实体一一对应。

答案 1 :(得分:0)

我没有对此深思熟虑,但我想到了两种方式。

  1. 发布实体时,请查找同一实体。然后使用反射将已发布实体上的空字段的值设置为您在这些相同字段上查找的实体的值。然后,您的实体不会有任何空字段,这些字段不是空的,您不必将它们全部作为隐藏字段放在表单中。
  2. (更新)如果您知道正在发布的实体以及其中的字段,那么您就不需要使用反射。

    1. 编写存储过程以更新实体,称为" sp_AddOrUpdate_EntityName"。创建第一个参数" @ignoreNullParameters BIT = 0",然后为实体上的所有字段添加参数。然后编写Transac SQL以更新实体(如果存在),如果不存在则将其插入。如果IgnoreNullParameters = 1则更新,则不应使用空值覆盖值。这看起来像

      更新[sometable] 设置[someField] = CASE WHEN @ignoreNullParameters = 1那么ISNULL(@someField,[someField])ELSE @someField END WHERE [id] @id

    2. 然后修改EF以调用存储过程来添加/更新实体(非常确定有一种方法可以做到这一点),而不是让EF让自己的查询更新/插入该实体。

答案 2 :(得分:0)

你应该看看带有Web API的OData:

Create an OData-v4 Endpoint

我在文章中找到了此代码,这样您就可以更新实体的 Delta ,这意味着您只发布要更新的属性:

public async Task<IHttpActionResult> Patch([FromODataUri] int key, Delta<Product> product)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    var entity = await db.Products.FindAsync(key);
    if (entity == null)
    {
        return NotFound();
    }
    product.Patch(entity);
    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!ProductExists(key))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }
    return Updated(entity);
}

答案 3 :(得分:0)

我意识到这对MVC网页有一个答案,也是一个很好的答案,但我想为未来的网络冲浪者发布一种不同的方法。

如果您的集成端点为您提供API的部分更新,并且您只想限制对所提供的值的更改:

在数据库上下文类中,在执行实际的保存操作之前,重写SaveChanges方法并将必要的业务逻辑应用于实体属性。

在我的情况下,我知道某些属性将为null但我不希望它们在数据存储中被访问API调用保存操作的端点覆盖。

Web应用程序执行直接数据更改以维护我不希望更改端点的属性。

这种方法的好处是无需重构整个应用程序来解决可能是边缘情况的问题。

下面的代码段也可以使用。

    public override int SaveChanges()
    {

        var selectedEntityList = ChangeTracker.Entries()
                                .Where(x => x.Entity is ctTerminalTimeZone &&
                                (x.State == EntityState.Added || x.State == EntityState.Modified));
        foreach (var entity in selectedEntityList)
        {
            this.ctTerminalTimeZoneChangeLogEntities.Add(new ctTerminalTimeZoneChangeLog()
            {
                DateModified = DateTime.Now.ToShortDateString(),
                TerminalLocationCode = ((ctTerminalTimeZone)entity.Entity).TerminalLocationCode,
                TimeModified = DateTime.Now.ToLongTimeString()

            });
            if (((ctTerminalTimeZone)entity.Entity).HiddenValue == null)
            {
                this.Entry(((ctTerminalTimeZone)entity.Entity)).Property(x => x.HiddenValue).IsModified = false;
            }
            //and so on and so on
        }
        return base.SaveChanges();
    }