Dapper یک mini-ORM سریع

۱۳۹۱/۰۲/۲۹ ۱۹:۵۴ Salar Khalilzadeh https://plus.google.com/105397214522932500988 منتشر شده در تاریخ : ۱۳۹۱/۰۲/۲۹ دسته بندی : ، ، ، 3

اخیرا در حال کار بر روی سرویس تحت وبی بودم که برخلاف حجم کوچک پروژه کار خیلی زیادی رو انجام میده و ترافیک زیادی قراره روی اون سوار بشه.

به علت فشاری که سرویس قرار بود متحل بشه مشغول بهینه سازی عملکرد بخش های مختلف بودم. این فشار حداقل 100 درخواست در ثانیه از مجموع 10 کاربر همزمان برای شروع کار در نظر گرفته شده بود. در چنین حجم کاری هر میلی ثانیه نیز با ازش هست. در حین بررسی اجرای برنامه متوجه تاخیرهای بیش از حد و غیر عادی عملیات دیتابیس شدم. برای کاهش تاخیر سعی شد تا عملیات به روز رسانی و درج در دیتابیس غیر همزمان انجام شود(با Thread Pooling توسط BeginInvoke ساده)، اما این کار ممکنه ایجاد تداخل کنه و همیشه قابل انجام نیست.
در کل تاخیر های انجام شده توسط EntityFramework محسوس بودند. بنچمارک های انجام شده نیز این مطلب را نشان می دهد که EF کندتر از سایر orm ها عمل می کند. این کندی به چه معناست؟ یعنی برای انجام 500 درخواست متوالی EF حدود 600 میلی ثانیه زمان مصرف می کند، در حالی کار به ado.net ساده زمانی حدود 44 میلی ثانیه لازم دارد.
البته این برای برنامه های دسکتاب اصلا به چشم نخواهد آمد، چون اصلا کاربر بیش از چند نفر نمی شود.
در این بین مدل 3Tier که توسط این برنامه تولید می شود یک گزینه موجود بود اما مناسب نبود. این مدل نیاز به کار با StoredProcedures ها دارد، در حال حاظر مقدور نبود از sp ها استفاده کنم و همچنین وابستگی شدیدی به مدل(database model) خود دارد و چند محدودیت دیگر. در حین جستجو برای بهترین نتایج، Dapper جالب به نظر رسید.
Dapper یک نیمچه ORM که به علت سرعت بالایی که داره انتخاب من شد. کارکرد Dapper بسیار جالب هست، یک فایل را به پروژه اضافه می کنید و تمام. با استفاده از چند متن افزوده به connection دیتابیس به راحتی امکان دریافت نتایج و مپ کردن آنها به کلاسهای مدل دیتابیس را فراهم میکنه.
استفاده از آن هم به سادگی کد زیر هست:
using (var conn = new SqlConnection(myConnectionString)) {
     conn.Open();
     Account result = conn.Query<account>(@"SELECT * FROM Account WHERE Id = @Id", new {  Id = Id }).FirstOrDefault();
     ...
}
همانطور که می بینید با استفاده از عبارات sql فراخوانی ها مستقیما انجام میشود. پارامتر دوم پارامترهای کوئری است که از نوع dynamic تعریف شده و هر نوع شیئی را می پذیرد.
امضاهای مختلفی از این متد وجود دارد. در صورتی که نیاز باشد تا فقط چند ستون از جدول انتخاب شود از امضای زیر که خروجی dynamic دارد استفاده می کنیم:

dynamic account = conn.Query<dynamic>(@"SELECT Name, Address, Country FROM Account WHERE Id = @Id", new { Id = Id }).FirstOrDefault();
Console.WriteLine(account.Name);
Console.WriteLine(account.Address);
Console.WriteLine(account.Country);
براس آشنایی بیشتر با چند متدهای دیگر به صفحه Dapper مراجعه کنید.
کارایی dapper برای من سوال بود و زیاد به بنچ مارکهای دیگران اعتماد نداشتم به همین جهت به گفته ها بسنده نکرده و در یک تست ساده select بر آن شدم تا مقایسه ای انجام بدم. و این هم از نتایج:
Selecting 1 record in 200 iterations
------------------------
EF CodeFirst total: 00:00:00.2616611, ms: 261
ActiveRecord total: 00:00:00.1116575, ms: 111
Dapper total:       00:00:00.0177814, ms: 17
------------------------
EF CodeFirst total: 00:00:00.2460143, ms: 246
ActiveRecord total: 00:00:00.1105966, ms: 110
Dapper total:       00:00:00.0183922, ms: 18
------------------------
EF CodeFirst total: 00:00:00.2454677, ms: 245
ActiveRecord total: 00:00:00.1116689, ms: 111
Dapper total:       00:00:00.0173781, ms: 17
------------------------
EF CodeFirst total: 00:00:00.2448425, ms: 244
ActiveRecord total: 00:00:00.1117898, ms: 111
Dapper total:       00:00:00.0174709, ms: 17
------------------------
EF CodeFirst total: 00:00:00.2478316, ms: 247
ActiveRecord total: 00:00:00.1104544, ms: 110
Dapper total:       00:00:00.0197321, ms: 19
هر فراخوانی 200 بار تکرار شده و کل آزمون رو 5 بار تکرار کردم. همانطور که می بینید EntityFramework بدترین نتیجه رو بدست آورده و البته این همه اختلاف جای تعجب دارد. در این تست کار مستقیم با ado.net رو پوشش ندادم ولی با توجه به نتایج تست خود صفحه dapper در 500 تکرار در حد 4 یا 5 میلی ثانیه اختلاف بین dapper و ado.net مسقیم وجود دارد.

