Как обновить только одно поле с помощью Entity Framework?

Здесь таблица

Пользователи

UserId
UserName
Password
EmailAddress

и код..

public void ChangePassword(int userId, string password){
//code to update the password..
}

Ответ 1

Ладислав отвечает на обновления, чтобы использовать DbContext (введенный в EF 4.1):

public void ChangePassword(int userId, string password)
{
  var user = new User() { Id = userId, Password = password };
  using (var db = new MyEfContextName())
  {
    db.Users.Attach(user);
    db.Entry(user).Property(x => x.Password).IsModified = true;
    db.SaveChanges();
  }
}

Ответ 2

Вы можете указать EF, свойства которого должны быть обновлены следующим образом:

public void ChangePassword(int userId, string password)
{
  var user = new User { Id = userId, Password = password };
  using (var context = new ObjectContext(ConnectionString))
  {
    var users = context.CreateObjectSet<User>();
    users.Attach(user);
    context.ObjectStateManager.GetObjectStateEntry(user)
      .SetModifiedProperty("Password");
    context.SaveChanges();
  }
}

Ответ 3

У вас есть в основном два варианта:

  • Пройдите путь EF полностью, в этом случае вы
    • загрузить объект на основе userId, предоставленного - весь объект загружается
    • обновить поле password
    • сохранить объект обратно с помощью контекстного метода .SaveChanges()

В этом случае это до EF, как с этим справиться подробно. Я только что протестировал это, и в случае, когда я изменяю только одно поле объекта, то, что создает EF, - это в значительной степени то, что вы создали вручную, - что-то вроде:

`UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId`

Итак, EF достаточно умен, чтобы выяснить, какие столбцы действительно изменились, и создаст оператор T-SQL для обработки именно тех обновлений, которые на самом деле необходимы.

  • вы определяете хранимую процедуру, которая делает именно то, что вам нужно, в коде T-SQL (просто обновите столбец password для данного userId и ничего больше - в основном выполняет UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId)), и вы создаете импорт функции для этой хранимой процедуры в вашей EF-модели, и вы вызываете эту функцию вместо выполнения описанных выше шагов.

Ответ 4

При поиске решения этой проблемы я нашел вариант ответа GONeale на блог Патрика Дежардинса:

public int Update(T entity, Expression<Func<T, object>>[] properties)
{
  DatabaseContext.Entry(entity).State = EntityState.Unchanged;
  foreach (var property in properties)
  {
    var propertyName = ExpressionHelper.GetExpressionText(property);
    DatabaseContext.Entry(entity).Property(propertyName).IsModified = true;
  }
  return DatabaseContext.SaveChangesWithoutValidation();
}

"Как вы можете видеть, в качестве второго параметра требуется выражение выражения функция. Это позволит использовать этот метод, указав в Lambda выражение, которое свойство для обновления."

...Update(Model, d=>d.Name);
//or
...Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);

(Здесь также приводится несколько аналогичное решение: fooobar.com/questions/13441/...)

Метод, который я использую в своем собственном коде, расширенный для обработки также (Linq) Выражения типа ExpressionType.Convert. Это было необходимо в моем случае, например, с помощью Guid и других свойств объекта. Они были "завернуты" в Convert() и поэтому не обрабатываются System.Web.Mvc.ExpressionHelper.GetExpressionText.

public int Update(T entity, Expression<Func<T, object>>[] properties)
{
    DbEntityEntry<T> entry = dataContext.Entry(entity);
    entry.State = EntityState.Unchanged;
    foreach (var property in properties)
    {
        string propertyName = "";
        Expression bodyExpression = property.Body;
        if (bodyExpression.NodeType == ExpressionType.Convert && bodyExpression is UnaryExpression)
        {
            Expression operand = ((UnaryExpression)property.Body).Operand;
            propertyName = ((MemberExpression)operand).Member.Name;
        }
        else
        {
            propertyName = System.Web.Mvc.ExpressionHelper.GetExpressionText(property);
        }
        entry.Property(propertyName).IsModified = true;
    }

    dataContext.Configuration.ValidateOnSaveEnabled = false;
    return dataContext.SaveChanges();
}

Ответ 5

Я использую это:

объект:

