چند نکته کاربردی درباره Entity Framework
نویسنده: ایمان محمدی
تاریخ: ۱۳۹۲/۰۳/۰۹ ۱۴:۵۵
آدرس: www.dntips.ir
| مطالب | ۳۶۹۴ |
| نویسندگان | ۲۷۶ |
| گروههای مطالب | ۱۰۲۴ |
| نقشههای راه | ۱۱۹ |
| دورهها | ۱۴ |
| اشتراکها | ۱۷۹۱۴ |
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key
T attachedEntity = set.Find(entity.Id); var attachedEntry = dbContext.Entry(attachedEntity); attachedEntry.CurrentValues.SetValues(entity);
public DbContext()
{
this.Configuration.ProxyCreationEnabled = false;
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.AutoDetectChangesEnabled = false;
}dbContext.Entry(entity).State = EntityState.Unchanged ; dbContext.Entry(entity).State = EntityState.Added ; //or Dbset.Add(entity) dbContext.Entry(entity).State = EntityState.Modified ; dbContext.Entry(entity).State = EntityState.Deleted ; // or Dbset.Remove(entity)
dbContext.Entry(entity).State = EntityState.Detached;
// group id=19 Name="General" var customer = new Customer(); customer.Group = group; customer.Name = "mohammadi"; dbContext.Entry(customer).State = EntityState.Added; var customerstate = dbContext.Entry(customer).State;// customerstate=EntityState.Added var groupstate = dbContext.Entry(group);// groupstate=EntityState.Added
// group id=19 Name="General" var customer = new Customer(); customer.Group = group; customer.Name = "mohammadi"; dbContext.Entry(group).State = EntityState.Unchanged; dbContext.Entry(customer).State = EntityState.Added; var customerstate = dbContext.Entry(customer).State;// customerstate=EntityState.Added var groupstate = dbContext.Entry(group);// groupstate=EntityState.Unchanged
// group id=19 Name="General" var customer = new Customer(); customer.Group = group; customer.Name = "mohammadi"; dbContext.Entry(customer).State = EntityState.Unchanged; dbContext.Entry(customer).State = EntityState.Added; var customerstate = dbContext.Entry(customer).State;// customerstate=EntityState.Added var groupstate = dbContext.Entry(group);//// groupstate=EntityState.Unchanged
var customer = dbContext.Customers.FirstOrDefault(); var customerAsNoTracking = dbContext.Customers.AsNoTracking().FirstOrDefault(); var customerstate = dbContext.Entry(customer).State;// customerstate=EntityState.Unchanged var customerstateAsNoTracking = dbContext.Entry(customerAsNoTracking).State;// customerstate=EntityState.Detached
var Entries = dbContext.ChangeTracker.Entries(); var AddedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Added); var ModifiedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Modified); var UnchangedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Unchanged); var DeletedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Deleted); var DetachedEntries = dbContext.ChangeTracker.Entries().Where(entityEntry => entityEntry.State==EntityState.Detached);//* not working !
var CustomersWithGroup = dbContext.Customers.AsNoTracking().Include("Group").ToList();
var CustomerFull = dbContext.Customers.AsNoTracking().Include("Group").Include("Bills").Include("Bills.BillDetails").ToList(); public virtual void AddOrUpdate(T entity)
{
if (entity.Id == 0)
Add(entity);
else
Update(entity);
}5) اگر بخواهیم موجودیتهای رابطه ای در دیتا گرید ویو (ویندوز فرم) نشون بدیم باید چه کار کنیم؟
گرید ویو در ویندوز فرم قادر به نشون دادن فیلدهای رابطه ای نیست برای حل این مشکل میتونیم یک نوع ستون جدید برای گرید ویو تعریف کنیم و برای نشون دادن فیلدهای رابطه ای از این نوع ستون استفاده کنیم:
public class DataGridViewChildRelationTextBoxCell : DataGridViewTextBoxCell
{
protected override object GetValue(int rowIndex)
{
try
{
var bs = (BindingSource)DataGridView.DataSource;
var cl = (DataGridViewChildRelationTextBoxColumn)DataGridView.Columns[ColumnIndex];
return getChildValue(bs.List[rowIndex], cl.DataPropertyName).ToString();
}
catch (Exception)
{
return "";
}
}
private object getChildValue(object dataSource, string childMember)
{
int nextPoint = childMember.IndexOf('.');
if (nextPoint == -1) return dataSource.GetType().GetProperty(childMember).GetValue(dataSource, null);
string proName = childMember.Substring(0, nextPoint);
object newDs = dataSource.GetType().GetProperty(proName).GetValue(dataSource, null);
return getChildValue(newDs, childMember.Substring(nextPoint + 1));
}
}
public class DataGridViewChildRelationTextBoxColumn : DataGridViewTextBoxColumn
{
public string DataMember { get; set; }
public DataGridViewChildRelationTextBoxColumn()
{
CellTemplate = new DataGridViewChildRelationTextBoxCell();
}
}نحوه استفاده را در ادامه میبینید. این روش توسط ویزارد گریدویو هم قابل استفاده است. موقع Add کردن Column نوع اون رو روی DataGridViewChildRelationTextBoxColumn تنظیم کنید.
GroupNameColumn= new DataGridViewChildRelationTextBoxColumn(); //from your class GroupNameColumn.HeaderText = "گروه مشتری"; GroupNameColumn.DataPropertyName = "Group.Name"; //EF Property: Customer.Group.Name GroupNameColumn.Visible = true; GroupNameColumn.Width = 300; DataGridView.Columns.Add(GroupNameColumn);
The Find method on DbSet uses the primary key value to attempt to find an entity tracked by the context. If the entity is not found in the context then a query will be sent to the database to find the entity there. Null is returned if the entity is not found in the context or in the database.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Diagnostics;
using System.Linq;
namespace EF_General.Models.Ex21
{
public abstract class BaseEntity
{
public int Id { set; get; }
}
public class Factor : BaseEntity
{
public int TotalPrice { set; get; }
}
public class MyContext : DbContext
{
public DbSet<Factor> Factors { get; set; }
public MyContext() { }
public MyContext(bool withTracking)
{
if (withTracking)
return;
this.Configuration.ProxyCreationEnabled = false;
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.AutoDetectChangesEnabled = false;
}
public void CustomUpdate<T>(T entity) where T : BaseEntity
{
if (entity == null)
throw new ArgumentException("Cannot add a null entity.");
var entry = this.Entry<T>(entity);
if (entry.State != EntityState.Detached)
return;
/*var set = this.Set<T>(); // اینها اضافی است
//متد فایند اگر اینجا باشه حتما به بانک اطلاعاتی رجوع میکنه در حالت منقطع از زمینه و در یک حلقه به روز رسانی کارآیی مطلوبی نخواهد داشت
T attachedEntity = set.Find(entity.Id);
if (attachedEntity != null)
{
var attachedEntry = this.Entry(attachedEntity);
attachedEntry.CurrentValues.SetValues(entity);
}
else
{*/
entry.State = EntityState.Modified;
//}
}
}
public class Configuration : DbMigrationsConfiguration<MyContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(MyContext context)
{
if (!context.Factors.Any())
{
for (int i = 0; i < 20; i++)
{
context.Factors.Add(new Factor { TotalPrice = i });
}
}
base.Seed(context);
}
}
public class Performance
{
public TimeSpan ListDisabledTracking { set; get; }
public TimeSpan ListNormal { set; get; }
public TimeSpan DetachedEntityDisabledTracking { set; get; }
public TimeSpan DetachedEntityNormal { set; get; }
}
public static class Test
{
public static void RunTests()
{
startDb();
var results = new List<Performance>();
var runs = 20;
for (int i = 0; i < runs; i++)
{
Console.WriteLine("\nRun {0}", i + 1);
var tsListDisabledTracking = PerformanceHelper.RunActionMeasurePerformance(() => updateListTotalPriceDisabledTracking());
var tsListNormal = PerformanceHelper.RunActionMeasurePerformance(() => updateListTotalPriceNormal());
var tsDetachedEntityDisabledTracking = PerformanceHelper.RunActionMeasurePerformance(() => updateDetachedEntityTotalPriceDisabledTracking());
var tsDetachedEntityNormal = PerformanceHelper.RunActionMeasurePerformance(() => updateDetachedEntityTotalPriceNormal());
results.Add(new Performance
{
ListDisabledTracking = tsListDisabledTracking,
ListNormal = tsListNormal,
DetachedEntityDisabledTracking = tsDetachedEntityDisabledTracking,
DetachedEntityNormal = tsDetachedEntityNormal
});
}
var detachedEntityDisabledTrackingAvg = results.Average(x => x.DetachedEntityDisabledTracking.TotalMilliseconds);
Console.WriteLine("detachedEntityDisabledTrackingAvg: {0} ms.", detachedEntityDisabledTrackingAvg);
var detachedEntityNormalAvg = results.Average(x => x.DetachedEntityNormal.TotalMilliseconds);
Console.WriteLine("detachedEntityNormalAvg: {0} ms.", detachedEntityNormalAvg);
var listDisabledTrackingAvg = results.Average(x => x.ListDisabledTracking.TotalMilliseconds);
Console.WriteLine("listDisabledTrackingAvg: {0} ms.", listDisabledTrackingAvg);
var listNormalAvg = results.Average(x => x.ListNormal.TotalMilliseconds);
Console.WriteLine("listNormalAvg: {0} ms.", listNormalAvg);
}
private static void updateDetachedEntityTotalPriceNormal()
{
using (var context = new MyContext(withTracking: true))
{
var detachedEntity = new Factor { Id = 1, TotalPrice = 10 };
var attachedEntity = context.Factors.Find(detachedEntity.Id);
if (attachedEntity != null)
{
attachedEntity.TotalPrice = 100;
context.SaveChanges();
}
}
}
private static void updateDetachedEntityTotalPriceDisabledTracking()
{
using (var context = new MyContext(withTracking: false))
{
var detachedEntity = new Factor { Id = 2, TotalPrice = 10 };
detachedEntity.TotalPrice = 200;
context.CustomUpdate(detachedEntity); // custom update with change tracking disabled.
context.SaveChanges();
}
}
private static void updateListTotalPriceNormal()
{
using (var context = new MyContext(withTracking: true))
{
foreach (var item in context.Factors)
{
item.TotalPrice += 10; // normal update with change tracking enabled.
}
context.SaveChanges();
}
}
private static void updateListTotalPriceDisabledTracking()
{
using (var context = new MyContext(withTracking: false))
{
foreach (var item in context.Factors)
{
item.TotalPrice += 10;
//نیازی به این دو سطر نیست
//context.ChangeTracker.DetectChanges(); // هربار باید محاسبه صورت گیرد در غیراینصورت وضعیت تغییر نیافته گزارش میشود
//context.CustomUpdate(item); // custom update with change tracking disabled.
}
context.ChangeTracker.DetectChanges(); // در غیراینصورت وضعیت تغییر نیافته گزارش میشود
context.SaveChanges();
}
}
private static void startDb()
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>());
// Forces initialization of database on model changes.
using (var context = new MyContext())
{
context.Database.Initialize(force: true);
}
}
}
public class PerformanceHelper
{
public static TimeSpan RunActionMeasurePerformance(Action action)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
action();
stopwatch.Stop();
return stopwatch.Elapsed;
}
}
}detachedEntityDisabledTrackingAvg: 22.32089 ms. detachedEntityNormalAvg: 54.546815 ms. listDisabledTrackingAvg: 413.615445 ms. listNormalAvg: 393.194625 ms.
T attachedEntity = set.Find(entity.Id); var attachedEntry = dbContext.Entry(attachedEntity); attachedEntry.CurrentValues.SetValues(entity);
dbContext.Entry(entity).State = EntityState.Modified;
public void InsertOrUpdate(Core.Models.PersonelAction personelAction {
if (personelAction.Id == default(int))
{
_unitOfWork.Entry(personelAction).State = EntityState.Added;
}
else
{
_unitOfWork.Entry(personelAction).State=EntityState.Modified;
}
}یعنی الان یک Context به ازای کل برنامه دارید که دچار این تداخل شدید؟ معمولا در WPF و همچنین WinForms این Context به ازای هر فرم تعریف میشود و با بسته شدن آن تخریب.
حالا یک سؤال مهم! به نظر شما در اولین سؤالی که پرسیدید، یک شخص چطور میبایستی ساختار کار شما رو که بر مبنای یک Context در کل برنامه است، حدس میزد و عیب یابی میکرد؟!
قسمت Find متد Update شما در حالت detached اضافی است. یعنی اگر میدونید که این Id در دیتابیس وجود داره نیازی به Findاش نیست. فقط State اون رو تغییر بدید کار میکنه.آیا این قسمت از کد برای تشخیص objectهای تکراری در گراف objectهای ما نیست؟ ونبایستی uncomment شود؟ طبق این لینک .