1- من دقیقا متوجه نشدم منظور شما از decoupling اول مقاله چیه؟ منظورتون تفکیک Domain از DAL هست؟ اگر اینطوره چه ربطی به UoW و انواع پیاده سازی اون داره؟
اگر منظورشما انفکاک بین EFContext و Repository هست، توجه شما رو به این نکته جلب میکنم که StudentRepository که در اول مقاله آورده شده در حقیقت یک پیاده سازی برپایه EF هست به عبارتی EFStudentRepository اسم مناسبتری میتونه باشه. بنابراین تزریق Context با هیچ اصلی مغایر نیست. چرا که این Repository یک پیاده سازی خاص از IStudentRepository است.
2- وجود متد Save در Repository؟ نه تنها قابل قبول نیست که اصلا اگر قرار باشه هر Repository مستقلا Save رو صدا بزنه که مفهوم Transaction از بین میره یا حداقل سخت میشه بهش رسید.
3- با شما موافقم که Generic Repository ایده خوبی نیست. البته فقط تا اینجا موافقم که این الگو برای Expose کردن Interface یک Repository مناسب نیست. چه بسا Repository هایی که فقط SELECT میکنند. ولی اگر پیاده سازی خاصی از یک Repository مد نظر دارید (مثلا پیاده سازی برپایه EF یا NHibernate) اونوقت دقیقا چیزی که به کمک شما میاد همین Generic Repository برای جلوگیری از کدهای تکراریه.
4- اصولا Repository برای اینکه منطق برنامه (یا به قول شما منطق تجاری) رو پیاده سازی کنه نیست. در حقیقت لایه ای که استفاده کننده مستقیم از Repository است میداند که چه موقع به چه Repository فرمانهای CRUD بده تا منطق برنامه پیاده سازی بشه.
5- در واقع استفاده از امکانات هر ORM تا حد بینهایتی امکان پذیره به شرطی که ORM و توانمندیهاشو در همون لایه DAL محصور کنید مثلا IQueriable و Cachable و گرنه Leaky Abstraction به طور خزنده و ساکتی کل برنامه رو مثل سرطان در خودش میکشه.
نهایتا اینکه نمیشه یک پیاده سازی مشکل دار از مفهوم Repository + UoW رو بدون درنظر گرفتن مفاهیم مهمی مثل Service Layer و Domain Model نقد کرد و بعدا نتیجه گرفت که این الگوها صحیح نیستند. ضمن اینکه این موضوع بسته به تجربه و نظر هر برنامه نویس و معماری میتونه پیاده سازی خاص خودشو داشته باشه که من شخصا هنوز موارد جالب و جدیدی که یک برنامه نویس باهوش برداشت کرده رو میبینم و نتیجه میگیرم که مفهوم Repository + UoW در بین ماها هنوز به یک تعریف جهانشمول نرسیده.
بررسی متد های یک طرفه در WCF
» ابتدا یک Extension برای OperationContext تعریف میکنیم(با فرض اینکه IDatabaseContext نماینده کلاس DbContext پروژه است):
private class OperationContainerExtension : IExtension<OperationContext> { public OperationContainerExtension( IDatabaseContext dbContext, string contextKey ) { this.CurrentDbContext = dbContext; this.ContextKey = contextKey; } public IDatabaseContext CurrentDbContext { get; private set; } public string ContextKey { get; private set; } public void Attach( OperationContext owner ) { } public void Detach( OperationContext owner ) { } }
if ( OperationContext.Current != null ) { OperationContext.Current.Extensions.Add( new OperationContainerExtension( dbContext , CONTEXTKEY ) ); OperationContext.Current.OperationCompleted += CurrentOperationContext_OperationCompleted; OperationContext.Current.Channel.Faulted += Channel_Faulted; }
void Channel_Faulted( object sender, EventArgs e ) { IDatabaseContext dbContext = GetDbContext(); if ( dbContext != null ) { dbContext.Dispose(); GC.Collect(); } } private void CurrentOperationContext_OperationCompleted( object sender, EventArgs e ) { IDatabaseContext dbContext = GetDbContext(); if ( dbContext != null ) { dbContext.Dispose(); GC.Collect(); } }
protected override IDatabaseContext GetDbContext() { if ( OperationContext.Current != null ) { var operationContainerExtension = OperationContext.Current.Extensions.OfType<OperationContainerExtension>().FirstOrDefault( e => e.ContextKey == CONTEXTKEY ); if ( operationContainerExtension != null ) { return operationContainerExtension.CurrentDbContext; } return staticDbContext; } else return staticDbContext; }
private string CONTEXTKEY = Guid.NewGuid().ToString();
در مورد استفاده از GUID به جای identity باید به یک نکته هم اشاره کنم که در بیشتر مواقع اگر مقدار GUIDی که به ازای یک فیلد UNIQUEIDENTIFIER تنظبم میکنید به صورت SEQUNTIAL نباشد باعث Fragment شدن ایندکس خواهد شد.
برای مقایسه بهتر بین Fragmentation ایندکس مربوط به Identity و GUID به مثال زیر دقت کنید. هر دو مثال فیلد ID خود را به شکل Clustered Index دارند بعد از درج تعدادی رکورد مساوی در دو جدول Fragmentation مربوط به جدولی که دارای GUID است به شدت بالا است که این موضوع باعث کاهش کارایی خواهد شد
USE TEMPDB GO IF OBJECT_ID('TABLE_GUID')>0 DROP TABLE TABLE_GUID GO CREATE TABLE TABLE_GUID ( ID UNIQUEIDENTIFIER PRIMARY KEY, FirstName NVARCHAR(1000), LastName NVARCHAR(1000) ) GO IF OBJECT_ID('TABLE_IDENTITY')>0 DROP TABLE TABLE_IDENTITY GO CREATE TABLE TABLE_IDENTITY ( ID INT IDENTITY PRIMARY KEY, FirstName NVARCHAR(1000), LastName NVARCHAR(1000) ) GO INSERT INTO TABLE_GUID(ID,FirstName,LastName) VALUES (NEWID(),REPLICATE('FARID*',100),REPLICATE('Taheri*',100)) GO 10000 INSERT INTO TABLE_IDENTITY(FirstName,LastName) VALUES (REPLICATE('FARID*',100),REPLICATE('Taheri*',100)) GO 10000 --Fragmentation بررسی وضعیت SELECT * FROM sys.dm_db_index_physical_stats(DB_ID(),OBJECT_ID('TABLE_GUID'),NULL,NULL,'DETAILED') DBCC SHOWCONTIG(TABLE_GUID) GO SELECT * FROM sys.dm_db_index_physical_stats(DB_ID(),OBJECT_ID('TABLE_IDENTITY'),NULL,NULL,'DETAILED') DBCC SHOWCONTIG(TABLE_IDENTITY) GO
خوب برای اینکه Fragmentation این نوع جداول را رفع کنید چند راه داریم
1- تولید GUID به صورت Sequential (لازم میدانم اشاره کنم این قابلیت در SQL Server وجود دارد ولی مقدار تولید شده باید به شکل یک Default Constraint باشد که این موضوع نیازمند این است که شما اگر در سورس به این GUID نیاز پیدا کنید مجبور به زدن Select و... شوید. اگر بخواهید در سورس این کار را انجام دهید باید از Extentionهایی که برای اینکار وجود دارند استفاده کنید فکر کنم Nhibernate این حالت رو پشتیبانی کنه در مورد EF دقیقا اطلاع ندارم باید اهل فن نظر بدن)
2- تنظیم مقدار Fillfactor به ازای ایندکس
3-Rebuild و یا Reorganize دوره ای ایندکس
اما سمت کلاینت شما هر کاری را میتوانید انجام دهید. برای مثال زمان نمایش اطلاعات در WPF یا سیلورلایت از یک Converter استاندارد آن (با پیاده سازی اینترفیس IValueConverter) در حین Binding استفاده کنید. اگر با ASP.NET Webforms کار میکنید حین نمایش اطلاعاتی که هم اکنون در سمت کلاینت مهیا است ، مثلا جهت نمایش در یک GridView یا موارد مشابه شما خواهید داشت myFunc(Eval("field")) و شبیه به این که myFunc باید در کدبیهایند شما پیاده سازی شود. در سایر فناوریها که میتواند شامل موارد قبل هم باشند، نهایتا شما یک لیست دریافتی از سرور را دارید، یک حلقه با LINQ یا حالت معمولی تشکیل شده و مقادیر مدل مورد نظر ویرایش میشوند تا جهت نمایش مناسب شوند.
تمام اینها در حالتی است که قصد شما فقط و فقط تغییر نحوهی نمایش است. به عبارتی الان کل دیتای فیلتر شده سمت کاربر مهیا است. شما میخواهید به آن شکل دهید.
حالت دیگر (حالت غیر نمایشی و استفاده در کوئریها):
اگر با LINQ کمی بیشتر از اطلاعات موجود در وب کار کرده باشید احتمالا به این سوال رسیدهاید که آیا میشود متد سفارشی خودمان را هم حین تهیه کوئریهایی از این دست استفاده کنیم؟ چون فقط یک سری extension method مشخص بیشتر وجود ندارند. اگر من extension method سفارشی خودم را تهیه کردم چطور؟
این سوال دو پاسخ دارد:
- متدهای سفارشی شما حتما روی کل اطلاعات دریافتی از سرور کار میکنند؛ اما بهینه نیستند. چون برای مثال myFunc سی شارپ من معادل SQL ایی ندارد که بتوانم مستقیما آنرا سمت سرور اجرا کنم. چون نهایتا LINQ to NHibernate باید به SQL یا T-SQL ترجمه شود. به همین جهت مجبورم کل اطلاعات را دریافت کنم، مثلا 100 هزار رکورد، حالا که اشیاء دات نتی من تشکیل و کامل شده، متد سفارشی LINQ خودم را بر روی اینها اجرا میکنم. این روش کار میکنه ولی از لحاظ کارآیی فاجعه است.
- روش دیگر: در NH 3.0 این امکان وجود دارد ... بسط پروایدر LINQ آن با صور مختلف. که اگر وقت شد یک مطلب کامل در مورد آن خواهم نوشت.
حال اگر بخواهیم همین پروژه را به صورت سورس باز ارائه دهیم، استفاده کنندگان نهایی به مشکل برخواهند خورد؛ زیرا فایل pfx حاصل، توسط کلمه عبور محافظت میشود و در سایر سیستمها بدون درنظر گرفتن این ملاحظات قابل استفاده نخواهد بود.
معادل فایلهای pfx، فایلهایی هستند با پسوند snk که تنها تفاوت مهم آنها با فایلهای pfx، عدم محافظت توسط کلمه عبور است و ... برای کارهای خصوصا سورس باز انتخاب مناسبی به شمار میروند. اگر دقت کنید، اکثر پروژههای سورس باز دات نتی موجود در وب (مانند NHibernate، لوسین، iTextSharp و غیره) از فایلهای snk برای اضافه کردن امضای دیجیتال به کتابخانه نهایی تولیدی استفاده میکنند و نه فایلهای pfx محافظت شده.
در اینجا اگر فایل pfx ایی دارید و میخواهید معادل snk آنرا تولید کنید، قطعه کد زیر چنین امکانی را مهیا میسازد:
using System.IO; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; namespace PfxToSnk { class Program { /// <summary> /// Converts .pfx file to .snk file. /// </summary> /// <param name="pfxData">.pfx file data.</param> /// <param name="pfxPassword">.pfx file password.</param> /// <returns>.snk file data.</returns> public static byte[] Pfx2Snk(byte[] pfxData, string pfxPassword) { var cert = new X509Certificate2(pfxData, pfxPassword, X509KeyStorageFlags.Exportable); var privateKey = (RSACryptoServiceProvider)cert.PrivateKey; return privateKey.ExportCspBlob(true); } static void Main(string[] args) { var pfxFileData = File.ReadAllBytes(@"D:\Key.pfx"); var snkFileData = Pfx2Snk(pfxFileData, "my-pass"); File.WriteAllBytes(@"D:\Key.snk", snkFileData); } } }