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


стол

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

UserId
UserName
Password
EmailAddress

и код..

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

13 ответов:

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

вы можете сказать 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();
  }
}

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

  • идти 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 и вызываете эту функцию вместо выполнения шагов, описанных выше

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

сущности:

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

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

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

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

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

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

это будет называться примерно так (для меня это было через ASP.NET Web API):

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

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

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

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

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

проблема является вторым аргументом в методе обновления,

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(Model, new Expression<Func<T, object>>[3] { d=>d.Name }, { d=>d.SecondProperty }, { d=>d.AndSoOn });

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

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

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

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

Поиск ответ

будьте осторожны

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

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

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