Відповіді:
Відповідь Ладіслава оновлено, щоб використовувати 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();
}
" Як ви бачите, він приймає за свій другий параметр вираження функції. Це дозволить використовувати цей метод, вказавши в виразі Lambda, яке властивість оновлювати. "
...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 - так. Я коментую відповідь на запитання, задане @Imran Rizvi, я перейшов до посилання @ Doku-so, яке показує подібну реалізацію. Питання @Imran Rizvi полягало в тому, що він отримував помилку за допомогою наданого рішення "Неможливо перетворити вираз Lambda у тип" Вираз> [] ", оскільки це не тип делегата". Я хотів запропонувати невелику модифікацію, яку я вніс до рішення @ 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' перед другою рухомою так.
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 замість масиву.
Ось моя реалізація рішення @ 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 - таким чином був класний підхід із використанням загальних, я використовував цю концепцію, щоб вирішити свою проблему, але ви просто не можете використовувати рішення @ Doku-so, як є, і в цій публікації, і в пов'язаному досі ніхто не відповідав на питання про помилки використання.
entityEntry.State = EntityState.Unchanged;
усі оновлені значення в параметрі entityEntry
повернути назад, тому ніяких змін не збережено, чи можете ви допомогти в цьому, дякую
В 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 для введення моделі прив'язки до сутності бази даних, використовуючи наступне:
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
, ви маєте в виду хеш пароля, НЕ так? :-)