public class Thing 
{
    [Key]
    public int Id { get; set; }
    public string Info { get; set; }
    public string OtherStuff { get; set; }
}

DbContext:

public class MyDataContext : DbContext
{
    public DbSet<Thing > Things { get; set; }
}

код доступа:

MyDataContext ctx = new MyDataContext();

// FIRST create a blank object
Thing thing = ctx.Things.Create();

// SECOND set the ID
thing.Id = id;

// THIRD attach the thing (id is not marked as modified)
db.Things.Attach(thing); 

// FOURTH set the fields you want updated.
thing.OtherStuff = "only want this field updated.";

// FIFTH save that thing
db.SaveChanges();

Ответ 6

Я опаздываю к игре здесь, но так я это делаю, я потратил некоторое время на поиски решения, которое я усвоил; это производит оператор UPDATE ТОЛЬКО для полей, которые были изменены, поскольку вы явно определяете, что они понимают в концепции "белого списка", которая более безопасна для предотвращения вставки веб-формы в любом случае.

Выдержка из моего репозитория данных ISession:

public bool Update<T>(T item, params string[] changedPropertyNames) where T 
  : class, new()
{
    _context.Set<T>().Attach(item);
    foreach (var propertyName in changedPropertyNames)
    {
        // If we can't find the property, this line wil throw an exception, 
        //which is good as we want to know about it
        _context.Entry(item).Property(propertyName).IsModified = true;
    }
    return true;
}

Это может быть завернуто в try..catch, если вы этого пожелаете, но мне лично нравится, как мой вызывающий абонент узнает об исключениях в этом сценарии.

Он будет вызван во что-то вроде этого способа (для меня это было через веб-интерфейс ASP.NET):

if (!session.Update(franchiseViewModel.Franchise, new[]
    {
      "Name",
      "StartDate"
  }))
  throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));

Ответ 7

Структура Entity отслеживает ваши изменения на объектах, которые вы запрашивали из базы данных через DbContext. Например, если имя экземпляра DbContext является dbContext

public void ChangePassword(int userId, string password){
     var user = dbContext.Users.FirstOrDefault(u=>u.UserId == userId);
     user.password = password;
     dbContext.SaveChanges();
}

Ответ 8

Я знаю, что это старый поток, но я также искал аналогичное решение и решил пойти с решением @Doku, поэтому предоставил. Я комментирую, чтобы ответить на вопрос, заданный @Imran Rizvi, я следил за ссылкой @Doku-so, которая показывает аналогичную реализацию. Вопрос @Imran Rizvi заключался в том, что он получал сообщение об ошибке, используя предоставленное решение "Невозможно преобразовать выражение Lambda в Type" Expression > [] ", потому что это не тип делегата". Я хотел предложить небольшую модификацию, которую я сделал для решения @Doku-so, которое исправляет эту ошибку, если кто-то еще сталкивается с этим сообщением и решает использовать решение @Doku-so.

Проблема - это второй аргумент метода Update,

public int Update(T entity, Expression<Func<T, object>>[] properties). 

Чтобы вызвать этот метод, используя предоставленный синтаксис...

Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn); 

Вы должны добавить ключевое слово "params" перед второй копией так.

public int Update(T entity, params Expression<Func<T, object>>[] properties)

или если вы не хотите изменять подпись метода, то для вызова метода обновления вам нужно добавить ключевое слово новое ", укажите размер массива, затем, наконец, используйте коллекцию синтаксис инициализатора объекта для каждого свойства для обновления, как показано ниже.

Update(Model, new Expression<Func<T, object>>[3] { d=>d.Name }, { d=>d.SecondProperty }, { d=>d.AndSoOn });

В примере @Doku он указывает массив выражений, поэтому вы должны передать свойства для обновления в массиве, из-за массива вы также должны указать размер массива. Чтобы этого избежать, вы также можете изменить аргумент выражения для использования IEnumerable вместо массива.

Вот моя реализация решения @Doku.

