كيفية تحديث حقل واحد فقط باستخدام إطار الكيان؟
-
30-09-2019 - |
سؤال
ها هي الجدول
المستخدمون
UserId
UserName
Password
EmailAddress
والرمز ..
public void ChangePassword(int userId, string password){
//code to update the password..
}
المحلول
تم تحديث إجابة Ladislav لاستخدام 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();
}
"كما ترون ، يستغرق الأمر كمعلمة ثانية تعبيرًا عن وظيفة. سيسمح ذلك باستخدام هذه الطريقة من خلال تحديد تعبير 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
وغيرها من خصائص الكائن. كانت "ملفوفة" في تحويل () وبالتالي لم يتم التعامل معها بواسطة 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();
}
في Contity Framework Core ، Attach
إرجاع الإدخال ، لذلك كل ما تحتاجه هو:
var user = new User { Id = userId, Password = password };
db.Users.Attach(user).Property(x => x.Password).IsModified = true;
db.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));
أعلم أن هذا موضوع قديم لكنني كنت أبحث أيضًا عن حل مماثل وقررت الذهاب مع الحل @doku-so. أنا أعلق للإجابة على السؤال الذي طرحته 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-so نهجًا رائعًا باستخدام Generic's ، لقد استخدمت المفهوم لحل مشكلتي ولكن لا يمكنك استخدام حل @doku-so كما هو وفي كل من هذا المنشور والمشاركة المرتبطة ، لم يرد أحد على أسئلة خطأ الاستخدام.
يتتبع إطار عمل الكيان التغييرات الخاصة بك على الكائنات التي استفسرت منها من قاعدة البيانات عبر DBContext. على سبيل المثال ، إذا كنت اسم مثيل DBContext هو dbContext
public void ChangePassword(int userId, string password){
var user = dbContext.Users.FirstOrDefault(u=>u.UserId == userId);
user.password = password;
dbContext.SaveChanges();
}
في 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 وتوصيفه:
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
يضمن العثور على أن الكيانات التي تم تحميلها بالفعل لا تؤدي إلى تحديد وأيضًا يرفع الكيان تلقائيًا إذا لزم الأمر (من المستندات):
/// 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.
أنا أستعمل 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
ابحث عن هذا الجواب
تنبيه قضائي
لن تعرف ما إذا كانت العقار قد تم مسحه عمداً إلى NULL أم أنه لم يكن له أي قيمة. بمعنى آخر ، لا يمكن استبدال قيمة الخاصية إلا بقيمة أخرى ولكن لم يتم مسحها.
كنت أبحث عن نفسه وأخيراً وجدت الحل
using (CString conn = new CString())
{
USER user = conn.USERs.Find(CMN.CurrentUser.ID);
user.PASSWORD = txtPass.Text;
conn.SaveChanges();
}
صدقني إنه يعمل بالنسبة لي مثل سحر.
الجمع بين العديد من الاقتراحات أقترح ما يلي:
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();
}
}