به همین دلایل یک الگو برای SalarDbCodeGenerator مهیا کردم که تا من رو از خطر نگهداری و نوشتن کدهای SQL تاحدودی راحت کنه. این الگو یک کلاس میانی برای هر جدول دیتابیس ایجاد می کنه که علاوه بر مدیریت اتصال های پایگاه داده (dapper مدیریت اتصال پایگاه داده را انجام نمی دهد) متد های اصلی CRUD را دارا است. همچنین برای ارتباطات جداول و کلیدهای اندیس و یکتا(unique) متدهای کمکی را تولید می کند.
خلاصه با استفاده از این الگو بدون نیاز به هر کاری می توان کارهای ساده با جداول را با قدرت Dapper انجام داد.
نکته: لازم نیست که حتما مدلهای جداول این الگو را تولید کنید، می توان از مدلهای سایر ORM ها با اندکی تغییر استفاده کرد.
نمونه کد:
using (var pdap = new PersonDap())
using (var transaction = pdap.BeginTransaction())
using (var cdap = new CarDap(pdap))
{
 var person = pdap.GetByPersonID(10);
 var carList = cdap.GetByPersonID(person.PersonID);

 var newCar = new Car()
     {
         CarPlaque = "1982-92",
         Color = "White",
         ModelType = "BMW",
         PersonID = person.PersonID,
     };
 cdap.Insert(newCar);
 transaction.Commit();

 var bmwList = pdap.Query<Car>(
  CarDap.SqlSelectCommand + " WHERE ModelType=@ModelType",
  new {ModelType = "BMW"})
  .ToList();

 ....
}
تنها نکته لازم به توضیح، ثابت SqlSelectCommand هست، این ثابت و سه ثابت دیگر دستورات sql پیشفرض برای عملیات CRUD در پایگاه داده هستند که توسط generator تولید شده و همیشه به روز هستند و در همه جداول وجود دارند. می توانید دردسر نوشتن دستورات sql را تا حدودی کاهش دهید.

و  در آخر بهتر است بدانید که Dapper توسط Sam Saffron یکی از برنامه نویسان سایت StackOverflow و برای همان سایت توسعه داده شده و بعدا open-source شده است.
  • لینکهای مفید

*پ.ن: نسخه جدید SalarDbCodeGenerator در سایت قرار گرفت که همراه با این مدل به نام DapperAccess است، یک به روزرسانی برای مدل EF دارد. از صفحه پروژه قابل دریافت است.

به روزرسانی: همانطور که در متن توضیح دادم Dapper یک ORM کامل نیست، برای اینکه شبهه ای ایجاد نشه، عنوان مطلب اصلاح شد.

 

3 بازخورد برای “Dapper یک mini-ORM سریع”

  1. شايد بد نباشه براي مقايسه بهتر يك سري از قابليت‌هاي EF خاموش شود و بعد تست شود. چون اجراي مستقيم يك كوئري بر روي ديتابيس متفاوت است با كاري كه EF انجام مي‌دهد.
    - change tracking
    - 1st level cache
    - lazy loading
    - dynamic proxies
    - ترجمه كوئري LINQ به معادل SQL
    و ...
    اين‌ها رو بهتر است خاموش كرد و بعد تست كرد.

    يعني منصفانه نيست كه بگيم من ORM ايي دارم كه كوئري‌هاي LINQ رو ترجمه نمي‌كنه، Change tracking نداره، پشت صحنه قرار نيست يك سري dynamic proxy براي مباحث lazy loading ايجاد كنه و ... سريعتر است.

    پاسخحذف
  2. در این تست برای EFCodeFirst هر دو قابلیت LazyLoadin و ProxyCreating غیر فعال بودند. برای ActiveRecord هم همه قابلیت ها پیش فرض و فعال بودند. اینها رو ذکر نکردم چون باز هم تفاوت خیلی فاحش هست.
    و البته dapper به حساب orm کامل نیست اما اگر در مقام مقایسه باشیم باز هم ef کندتر است و activerecord این مسئله رو نشون میده.
    به نظرم فابلیت بیشتری نمی شه از dapper با توجه به نوع کاری که انجام میده انتظار داشت.

    پاسخحذف
  3. سلام، خسته نباشید. ببخشید من واسه پایان نامم باید یه سایت فروشگاه الکترونیکی طراحی کنم. میشه بگین چه طوری صفحه اول یا همون صفحه ورودتون رو که دوتا باتن داره ، رو طراحی کردین. لطفا کمکم کنین، من خیلی تجربم کمه و باید تا آخر مردادماه تمومش کنم. دعاگوتون هستم.

    پاسخحذف