public int Update<TEntity>(LcmsEntities dataContext, DbEntityEntry<TEntity> entityEntry, params Expression<Func<TEntity, object>>[] properties)
     where TEntity: class
    {
        entityEntry.State = System.Data.Entity.EntityState.Unchanged;

        properties.ToList()
            .ForEach((property) =>
            {
                var propertyName = string.Empty;
                var bodyExpression = property.Body;
                if (bodyExpression.NodeType == ExpressionType.Convert
                    && bodyExpression is UnaryExpression)
                {
                    Expression operand = ((UnaryExpression)property.Body).Operand;
                    propertyName = ((MemberExpression)operand).Member.Name;
                }
                else
                {
                    propertyName = System.Web.Mvc.ExpressionHelper.GetExpressionText(property);
                }

                entityEntry.Property(propertyName).IsModified = true;
            });

        dataContext.Configuration.ValidateOnSaveEnabled = false;

        return dataContext.SaveChanges();
    }

Использование:

this.Update<Contact>(context, context.Entry(modifiedContact), c => c.Active, c => c.ContactTypeId);

@Doku-so предоставил классный подход с использованием generic's, я использовал концепцию для решения моей проблемы, но вы просто не можете использовать @Doku-so решение, которое есть, и как в этом сообщении, так и в связанном сообщении никто не ответил на использование ошибки.

Ответ 9

В ядре Entity Framework Core Attach возвращает запись, поэтому вам нужно всего лишь:

var user = new User { Id = userId, Password = password };
db.Users.Attach(user).Property(x => x.Password).IsModified = true;
db.SaveChanges();

Ответ 10

Объединяя несколько предложений, я предлагаю следующее:

    async Task<bool> UpdateDbEntryAsync<T>(T entity, params Expression<Func<T, object>>[] properties) where T : class
    {
        try
        {
            var entry = db.Entry(entity);
            db.Set<T>().Attach(entity);
            foreach (var property in properties)
                entry.Property(property).IsModified = true;
            await db.SaveChangesAsync();
            return true;
        }
        catch (Exception ex)
        {
            System.Diagnostics.Debug.WriteLine("UpdateDbEntryAsync exception: " + ex.Message);
            return false;
        } 
    }

вызываемый

UpdateDbEntryAsync(dbc, d => d.Property1);//, d => d.Property2, d => d.Property3, etc. etc.);

Или

await UpdateDbEntryAsync(dbc, d => d.Property1);

Или

bool b = UpdateDbEntryAsync(dbc, d => d.Property1).Result;

Ответ 11

Я использую ValueInjecter nuget для ввода Binding Model в базу данных Entity, используя следующую команду:

public async Task<IHttpActionResult> Add(CustomBindingModel model)
{
   var entity= await db.MyEntities.FindAsync(model.Id);
   if (entity== null) return NotFound();

   entity.InjectFrom<NoNullsInjection>(model);

   await db.SaveChangesAsync();
   return Ok();
}

Обратите внимание на использование пользовательского соглашения, которое не обновляет свойства, если они имеют нуль от сервера.

ValueInjecter v3 +

public class NoNullsInjection : LoopInjection
{
    protected override void SetValue(object source, object target, PropertyInfo sp, PropertyInfo tp)
    {
        if (sp.GetValue(source) == null) return;
        base.SetValue(source, target, sp, tp);
    }
}

Использование:

target.InjectFrom<NoNullsInjection>(source);

Инжектор значений V2

Поиск этого ответа

Caveat

Вы не узнаете, намеренно ли оно было очищено до нуля или оно просто не имело значения. Другими словами, значение свойства может быть заменено только на другое значение, но не очищено.

Ответ 12

public async Task<bool> UpdateDbEntryAsync(TEntity entity, params Expression<Func<TEntity, object>>[] properties)
{
    try
    {
        this.Context.Set<TEntity>().Attach(entity);
        EntityEntry<TEntity> entry = this.Context.Entry(entity);
        entry.State = EntityState.Modified;
        foreach (var property in properties)
            entry.Property(property).IsModified = true;
        await this.Context.SaveChangesAsync();
        return true;
    }
    catch (Exception ex)
    {
        throw ex;
    }
}

Ответ 13

public void ChangePassword(int userId, string password)
{
  var user = new User{ Id = userId, Password = password };
  using (var db = new DbContextName())
  {
    db.Entry(user).State = EntityState.Added;
    db.SaveChanges();
  }
}