در حالت Detached (مثل ایجاد یک شیء CLR ساده)
در متد Updateایی که نوشتید، قسمت Find حتما اتفاق میافته. چون Tracking خاموش هست (مطابق تنظیماتی که عنوان کردید)، بنابراین Find چیزی رو از کشی که وجود نداره نمیتونه دریافت کنه و میره سراغ دیتابیس.
ماخذ :
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.
حالا تصور کنید که در یک حلقه میخواهید 100 آیتم رو ویرایش کنید. یعنی 100 بار رفت و برگشت خواهید داشت با این متد Update سفارشی که ارائه دادید. البته منهای کوئریهای آپدیت متناظر. این 100 تا کوئری فقط Find است.
قسمت Find متد Update شما در حالت detached اضافی است. یعنی اگر میدونید که این Id در دیتابیس وجود داره نیازی به Findاش نیست. فقط State اون رو
تغییر بدید کار میکنه.
در حالت نه آنچنان Detached ! (دریافت یک لیست از Context ایی که ردیابی نداره)
با خاموش کردن Tracking حتما نیاز خواهید داشت تا متد context.ChangeTracker.DetectChanges رو هم پیش از ذخیره سازی یک لیست دریافت شده از بانک اطلاعاتی فراخوانی کنید. وگرنه چون این اطلاعات ردیابی نمیشوند، هر تغییری در آنها، وضعیت Unchanged رو خواهد داشت و نه Detached. بنابراین SaveChanges عمل نمیکنه؛ مگر اینکه DetectChanges فراخوانی بشه.
سؤال: این سربار که میگن چقدر هست؟ ارزشش رو داره که راسا خاموشش کنیم؟ یا بهتره فقط برای گزارشگیری این کار رو انجام بدیم؟
یک آزمایش:
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;
}
}
}
نتیجه این آزمایش بعد از 20 بار اجرا و اندازه گیری:
detachedEntityDisabledTrackingAvg: 22.32089 ms.
detachedEntityNormalAvg: 54.546815 ms.
listDisabledTrackingAvg: 413.615445 ms.
listNormalAvg: 393.194625 ms.
در حالت کار با یک شیء ساده، به روز رسانی حالت منقطع بسیار سریعتر است (چون یکبار رفت و برگشت کمتری داره به دیتابیس).
در حالت کار با لیستی از اشیاء دریافت شده از بانک اطلاعاتی، به روز رسانی حالت متصل به Context سریعتر است.