Виключити властивість на оновлення в Entity Framework


79

Я шукав належний спосіб позначити властивість, яку НЕ можна змінювати під час оновлення моделі в 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();

Найкращий спосіб, який я знайшов дотепер, - це включення та встановлення всіх властивостей, які хочу включити вручну, але я справді хочу сказати лише, які з них слід виключити.


Можливий дублікат: stackoverflow.com/questions/3809583/…
Nate-Wilkins

Я не думаю, що це дублікат: я хочу завжди виключити певне властивість із оновлення взагалі. Користувач не повинен мати можливості його змінити.
Мануель Швайгерт

2
Ви можете використовувати viewmodels і просто відобразити те, що ви хочете оновити.
frennky

Я можу. Є кілька способів обійти це питання. Але я хочу знати, чи є хороший спосіб зробити це, і якщо такий є, як це працює. До речі, найменше "рішення", яке я маю для цього банкомату, - це відкрити ще одну транзакцію: using (var db2 = new DataContext()) model.Token = db2.Models.Find(model.Id).Token;Але я теж не задоволений цією.
Мануель Швайгерт,

3
Я визнаю, що це "правильний" спосіб зробити це, але в цьому випадку є причини не робити цього: а) накладні витрати, б) не спритні, в) неможливі дотримання / схильні до помилок. Так, так, я відмовляюся створювати два однакові класи за винятком одного властивості.
Мануель Швайгерт,

Відповіді:


156

ми можемо використовувати так

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

воно буде оновлено, але без властивості Token


2
Що робити, якщо ви використовуєте AddOrUpdate? - Звідки ти знаєш користуватися EntityState.Modifiedчи EntityState.Added?
Джесс

1
ОНОВЛЕННЯ: Щоб це працювало в EF 6 .. вам потрібно db.Model.Attach (модель);
Maxi

6
Просто зауважте іншим, що порядок тут важливий: якщо ви встановите db.Entry(model).State = EntityState.Modified;після налаштування db.Entry(model).Property(x => x.Token).IsModified = false; , властивість буде оновлено при збереженні.
акерра

1
А також це повинно бути після оновлення значень моделі db.Entry (модель) .CurrentValues.SetValues ​​(sourceModel); Якщо ні, тоді властивість також оновлюється при збереженні.
DotNet Fan

10

Створіть нову модель, яка матиме обмежений набір властивостей, які ви хочете оновити.

Тобто, якщо ваша модель сутності:

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

Коли ви викличете цей метод, ви оновите ім'я, але властивість Enabled залишиться незмінним. Я використовував прості моделі, але, думаю, ви зрозумієте, як ними користуватися.


дякую, це виглядає добре, але це все-таки білий, а не чорний список властивостей.
Мануель Швайгерт

Ви додаєте до чорного списку все, чого немає у вашій моделі перегляду, і для цього не потрібно додаткове кодування, ви використовуєте лише функції EF. Крім того, коли суттєва сутність приєднується за допомогою Attach, для всіх значень властивостей встановлюється значення null / default. Якщо ви використовуєте SetValues ​​(модель), якщо властивість вашої моделі перегляду є нульовим, оскільки воно вже було приєднане як нульове, відстежувач змін не позначить його як змінений, і, отже, це властивість буде пропущено із збереження. Спробуй це.
Admir Tuzović

3
Я не хочу з вами сперечатися. Чорний і білий списки - це різні підходи з однаковим результатом, ваш підхід - білий. Як я вже говорив раніше, є багато способів, але я запитував про один, зокрема. Крім того, ваше рішення працює лише з типовими типами.
Мануель Швайгерт,

1. Приєднайте свою модель за допомогою Attach 2., прокрутіть властивості за допомогою db.Entry (модель) .Property ("Ім'я власності"). State = PropertyState.Modified; 3. Зробіть SaveChanges.
Admir Tuzović

Те, що ви робите, встановлює всю модель для модифікації, а потім встановлює деякі властивості на Немодифікований. Те, що я написав вам, - спочатку приєднати модель (ніщо не встановлено як змінене), а потім позначити властивості, які ви хочете оновити, як Модифіковані, що саме те, що ви хотіли => білий список.
Admir Tuzović

8

Той, хто шукає, як цього досягти на EF Core. В основному це те саме, але ваш IsModified повинен бути після додавання моделі для оновлення.

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

Не знаю чому, але мій EF Core мав лише рядкову версію, і я не міг використовувати лямбду. Я використовував nameof замість цього, але це шлях. Дякую
Cubelaster

Ця відповідь настільки "Microsofty", що я знав, що це спрацює ще до тестування. На відміну від наведеного коментаря, як рядок, так і лямбда-версія давали однакові результати. можливо, ми використовуємо різні версії.
T3.0

3

Я зробив простий спосіб редагувати властивості об’єктів, якими я поділюсь з вами. цей код буде редагувати властивості Ім'я та Сімейство сутності:

    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
}

1

Думаю, ви не хочете, щоб властивість змінювалося лише в деяких випадках, тому що якщо ви не збираєтеся використовувати його ніколи у своїй програмі, просто видаліть його зі своєї моделі.

Якщо ви хочете використовувати його лише в деяких сценаріях і уникнути його "зведення нанівець" у наведеному вище випадку, ви можете спробувати:

  • Приховати параметр у поданні за допомогою HiddenFor:

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

Це призведе до того, що ваше початкове значення залишатиметься незмінним і передаватиметься назад контролеру.

Завантажте знову свій об’єкт у контролер з вашого DBSetі запустіть цей метод. Ви можете вказати як білий список, так і чорний список параметрів, які будуть або не будуть оновлюватися.


Ви можете знайти хорошу дискусію про TryUpdateModel тут: посилання . Як сказано у підтвердженій відповіді, краще створити моделі перегляду, щоб точно відповідати властивостям, необхідним у кожному поданні.
Хайме,

1
Використання @Html.HiddenForзапише значення в HTML представлення та дозволить користувачеві використовувати елемент перевірки у своєму браузері та модифікувати його. Після цього він все ще передається контролеру, але з іншим значенням, і буде оновлений. Я просто тестував це.
duckwizzle
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.