簡體   English   中英

在實體框架中更新時排除屬性

[英]Exclude Property on Update in Entity Framework

我一直在尋找一種正確的方法來標記在 MVC 中更新模型時不會更改的屬性。

例如,讓我們以這個小模型為例:

class Model
{
    [Key]
    public Guid Id {get; set;}
    public Guid Token {get; set;}

    //... lots of properties here ...
}

那么 MVC 創建的編輯方法如下所示:

[HttpPost]
public ActionResult Edit(Model model)
{
    if (ModelState.IsValid)
    {
        db.Entry(model).State = EntityState.Modified;
        db.SaveChanges();
        return RedirectToAction("Index");
    }
    return View(model);
}

現在,如果我的視圖不包含令牌,它將通過該編輯無效。

我正在尋找這樣的東西:

db.Entry(model).State = EntityState.Modified;
db.Entry(model).Property(x => x.Token).State = PropertyState.Unmodified;
db.SaveChanges();

到目前為止我發現的最好方法是包容並手動設置我想要包含的所有屬性,但我真的只想說要排除哪些屬性。

我們可以這樣使用

 db.Entry(model).State = EntityState.Modified;
 db.Entry(model).Property(x => x.Token).IsModified = false;
 db.SaveChanges();

任何正在尋找如何在 EF Core 上實現這一目標的人。 基本相同,但您的 IsModified 需要在添加要更新的模型之后進行。

db.Update(model);
db.Entry(model).Property(x => x.Token).IsModified = false;
db.SaveChanges();

創建具有要更新的有限屬性集的新模型。

即,如果您的實體模型是:

public class User
{
    public int Id {get;set;}
    public string Name {get;set;}
    public bool Enabled {get;set;}
}

您可以創建自定義視圖模型,允許用戶更改名稱,但不允許更改標志:

public class UserProfileModel
{
   public int Id {get;set;}
   public string Name {get;set;}
}

當您想要進行數據庫更新時,請執行以下操作:

YourUpdateMethod(UserProfileModel model)
{
    using(YourContext ctx = new YourContext())
    { 
        User user = new User { Id = model.Id } ;   /// stub model, only has Id
        ctx.Users.Attach(user); /// track your stub model
        ctx.Entry(user).CurrentValues.SetValues(model); /// reflection
        ctx.SaveChanges();
    }
}

當您調用此方法時,您將更新 Name,但 Enabled 屬性將保持不變。 我使用了簡單的模型,但我想你會知道如何使用它。

我做了一個簡單的方法來編輯我將與你分享的實體的屬性。 此代碼將編輯實體的名稱和家庭屬性:

    public void EditProfileInfo(ProfileInfo profileInfo)
    {
        using (var context = new TestContext())
        {
            context.EditEntity(profileInfo, TypeOfEditEntityProperty.Take, nameof(profileInfo.Name), nameof(profileInfo.Family));
        }
    }

並且此代碼將忽略編輯實體的名稱和家庭屬性,它將編輯另一個屬性:

    public void EditProfileInfo(ProfileInfo profileInfo)
    {
        using (var context = new TestContext())
        {
            context.EditEntity(profileInfo, TypeOfEditEntityProperty.Ignore, nameof(profileInfo.Name), nameof(profileInfo.Family));
        }
    }

使用這個擴展:

public static void EditEntity<TEntity>(this DbContext context, TEntity entity, TypeOfEditEntityProperty typeOfEditEntityProperty, params string[] properties)
   where TEntity : class
{
    var find = context.Set<TEntity>().Find(entity.GetType().GetProperty("Id").GetValue(entity, null));
    if (find == null)
        throw new Exception("id not found in database");
    if (typeOfEditEntityProperty == TypeOfEditEntityProperty.Ignore)
    {
        foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
        {
            if (!item.CanRead || !item.CanWrite)
                continue;
            if (properties.Contains(item.Name))
                continue;
            item.SetValue(find, item.GetValue(entity, null), null);
        }
    }
    else if (typeOfEditEntityProperty == TypeOfEditEntityProperty.Take)
    {
        foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
        {
            if (!item.CanRead || !item.CanWrite)
                continue;
            if (!properties.Contains(item.Name))
                continue;
            item.SetValue(find, item.GetValue(entity, null), null);
        }
    }
    else
    {
        foreach (var item in entity.GetType().GetProperties(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.GetProperty))
        {
            if (!item.CanRead || !item.CanWrite)
                continue;
            item.SetValue(find, item.GetValue(entity, null), null);
        }
    }
    context.SaveChanges();
}

public enum TypeOfEditEntityProperty
{
    Ignore,
    Take
}

我猜您不希望僅在某些情況下更改屬性,因為如果您不打算在應用程序中使用它,只需將其從模型中刪除即可。

如果您只想在某些情況下使用它並避免在上述情況下“無效”,您可以嘗試:

  • 用 HiddenFor 在視圖中隱藏參數:

    @Html.HiddenFor(m => m.Token)

這將使您的原始值保持不變並傳遞回控制器。

從您的DBSet再次將您的對象加載到控制器中並運行此方法。 您可以指定應更新或不應更新的參數的白名單和黑名單。

我使用 dapper,但我的解決方案也適用於 EF。 如果您將來可能要更改您的 ORM,我的解決方案可能對您更好。

class Model
{
    public Foo { get; set; }
    public Boo { get; set; }
    public Bar { get; set; }
    // More properties...

    public void SafeUpdate(Model updateModel, bool updateBoo = false)
    {
        // Notice Foo is excluded

        // An optional update
        if (updateBoo)
            Boo = updateModel.Boo;

        // A property that is always allowed to be updated
        Bar = updateModel.Bar;
        
        // More property mappings...
    }
}

如您所見,我只允許更新我希望的屬性。

我的方法的一個缺點是,如果您向模型引入新屬性(允許更新),則需要手動更新此方法。 但我相信這並不總是一個缺點,但有時是一個優點,因為您需要了解正在更新的內容,這在安全性方面可能是有益的。

讓我們看一下這種方法的演示。

// Some code, DI etc...

public IActionResult Put([FromBody] Model updateModel)
{
   var safeModel = new Model();
   safeModel.Update(updateModel);

   // Add validation logic for safeModel here...

   _modelRepository.Update(safeModel);
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM