Как обновить только одно поле с помощью Entity Framework? - PullRequest
168 голосов
/ 04 сентября 2010

Вот таблица

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

UserId
UserName
Password
EmailAddress

и код ..

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

Ответы [ 15 ]

337 голосов
/ 06 апреля 2011

Ответ Ладислава обновлен для использования 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();
  }
}
52 голосов
/ 04 сентября 2010

Вы можете указать 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();
  }
}
15 голосов
/ 04 сентября 2010

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

  • пройти путь 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) и создаете функцию import для этой хранимой процедуры в вашей модели EF, и вы вызываете эту функцию вместо выполнения шагов, описанных выше
10 голосов
/ 13 сентября 2012

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

лицо:

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();
8 голосов
/ 05 октября 2016

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

var user = new User { Id = userId, Password = password };
db.Users.Attach(user).Property(x => x.Password).IsModified = true;
db.SaveChanges();
8 голосов
/ 24 апреля 2013

При поиске решения этой проблемы я нашел вариант ответа 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();
}

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

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

(Здесь также приводится несколько похожее решение: https://stackoverflow.com/a/5749469/2115384)

Метод, который я сейчас использую в своем собственном коде , расширен для обработки (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();
}
6 голосов
/ 20 декабря 2012

Я опаздываю к игре здесь, но вот как я это делаю, я потратил некоторое время на поиски решения, которое меня удовлетворило;это создает оператор 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;
}

Это можно обернуть в попытку ... поймать, если вы того пожелаете, но мне лично нравится, чтобы вызывающая сторона знала об исключениях в этом сценарии.будет вызываться примерно так (для меня это было через веб-API ASP.NET):

if (!session.Update(franchiseViewModel.Franchise, new[]
    {
      "Name",
      "StartDate"
  }))
  throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
3 голосов
/ 17 октября 2016

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

public void ChangePassword(int userId, string password){
     var user = dbContext.Users.FirstOrDefault(u=>u.UserId == userId);
     user.password = password;
     dbContext.SaveChanges();
}
3 голосов
/ 25 февраля 2015

Я знаю, что это старая ветка, но я также искал похожее решение и решил воспользоваться решением @ Doku-so.Я комментирую, чтобы ответить на вопрос, заданный @Imran Rizvi, я перешел по ссылке @ Doku-so, на которой показана аналогичная реализация.Вопрос Имрана Ризви состоял в том, что он получал сообщение об ошибке, используя предоставленное решение «Не удалось преобразовать лямбда-выражение в тип« Выражение> [] », потому что это не тип делегата».Я хотел предложить небольшое изменение, которое я внес в решение @ 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' перед вторым arugment, как это так.

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

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

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

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

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

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 предоставил классный подход с использованием дженериков, я использовал эту концепцию для решения своей проблемы, но вы просто не можете использовать решение @ Doku-so как естьи в этом, и в связанном посте никто не ответил на вопросы об ошибках использования.

1 голос
/ 22 февраля 2019

В EntityFramework Core 2.x нет необходимости в Attach:

 // get a tracked entity
 var entity = context.User.Find(userId);
 entity.someProp = someValue;
 // other property changes might come here
 context.SaveChanges();

Попробовал это в SQL Server и профилировал его:

exec sp_executesql N'SET NOCOUNT ON;
UPDATE [User] SET [someProp] = @p0
WHERE [UserId] = @p1;
SELECT @@ROWCOUNT;

',N'@p1 int,@p0 bit',@p1=1223424,@p0=1

Поиск гарантирует, что уже загруженсущности не вызывают SELECT, а также автоматически присоединяют сущность при необходимости (из документов):

    ///     Finds an entity with the given primary key values. If an entity with the given primary key values
    ///     is being tracked by the context, then it is returned immediately without making a request to the
    ///     database. Otherwise, a query is made to the database for an entity with the given primary key values
    ///     and this entity, if found, is attached to the context and returned. If no entity is found, then
    ///     null is returned.
...