Ответы:
Ответ Ладислава обновлен для использования 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();
}
}
db.Entry(user).Property(x => x.Password).IsModified = true;
и нетdb.Entry(user).Property("Password").IsModified = true;
db.Configuration.ValidateOnSaveEnabled = false;
вы можете продолжить проверять поле, которое вы обновляете:if (db.Entry(user).Property(x => x.Password).GetValidationErrors().Count == 0)
Вы можете сказать 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();
}
}
У вас есть в основном два варианта:
userId
предоставленного - весь объект загружаетсяpassword
поле.SaveChanges()
методВ этом случае это зависит от EF, как обращаться с этим подробно. Я только что проверил это, и в случае, если я изменяю только одно поле объекта, то, что создает EF, - это почти то же самое, что вы бы тоже создали вручную - что-то вроде:
`UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId`
Таким образом, EF достаточно умен, чтобы выяснить, какие столбцы действительно изменились, и он создаст оператор T-SQL для обработки только тех обновлений, которые действительно необходимы.
Password
столбец для заданного UserId
и ничего больше - в основном выполняется UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId
), и вы создаете функцию импорта для этой хранимой процедуры в вашей модели EF, и вы вызываете это функция вместо того, чтобы делать шаги, описанные вышеВ Entity Framework Core Attach
возвращает запись, поэтому все, что вам нужно, это:
var user = new User { Id = userId, Password = password };
db.Users.Attach(user).Property(x => x.Password).IsModified = true;
db.SaveChanges();
Я использую это:
организация:
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();
}
« Как видите, он принимает в качестве второго параметра выражение функции. Это позволит использовать этот метод, указав в лямбда-выражении, какое свойство обновлять ».
...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;
}
Это можно обернуть в попытку… поймать, если вы того пожелаете, но мне лично нравится, чтобы звонивший знал об исключениях в этом сценарии.
Он будет вызываться примерно так (для меня это было через веб-API ASP.NET):
if (!session.Update(franchiseViewModel.Franchise, new[]
{
"Name",
"StartDate"
}))
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
UpdateModel
), таким образом вы гарантируете, что внедрение хакерской формы не может произойти, и они не могут обновить поля, которые им не разрешено обновлять. Если, однако, кто-то может преобразовать строковый массив в какой-то параметр лямбда-выражений и работать с ним в Update<T>
отличном состоянии
var entity=_context.Set<T>().Attach(item);
затем entity.Property(propertyName).IsModified = true;
в цикле должно работать.
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, на которой показана аналогичная реализация. Вопрос Имрана Ризви состоял в том, что он получал ошибку, используя предоставленное решение «Невозможно преобразовать лямбда-выражение в тип« Выражение> [] », потому что это не тип делегата». Я хотел предложить небольшую модификацию решения @ 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 вам нужно добавить ключевое слово ' 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 как есть, и в этом посте, и в связанном посте никто не ответил на вопросы об ошибках использования.
entityEntry.State = EntityState.Unchanged;
все обновленные значения в параметре entityEntry
get возвращаются, поэтому никакие изменения не сохраняются, не могли бы вы помочь с этим, спасибо
В 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
Find гарантирует, что уже загруженные сущности не вызывают 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.
Объединяя несколько предложений, я предлагаю следующее:
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;
Я использую 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();
}
Обратите внимание на использование пользовательского соглашения, которое не обновляет свойства, если они нулевые с сервера.
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);
Найти этот ответ
Вы не будете знать, было ли свойство намеренно очищено до нуля, ИЛИ оно просто не имело никакого значения. Другими словами, значение свойства может быть заменено только другим значением, но не очищено.
Я искал то же самое и, наконец, я нашел решение
using (CString conn = new CString())
{
USER user = conn.USERs.Find(CMN.CurrentUser.ID);
user.PASSWORD = txtPass.Text;
conn.SaveChanges();
}
поверьте мне, это работает для меня как шарм.
Это то, что я использую, используя собственный InjectNonNull (obj dest, obj src), что делает его полностью гибким
[HttpPost]
public async Task<IActionResult> Post( [FromQuery]Models.Currency currency ) {
if ( ModelState.IsValid ) {
// find existing object by Key
Models.Currency currencyDest = context.Currencies.Find( currency.Id );
context.Currencies.Attach( currencyDest );
// update only not null fields
InjectNonNull( currencyDest, currency );
// save
await context.SaveChangesAsync( );
}
return Ok();
}
// Custom method
public static T InjectNonNull<T>( T dest, T src ) {
foreach ( var propertyPair in PropertyLister<T, T>.PropertyMap ) {
var fromValue = propertyPair.Item2.GetValue( src, null );
if ( fromValue != null && propertyPair.Item1.CanWrite ) {
propertyPair.Item1.SetValue( dest, fromValue, null );
}
}
return dest;
}
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();
}
}
Password
, вы имеете в виду хэш пароля, не так ли? :-)