Odpowiedzi:
Zaktualizowano odpowiedź Ladislava, by używała DbContext (wprowadzona w 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;
a której niedb.Entry(user).Property("Password").IsModified = true;
db.Configuration.ValidateOnSaveEnabled = false;
możesz nadal sprawdzać poprawność aktualizowanego pola:if (db.Entry(user).Property(x => x.Password).GetValidationErrors().Count == 0)
Możesz powiedzieć EF, które właściwości należy zaktualizować w ten sposób:
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();
}
}
Masz w zasadzie dwie opcje:
userId
dostarczonego - cały obiekt zostanie załadowanypassword
pole.SaveChanges()
metodęW takim przypadku to EF zależy od tego, jak sobie z tym poradzić. Właśnie to przetestowałem i w przypadku, gdy zmieniam tylko jedno pole obiektu, to, co tworzy EF, jest prawie tym, co stworzyłbyś ręcznie - coś w stylu:
`UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId`
EF jest więc wystarczająco inteligentny, aby dowiedzieć się, które kolumny rzeczywiście się zmieniły, i utworzy instrukcję T-SQL do obsługi tylko tych aktualizacji, które w rzeczywistości są konieczne.
Password
kolumnę dla podanego UserId
i nic więcej - w zasadzie wykonuje się UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId
) i tworzysz import funkcji dla tej procedury przechowywanej w modelu EF i wywołujesz to funkcja zamiast wykonywać czynności opisane powyżejW Entity Framework Core Attach
zwraca wpis, więc wszystko czego potrzebujesz to:
var user = new User { Id = userId, Password = password };
db.Users.Attach(user).Property(x => x.Password).IsModified = true;
db.SaveChanges();
używam tego:
jednostka:
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; }
}
kod akcesorium:
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();
Szukając rozwiązania tego problemu, znalazłem odmianę odpowiedzi GONeale na blogu Patricka Desjardinsa :
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();
}
„ Jak widać, jako drugi parametr przyjmuje wyrażenie funkcji. Pozwoli to na użycie tej metody przez określenie w wyrażeniu Lambda, którą właściwość należy zaktualizować. ”
...Update(Model, d=>d.Name);
//or
...Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);
(Nieco podobne rozwiązanie podano również tutaj: https://stackoverflow.com/a/5749469/2115384 )
Metoda, której obecnie używam we własnym kodzie , została rozszerzona o obsługę również wyrażeń typu (Linq) ExpressionType.Convert
. Było to konieczne w moim przypadku, na przykład w przypadku Guid
i innych właściwości obiektu. Zostały one „opakowane” w Convert () i dlatego nie były obsługiwane przez 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();
}
Spóźniłem się tutaj na grę, ale tak to robię, spędziłem trochę czasu na poszukiwaniu rozwiązania, z którego byłem usatysfakcjonowany; tworzy to UPDATE
instrukcję TYLKO dla pól, które zostały zmienione, ponieważ wyraźnie definiujesz, co to jest za pomocą koncepcji „białej listy”, która jest bezpieczniejsza, aby zapobiec wstrzyknięciu formularza internetowego.
Fragment z mojego repozytorium danych 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;
}
Można to zawrzeć w try..catch, jeśli chcesz, ale osobiście lubię mojego rozmówcę, aby wiedział o wyjątkach w tym scenariuszu.
Zostałby wywołany w taki sposób (dla mnie było to przez interfejs API sieci Web ASP.NET):
if (!session.Update(franchiseViewModel.Franchise, new[]
{
"Name",
"StartDate"
}))
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
UpdateModel
), w ten sposób upewnisz się, że nie można wprowadzić wstrzyknięcia formularza hakera i nie mogą oni aktualizować pól, których nie mogą aktualizować. Jeśli jednak ktoś może przekonwertować tablicę ciągów znaków na jakiś parametr wyrażeń lambda i pracować z nim wUpdate<T>
var entity=_context.Set<T>().Attach(item);
następować entity.Property(propertyName).IsModified = true;
w pętli.
Struktura Entity śledzi twoje zmiany w obiektach, które sprawdziłeś w bazie danych za pomocą DbContext. Na przykład jeśli nazwa instancji DbContext to dbContext
public void ChangePassword(int userId, string password){
var user = dbContext.Users.FirstOrDefault(u=>u.UserId == userId);
user.password = password;
dbContext.SaveChanges();
}
Wiem, że to stary wątek, ale szukałem również podobnego rozwiązania i postanowiłem skorzystać z dostarczonego rozwiązania @ Doku. Komentuję, aby odpowiedzieć na pytanie zadane przez @Imran Rizvi, podążyłem za linkiem @ Doku-so, który pokazuje podobną implementację. @Imran Rizvi zadał pytanie, że dostaje błąd przy użyciu dostarczonego rozwiązania „Nie można przekonwertować wyrażenia Lambda na typ„ Wyrażenie> [] ”, ponieważ nie jest to typ delegata”. Chciałem zaoferować niewielką modyfikację, którą wprowadziłem w rozwiązaniu @ Doku-so, która naprawia ten błąd w przypadku, gdy ktoś napotka ten post i zdecyduje się użyć rozwiązania @ Doku-so.
Problem jest drugim argumentem w metodzie aktualizacji,
public int Update(T entity, Expression<Func<T, object>>[] properties).
Aby wywołać tę metodę przy użyciu dostarczonej składni ...
Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);
Musisz dodać słowo kluczowe „params” przed drugim argumentem.
public int Update(T entity, params Expression<Func<T, object>>[] properties)
lub jeśli nie chcesz zmieniać podpisu metody, a następnie, aby wywołać metodę Update, musisz dodać słowo kluczowe „ new ”, określić rozmiar tablicy, a następnie w końcu użyć składni inicjalizującej obiekt kolekcji dla każdej właściwości do zaktualizowania, jak widać poniżej.
Update(Model, new Expression<Func<T, object>>[3] { d=>d.Name }, { d=>d.SecondProperty }, { d=>d.AndSoOn });
W przykładzie @ Doku-so określa tablicę wyrażeń, więc musisz przekazać właściwości, aby zaktualizować tablicę, ponieważ ze względu na tablicę musisz także określić jej rozmiar. Aby tego uniknąć, możesz również zmienić argument wyrażenia, aby używał IEnumerable zamiast tablicy.
Oto moja implementacja rozwiązania @ 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();
}
Stosowanie:
this.Update<Contact>(context, context.Entry(modifiedContact), c => c.Active, c => c.ContactTypeId);
@ Doku-so zapewniło fajne podejście przy użyciu ogólnych, użyłem tej koncepcji, aby rozwiązać mój problem, ale po prostu nie możesz użyć rozwiązania @ Doku-so w takiej postaci, w jakiej się znajduje, i zarówno w tym poście, jak i w łączonym poście nikt nie odpowiedział na pytania o błędzie użytkowania.
entityEntry.State = EntityState.Unchanged;
wszystkie zaktualizowane wartości w parametrze entityEntry
get cofnij, więc żadne zmiany nie są zapisywane, czy możesz pomóc w tym, dziękuję
W EntityFramework Core 2.x nie ma potrzeby Attach
:
// get a tracked entity
var entity = context.User.Find(userId);
entity.someProp = someValue;
// other property changes might come here
context.SaveChanges();
Próbowałem tego w SQL Server i profilowałem:
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
Znajdź zapewnia, że już załadowane encje nie wyzwalają SELECT, a także automatycznie dołącza encję w razie potrzeby (z dokumentacji):
/// 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.
Łącząc kilka sugestii proponuję następujące:
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;
}
}
nazwany przez
UpdateDbEntryAsync(dbc, d => d.Property1);//, d => d.Property2, d => d.Property3, etc. etc.);
Lub przez
await UpdateDbEntryAsync(dbc, d => d.Property1);
Lub przez
bool b = UpdateDbEntryAsync(dbc, d => d.Property1).Result;
Używam ValueInjecter
nugetu do wstrzykiwania modelu wiązania do encji bazy danych przy użyciu:
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();
}
Zwróć uwagę na użycie niestandardowej konwencji, która nie aktualizuje właściwości, jeśli są one zerowe z serwera.
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);
}
}
Stosowanie:
target.InjectFrom<NoNullsInjection>(source);
Wyszukaj tę odpowiedź
Nie będziesz wiedział, czy właściwość została celowo wyczyszczona na wartość zerową LUB po prostu nie miała żadnej wartości. Innymi słowy, wartość właściwości może być zastąpiona inną wartością, ale nie wyczyszczona.
Szukałem tego samego i wreszcie znalazłem rozwiązanie
using (CString conn = new CString())
{
USER user = conn.USERs.Find(CMN.CurrentUser.ID);
user.PASSWORD = txtPass.Text;
conn.SaveChanges();
}
uwierz mi, działa dla mnie jak urok.
Właśnie tego używam, używając niestandardowego InjectNonNull (obj dest, obj src), dzięki czemu jest w pełni elastyczny
[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
, to znaczy hashed hasło, prawda? :-)