Risposte:
La risposta di Ladislav è stata aggiornata per utilizzare DbContext (introdotto in 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;
e non usaredb.Entry(user).Property("Password").IsModified = true;
db.Configuration.ValidateOnSaveEnabled = false;
potresti voler continuare a convalidare il campo che stai aggiornando:if (db.Entry(user).Property(x => x.Password).GetValidationErrors().Count == 0)
Puoi dire a EF quali proprietà devono essere aggiornate in questo modo:
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();
}
}
Hai sostanzialmente due opzioni:
userId
fornito: l'intero oggetto viene caricatopassword
campo.SaveChanges()
metodo del contestoIn questo caso, spetta a EF come gestirlo in dettaglio. Ho appena provato questo, e nel caso cambio solo un singolo campo di un oggetto, ciò che EF crea è praticamente anche quello che creeresti manualmente - qualcosa del tipo:
`UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId`
Quindi EF è abbastanza intelligente da capire quali colonne sono effettivamente cambiate e creerà un'istruzione T-SQL per gestire solo gli aggiornamenti che sono effettivamente necessari.
Password
colonna per il dato UserId
e nient'altro - praticamente esegue UPDATE dbo.Users SET Password = @Password WHERE UserId = @UserId
) e crei una funzione di importazione per quella procedura memorizzata nel tuo modello EF e la chiami funzione invece di eseguire i passaggi descritti sopraIn Entity Framework Core, Attach
restituisce la voce, quindi tutto ciò che serve è:
var user = new User { Id = userId, Password = password };
db.Users.Attach(user).Property(x => x.Password).IsModified = true;
db.SaveChanges();
sto usando questo:
entità:
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; }
}
codice accessorio:
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();
Durante la ricerca di una soluzione a questo problema, ho trovato una variazione sulla risposta di GONeale attraverso il blog di Patrick Desjardins :
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();
}
" Come puoi vedere, prende come secondo parametro un'espressione di una funzione. Ciò consentirà di utilizzare questo metodo specificando in un'espressione Lambda quale proprietà aggiornare. "
...Update(Model, d=>d.Name);
//or
...Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);
(Una soluzione in qualche modo simile è anche fornita qui: https://stackoverflow.com/a/5749469/2115384 )
Il metodo che sto attualmente usando nel mio codice , esteso per gestire anche (Linq) espressioni di tipo ExpressionType.Convert
. Questo era necessario nel mio caso, ad esempio con Guid
e altre proprietà dell'oggetto. Quelli erano "racchiusi" in un Convert () e quindi non gestiti da 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();
}
Sono in ritardo al gioco qui, ma è così che lo sto facendo, ho trascorso un po 'a cercare una soluzione di cui ero soddisfatto; questo produce una UPDATE
dichiarazione SOLO per i campi che vengono modificati, poiché si definisce esplicitamente ciò che sono attraverso un concetto di "lista bianca" che è più sicuro per impedire comunque l'iniezione di moduli web.
Un estratto dal mio repository di dati 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;
}
Questo potrebbe essere racchiuso in un tentativo..catch se lo desideri, ma personalmente mi piace il mio chiamante per conoscere le eccezioni in questo scenario.
Sarebbe chiamato in questo modo (per me, questo era tramite un'API Web ASP.NET):
if (!session.Update(franchiseViewModel.Franchise, new[]
{
"Name",
"StartDate"
}))
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
UpdateModel
comando ASP.NET MVC ), in questo modo assicurati che l'iniezione del modulo hacker non possa avvenire e che non possano aggiornare i campi che non sono autorizzati ad aggiornare. Se tuttavia qualcuno può convertire l'array string in una sorta di parametro delle espressioni lambda e lavorare con esso nel Update<T>
, fantastico
var entity=_context.Set<T>().Attach(item);
seguita dal entity.Property(propertyName).IsModified = true;
ciclo dovrebbe funzionare.
Entity framework tiene traccia delle modifiche sugli oggetti sottoposti a query dal database tramite DbContext. Ad esempio se il nome dell'istanza DbContext è dbContext
public void ChangePassword(int userId, string password){
var user = dbContext.Users.FirstOrDefault(u=>u.UserId == userId);
user.password = password;
dbContext.SaveChanges();
}
So che questo è un vecchio thread ma stavo anche cercando una soluzione simile e ho deciso di andare con la soluzione @ Doku-così fornita. Sto commentando per rispondere alla domanda posta da @Imran Rizvi, ho seguito il link @ Doku-so che mostra un'implementazione simile. La domanda di @Imran Rizvi era che stava ottenendo un errore usando la soluzione fornita "Impossibile convertire l'espressione Lambda in Tipo" Espressione> [] "perché non è un tipo delegato". Volevo offrire una piccola modifica che ho apportato alla soluzione di @ Doku-so che corregge questo errore nel caso in cui qualcun altro si imbattesse in questo post e decidesse di usare la soluzione di @ Doku-so.
Il problema è il secondo argomento nel metodo di aggiornamento,
public int Update(T entity, Expression<Func<T, object>>[] properties).
Per chiamare questo metodo utilizzando la sintassi fornita ...
Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);
È necessario aggiungere la parola chiave 'params' davanti al secondo arugment in questo modo.
public int Update(T entity, params Expression<Func<T, object>>[] properties)
o se non si desidera modificare la firma del metodo, quindi per chiamare il metodo di aggiornamento è necessario aggiungere la parola chiave ' new ', specificare la dimensione dell'array, quindi utilizzare infine la sintassi dell'inizializzatore dell'oggetto di raccolta per ogni proprietà da aggiornare come visto sotto.
Update(Model, new Expression<Func<T, object>>[3] { d=>d.Name }, { d=>d.SecondProperty }, { d=>d.AndSoOn });
Nell'esempio di @ Doku-so sta specificando una matrice di espressioni, quindi è necessario passare le proprietà per l'aggiornamento in una matrice, a causa della matrice è necessario specificare anche la dimensione della matrice. Per evitare ciò, è anche possibile modificare l'argomento expression per utilizzare IEnumerable anziché un array.
Ecco la mia implementazione della soluzione di @ 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();
}
Uso:
this.Update<Contact>(context, context.Entry(modifiedContact), c => c.Active, c => c.ContactTypeId);
@ Doku-così ha fornito un approccio interessante usando i generici, ho usato il concetto per risolvere il mio problema, ma non puoi usare la soluzione di @ Doku-so così com'è e sia in questo post che nel post collegato nessuno ha risposto alle domande sull'errore di utilizzo.
entityEntry.State = EntityState.Unchanged;
tutti i valori aggiornati nel parametro entityEntry
vengono ripristinati, quindi non vengono salvate modifiche, puoi aiutarci, grazie
In EntityFramework Core 2.x non è necessario Attach
:
// get a tracked entity
var entity = context.User.Find(userId);
entity.someProp = someValue;
// other property changes might come here
context.SaveChanges();
Ho provato questo in SQL Server e profilandolo:
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
Trova assicura che le entità già caricate non attivino un SELECT e inoltre allega automaticamente l'entità se necessario (dai documenti):
/// 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.
Combinando diversi suggerimenti, propongo quanto segue:
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;
}
}
chiamato da
UpdateDbEntryAsync(dbc, d => d.Property1);//, d => d.Property2, d => d.Property3, etc. etc.);
O di
await UpdateDbEntryAsync(dbc, d => d.Property1);
O di
bool b = UpdateDbEntryAsync(dbc, d => d.Property1).Result;
Uso ValueInjecter
nuget per iniettare il modello vincolante nell'entità database usando quanto segue:
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();
}
Si noti l'utilizzo della convenzione personalizzata che non aggiorna le proprietà se sono nulle dal server.
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);
}
}
Uso:
target.InjectFrom<NoNullsInjection>(source);
Cerca questa risposta
Non saprai se la proprietà viene intenzionalmente cancellata su null OPPURE non aveva alcun valore. In altre parole, il valore della proprietà può essere sostituito solo con un altro valore ma non cancellato.
Stavo cercando lo stesso e finalmente ho trovato la soluzione
using (CString conn = new CString())
{
USER user = conn.USERs.Find(CMN.CurrentUser.ID);
user.PASSWORD = txtPass.Text;
conn.SaveChanges();
}
credimi, funziona per me come un incantesimo.
Questo è quello che uso, usando InjectNonNull personalizzato (obj dest, obj src) lo rende completamente flessibile
[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
, vuoi dire la password hash, giusto? :-)