نیاز به sql server
Value
// Behavior 1 object obj = null; bool objValueEqual = obj.Equals(null); // Behavior 2 object obj = null; Type objType = obj.GetType(); // Behavior 3 string str = (string)null; bool strType = str is string; // Behavior 4 int num = 5; Nullable<int> nullableNum = 5; bool typeEqual = num.GetType() == nullableNum.GetType(); // Behavior 5 Type inType = typeof(int); Type nullableIntType = typeof(Nullable<int>); bool typeEqual = inType == nullableIntType;
- در رفتار اول هرچند که متد Equals از شی null در دسترس است و با مقدار null مقایسه شده اما در زمان اجرا پیغام خطای NullReferenceException را خواهیم داشت.
- در رفتار دوم هم پیغام خطا را خواهیم داشت. شئ با مقدار null، در زمان اجرا هیچ نوعی را برنمیگرداند.
- در رفتار سوم هر چند که مقدار null صریحا به رشته تبدیل شده و برای چاپ متغیر str پیام خطایی را نخواهیم داشت، اما متغیر strType در خروجی، false خواهد بود. همانطور که در رفتار دوم گفته شد، شیء با مقدار null هیچ نوعی را برنمیگرداند.
- خروجی رفتار چهارم true خواهد بود. به این صورت که هر دو از نوع System.int32 خواهند بود.
- در رفتار پنجم اگر از نوعها، خروجی جداگانه بگیریم، خواهیم دیدکه نوع int از System.int32 و <Nullable<int از نوع System.Nullable`1[System.Int32] میباشند، در نتیجه خروجی false است. اشیای nullable بعد از اینکه مقداری مشخص را دریافت کردند، به صورت یک شیء غیر nullable رفتار خواهند کرد.
مدیریت مقادیر null در سربارگذاری متدها
static void Main(string[] args) { Console.WriteLine(Method(null)); Console.ReadLine(); } private static string Method(object obj) { return "Object parameter"; } private static string Method(string str) { return "String parameter"; }
رفتارهای ()Math.Round
var rounded = Math.Round(1.5); // 2 var rounded = Math.Round(2.5); // 2 var rounded = Math.Round(2.5, MidpointRounding.ToEven); // 2 var rounded = Math.Round(2.5, MidpointRounding.AwayFromZero); // 3 var value = 1.4f; var rounded = Math.Round(value + 0.1f); // 1
مقدار دهی اولیه کلاسها
- فیلدهای استاتیک (زمانیکه کلاس برای اولین بار در دسترس قرار میگیرد)
- سازنده استاتیک (زمانیکه کلاس برای اولین بار در دسترس قرار میگیرد)
- فیلدهایی از کلاس که در نمونه ساخته شده در دسترس قرار میگیرند.
- سازنده کلاس که در زمان ایجاد یک نمونه از کلاس در دسترس قرار میگیرد.
public static class Config { public static bool ThrowException { get; set; } = true; } public class FailingClass { static FailingClass() { if (Config.ThrowException) { throw new InvalidOperationException(); } } }
try { var failedInstance = new FailingClass(); } catch (TypeInitializationException) { } Config.ThrowException = false; var instance = new FailingClass();
public class BaseClass { { public BaseClass() { VirtualMethod(1); } public virtual int VirtualMethod(int dividend) { return dividend / 1; } } public class DerivedClass : BaseClass { int divisor; public DerivedClass() { divisor = 1; } public override int VirtualMethod(int dividend) { return base.VirtualMethod(dividend / divisor); } }
چند ریختی
class Program { static void Main(string[] args) { var instance = new DerivedClass(); var result = instance.Method(); result = ((BaseClass)instance).Method(); Console.WriteLine(instance + " -> " + result); // Derived Class ... -> Method in BaseClass Console.ReadLine(); } } public class BaseClass { public virtual string Method() { return "Method in BaseClass"; } } public class DerivedClass : BaseClass { public override string ToString() { return "Derived Class ... "; } public new string Method() { return "Method in DerivedClass"; } }
class Program { static void Main(string[] args) { var instance = new DerivedClass(); var result = instance.Method(); // -> Method in DerivedClass result = ((IInterface)instance).Method(); // -> Method belonging to IInterface Console.WriteLine(result); Console.ReadLine(); } } public interface IInterface { string Method(); } public class DerivedClass : IInterface { public string Method() { return "Method in DerivedClass"; } string IInterface.Method() { return "Method belonging to IInterface"; } }
Iterators
private IEnumerable<int> GetEnumerable(StringBuilder log) { using (var test = new Test(log)) { return Enumerable.Range(1, 5); } }
var log = new StringBuilder(); foreach (var number in GetEnumerable(log)) { log.AppendLine($"{number}"); }
Created Disposed 1 2 3 4 5
using (var test = new Test(log)) { foreach (var i in Enumerable.Range(1,5)) { yield return i; } }
Created 1 2 3 4 5 Disposed
در فریمورک NET. ابزارهای مختلفی برای کار با دادههای XML در نظر گرفته شدهاست که بعد از نسخه 3.5 آن، انتخاب اول LINQ to XML می باشد. در این مطلب قصد داریم API ای را برای خواندن اطلاعات فایلهای XML با استفاده از LINQ to XML و انقیاد پویا در سیشارپ (Dynamic Binding) تهیه کنیم.
راه حل اول: استفاده از ExpandoObject
public static class ExpandoXml { public static dynamic AsExpando(this XDocument document) { return CreateExpando(document.Root); } private static dynamic CreateExpando(XElement element) { var result = new ExpandoObject() as IDictionary<string, object>; if (element.Elements().Any(e => e.HasElements)) { var list = new List<ExpandoObject>(); result.Add(element.Name.ToString(), list); foreach (var childElement in element.Elements()) { list.Add(CreateExpando(childElement)); } } else { foreach (var leafElement in element.Elements()) { result.Add(leafElement.Name.ToString(), leafElement.Value); } } return result; } }
در تکه کد بالا از طریق متد CreateExpando به صورت بازگشتی ابتدا بررسی میشود که آیا عنصر جاری دارای عناصری میباشد و همچنین آیا آنها دارای فرزند میباشند یا خیر؛ در صورت برقراری شرط، نتیجهی اجرای متد CreateExpando بر روی تک تک عناصر فرزند را درون لیستی از ExpandoObject قرار داده و سپس آن لیست نیز به عنوان Value عنصر جاری در نظر گرفته میشود. در صورت عدم برقراری شرط مذکور، مقادیر مربوط به عناصر فرزند را در قالب یک ExpandoObject به عنوان خروجی بازگشت خواهد داد.
راه حل دوم: استفاده از DynamicObject
public class DynamicXml : DynamicObject, IEnumerable { private readonly dynamic _xml; public DynamicXml(string fileName) { _xml = XDocument.Load(fileName); } public DynamicXml(dynamic xml) { _xml = xml; } public IEnumerator GetEnumerator() { foreach (var item in _xml.Elements()) { yield return new DynamicXml(item); } } public override bool TryGetMember(GetMemberBinder binder, out object result) { var xml = _xml.Element(binder.Name); if (xml != null) { result = new DynamicXml(xml); return true; } var attribute = _xml.Attribute(binder.Name); if (attribute != null) { result = new DynamicXml(attribute); return true; } result = null; return false; } public static implicit operator string(DynamicXml xml) { return xml._xml.Value; } }
کلاس DynamicXml از طریق سازنده اول، نام فایل را دریافت کرده و از طریق LINQ to XML با استفاده از متد Load کلاس XDocument، فایل مورد نظر بارگذاری شده و درون فیلدی به نام xml_ از نوع dynamic نگه داشته میشود. کار بعدی، بازنویسی متد TryGetMember میباشد. در بدنه بازنویسی شده این متد ابتدا بررسی میشود که آیا با نام خصوصت درخواست شده عنصری در داده XML وجود دارد یا خیر؛ در صورت موجود بودن، پارامتر result با یک وهله جدید از DynamicXml مقدار دهی میشود که عنصر یافت شده از طریق سازنده دوم، به عنوان داده xml برای مقدار دهی فیلد xml_ به عنوان آرگومان ارسال میشود. در صورت عدم وجود عنصر مذکور، بدنبال خصوصیتی با آن نام بوده و در صورت یافت شدن، باز به عنوان یک وهله DynamicXml برای مقدار دهی result استفاده میشود.
در ادامه برای نسبت دادن یک وهله از DynamicXml به یک متغیر string و دستیابی به مقدار یک عنصر که از طریق خصوصیت، درخواست میشود نیاز است تا اپراتور ضمنی string را نیز برای کلاس بالا نظر بگیریم. همچنین برای ایجاد امکان پیمایش برروی عناصر فرزند از طریق foreach، لازم است واسط IEnumerable را نیز پیاده سازی کرده باشیم.
طریقه استفاده
class Program { static void Main(string[] args) { var doc1 = XDocument.Load("Employees.xml"); foreach (var element in doc1.Element("Employees").Elements("Employee")) { Console.WriteLine(element.Element("FirstName").Value); } dynamic doc2 = XDocument.Load("Employees.xml").AsExpando(); foreach (var employee in doc2.Employees) { Console.WriteLine(employee.FirstName); } dynamic doc3 = new DynamicXml("Employees.xml"); foreach (var employee in doc3.Employees) { Console.WriteLine(employee.FirstName); Console.WriteLine(employee.Id); } } }
<?xml version="1.0" encoding="utf-8" ?> <Employees> <Employee Id="1"> <FirstName> Employee1 </FirstName> </Employee> <Employee Id="2"> <FirstName> Employee2 </FirstName> </Employee> <Employee Id="3"> <FirstName> Employee3 </FirstName> </Employee> <Employee Id="4"> <FirstName> Employee4 </FirstName> </Employee> </Employees>
private static void seedDb(ApplicationDbContext context) { if (!context.Chapters.Any()) { var user1 = context.Users.Add(new User { Name = "Test User" }); context.Chapters.Add(new Chapter { Title = "Learn SQlite FTS5", Text = "This tutorial teaches you how to perform full-text search in SQLite using FTS5", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "Advanced SQlite Full-text Search", Text = "Show you some advanced techniques in SQLite full-text searching", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "SQLite Tutorial", Text = "Help you learn SQLite quickly and effectively", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "Handle markup in text", Text = "<p>Isn't this <font face=\"Comic Sans\">funny</font>?", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "آزمایش متن فارسی", Text = "برای نمونه تهیه شدهاست", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "Exclude test 1", Text = "in the years 2018-2019 something happened.", User = user1.Entity }); context.Chapters.Add(new Chapter { Title = "Exclude test 2", Text = "It was 2018 and then it was 2019", User = user1.Entity }); context.SaveChanges(); } }
ثبت اطلاعات فوق، چنین رکوردهایی را در جدول Chapters به وجود میآورد که شامل اطلاعات یونیکد، HTML ای و غیره است:
اجرای اولین کوئری بر روی جدول مجازی Chapters_FTS به صورت مستقیم
کوئریهای Full-text در SQLite، چنین شکل کلی را دارند و توسط تابع match انجام میشوند:
select * from Chapters_FTS where Chapters_FTS match "fts5"
همانطور که مشاهده میکنید در اینجا تنها دو ستونی که ایندکس شدهاند، در خروجی نهایی ظاهر میشوند؛ اما این جدول به همراه ستونهای مخفی توکار دیگری نیز هست:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "fts5"
- Rowid با توجه به تعریفی که در قسمت قبل انجام دادیم:
CREATE VIRTUAL TABLE "Chapters_FTS" USING fts5("Text", "Title", content="Chapters", content_rowid="Id")
- تمام جداول مجازی FTS، به همراه ستون مخفی rank نیز هستند که میزان نزدیک بودن خروجی حاصل را به کوئری درخواستی مشخص میکنند. این عدد توسط تابعی به نام bm25 تهیه میشود. اگر کوئری FTS به همراه قسمت where نباشد، مقدار rank همواره نال خواهد بود. اما اگر قسمت where به همراه match قید شود، مقدار rank، مقدار از پیش محاسبه شدهی تابع توکار bm25 است. به همین جهت کار با این مقدار از پیش محاسبه شده، سریعتر از فراخوانی مستقیم متد bm25 است. برای مثال دو کوئری زیر اساسا یکی هستند؛ اما دومی سریعتر است:
select * from Chapters_FTS where Chapters_FTS match "fts5" ORDER BY bm25(fts); select * from Chapters_FTS where Chapters_FTS match "fts5" ORDER BY rank;
یک نکته: کوئری FTS فوق بر روی هر دو ستون title و text اجرا میشود (و یا هر ستون موجود دیگری که پیشتر ایندکس شده باشد).
اجرای اولین کوئری بر روی جدول مجازی Chapters_FTS توسط EF Core
پس از آشنایی مقدماتی با کوئری نویسی FTS در SQLite، بر انجام یک چنین کوئری در EF Core میتوان به صورت زیر عمل کرد:
- ابتدا باید یک موجودیت بدون کلید را مطابق ستونهای مخفی و ایندکس شدهی بازگشتی تهیه کنیم:
namespace EFCoreSQLiteFTS.Entities { public class ChapterFTS { public int RowId { get; set; } public decimal? Rank { get; set; } public string Title { get; set; } public string Text { get; set; } } }
- سپس نیاز است این موجودیت بدون کلید را به EF معرفی کنیم:
namespace EFCoreSQLiteFTS.DataLayer { public class ApplicationDbContext : DbContext { //... protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); builder.Entity<ChapterFTS>().HasNoKey().ToView(null); } //... } }
- و در آخر روش کوئری گرفتن از جدول مجازی FTS در EF Core به صورت زیر میباشد که توسط متد FromSqlRaw به صورت پارامتری (مقاوم در برابر حملات تزریق اسکیوال)، قابل انجام است:
const string ftsSql = "SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH {0}"; foreach (var chapter in context.Set<ChapterFTS>().FromSqlRaw(ftsSql, "fts5")) { Console.WriteLine($"Title: {chapter.Title}"); Console.WriteLine($"Text: {chapter.Text}"); }
بررسی قابلیتهای ویژهی کوئریهای FTS در SQLite
اکنون که با روش کلی کوئری گرفتن از جدول مجازی FTS آشنا شدیم، نکات ویژهی آنرا بررسی میکنیم و در اینجا بیشتر پارامتر ذکر شدهی پس از عملگر match تغییر خواهد کرد و مابقی قسمتهای آن ثابت و مانند قبل هستند.
بجای عملگر match میتوان از = نیز استفاده کرد
دو کوئری زیر دقیقا به یک معنا هستند:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "fts5"; SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS = "fts5";
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "fts5" ORDER by rank;
جستجوهایی به همراه واژههایی در کنار هم
از دیدگاه FTS، دو کوئری زیر که در قسمت match آنها، واژهها با فاصله در کنار هم قرار گرفتهاند، یکی هستند:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "learn SQLite" ORDER by rank; SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "learn + SQLite" ORDER by rank;
علت اینجا است که یک full-text search بر اساس ایندکس شدن واژهها تولید میشود و هر کدام از این واژهها به یک توکن نگاشت خواهند شد. به همین جهت است که در اینجا تفاوتی بین + و فاصله در عبارت جستجو شده وجود ندارد. در این حالت اگر در یکی از ستونهای ایندکس شده، واژهی learn و یا واژهی SQLite بکار رفته باشد، در خروجی نهایی لیست خواهد شد.
امکان جستجو بر اساس پیشوندها
میتوان با استفاده از *، تمام توکنهای ایندکس شده و شروع شدهی با واژهی مشخصی را جستجو کرد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "search*" ORDER by rank;
امکان استفاده از عملگرهای بولی NOT، AND و OR
اگر learn text را جستجو کنیم:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "learn text" ORDER by rank;
رکوردی با ID مساوی 1 بازگشت داده میشود. اما اگر نیاز باشد رکوردی بازگشت داده شود که حاوی learn باشد، اما text خیر، میتوان از عملگر NOT استفاده کرد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "learn NOT text" ORDER by rank;
که اینبار رکوردی با ID مساوی 3 را بازگشت دادهاست.
نکتهی مهم: عملگرهای بولی FTS مانند AND، OR، NOT و غیره باید با حروف بزرگ قید شوند.
در ادامه مثال دیگری از ترکیب عملگرهای بولی را مشاهده میکنید:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "search AND sqlite OR help" ORDER by rank;
که تقدم و تاخر این عملگرها را میتوان توسط پرانتزها به صورت صریحی نیز مشخص کرد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "search AND (sqlite OR help)" ORDER by rank;
امکان ذکر صریح ستونهای مدنظر در کوئری
همانطور که عنوان شد، حالت پیشفرض جستجوهای تمام متنی، جستجوی واژهی مدنظر در تمام ستونهای ایندکس شدهاست؛ اما شاید این مورد مدنظر شما نباشد. به همین منظور میتوان ابتدا نام ستون مدنظر را ذکر کرد و پس از آن یک : را قرار داد تا فقط جستجو بر روی آن ستون خاص صورت گیرد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "text:some AND title:sqlite" ORDER by rank;
امکان ترکیب نام ستونها به صورت {col2 col1 col3} نیز وجود دارد.
نکتهی مهم! در جستجوهای FTS در SQLite، ذکر - به معنای قید صریح نام یک ستون خاص است (و یا لیست ستونهایی به صورت {col2 col1 col3}-) که قرار نیست چیزی با آن(ها) انطباق داده شود (- شبیه به عملگر NOT عمل میکند؛ اینبار در مورد ستونها) و این مورد عموما تازهکاران را به اشتباه میاندازد. برای مثال در ابتدای بحث، دو رکورد را که دارای text ای مساوی عبارات زیر هستند، ثبت کردیم:
"in the years 2018-2019 something happened" "It was 2018 and then it was 2019"
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "2018-2019" ORDER by rank;
Execution finished with errors. Result: no such column: 2019
و یا میتوان عبارت جستجو شده را بین "" قرار داد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH '"2018-2019"' ORDER by rank;
و یا حتی میتوان '"2018 2019"' را نیز جستجو کرد که نتیجهی مشابهی را ارائه میدهد.
امکان جستجوی بر روی عبارات یونیکد
FTS5 و آخرین نگارش SQLite، به همراه tokenizer مخصوص یونیکد نیز هست و با اینگونه جستجوهای تمام متنی، مشکلی ندارد:
SELECT rowid, title, text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "آزمایش" ORDER by rank;
توابع کمکی FTS در SQLite برای متمایز سازی عبارات یافت شدهی در متن
فرض کنید میخواهیم واژهی fts5 را جستجو کرده و همچنین در خروجی نهایی، هرجائیکه fts5 قرار دارد، آنرا به صورت bold نمایش دهیم. برای اینکار، تابع توکار highlight قابل استفادهاست. اما اگر در این بین خواستیم فقط قسمت کوتاهی از متن مورد نظر را که به جستجوی ما نزدیک است نمایش دهیم، میتوان از متد توکار snippet استفاده کرد:
SELECT rowid, highlight(Chapters_FTS, title, '<b>', '</b>') as title, snippet(Chapters_FTS, text, '<b>', '</b>', '...', 64) as text, rank FROM Chapters_FTS WHERE Chapters_FTS MATCH "fts5" ORDER BY rank
نکتهی مهم: چون بر اساس نکات قسمت قبل، متنی که به Chapters_FTS ارسال میشود، نرمال سازی شدهاست، متدهای فوق کارآیی خودشان را از دست میدهند. برای مثال اگر در کوئری فوق، واژهی funny را که به یک رکورد HTML ای اشاره میکند، جستجو کنیم، خروجی زیر را دریافت خواهیم کرد:
خروجی نهایی، چون به جدول اصلی chapters متصل است، اصل متن را بازگشت میدهد، اما چون اطلاعاتی را که به Chapters_FTS ارسال کردهایم، فاقد تگهای HTML هستند، تا خروجی دقیقی حاصل شود، متدهای highlight و snippet دیگر قادر به علامتگذاری خروجی نهایی نبوده و اینکار را باید خودمان به صورت دستی در سمت کلاینت انجام دهیم.
public enum Grade { Failing, // = 0 BelowAverage, // = 1 Average, // = 2 VeryGood, // = 3 Excellent // = 4 }
- در این حالت متد ()ToString نوع داده Enum عنوان مقادیر ثابت را بر میگرداند.
- جهت برگشت مقدار عددی و شماره مقادیر ثابتهای تعریف شده از متد ()ToString با فرمت D (شماره مقدار را بصورت Decimal نشان میدهد) و فرمت X جهت نمایش بصورت هگزا میتوان استفاده کرد.
- روش عرف برای نمایش مقدار عددی استفاده از تبدیل نوع صریح به int است.
static void Main(string[] args) { Grade grade = Grade.Average; Console.WriteLine(grade.ToString()); // Print Avarage Console.WriteLine(grade.ToString("D")); // Print 2 Console.WriteLine(grade.ToString("X")); // Print 00000002 Console.WriteLine((int) grade); //Print 2 Console.ReadKey(); }
public enum Grade { Failing = 5, BelowAverage = 10, Average = BelowAverage + 5, // = 15 VeryGood = 18, Excellent = 20 }
public enum Grade : byte { Failing = 5, BelowAverage = 10, Average = BelowAverage + 5, // = 15 VeryGood = 18, Excellent = 20 }
Console.WriteLine(grade.ToString("X")); // Print 0F
Console.WriteLine("Underlying type: {0}", Enum.GetUnderlyingType(grade.GetType())); // Print System.Byte Console.WriteLine("Type Code : {0}", grade.GetTypeCode()); // Print Byte
Console.WriteLine("Value : {0}", (int)grade); // Print 15
مقایسه کارآیی ORMهای مطرح دات نت
مشکل مهم این روش عدم سازگاری کامل آن با EF است. برای مثال در آن تفاوتی بین (Include(x=>x.Tags و (Include(x=>x.Users وجود ندارد. به همین جهت در این نوع موارد، قادر به تولید کلید منحصربفردی جهت کش کردن اطلاعات یک کوئری مشخص نیست. در اینجا یک کوئری LINQ، به معادل رشتهای آن تبدیل میشود و سپس Hash آن محاسبه میگردد. این هش، کلید ذخیره سازی اطلاعات حاصل از کوئری، در سیستم کش خواهد بود. زمانیکه دو کوئری Include دار متفاوت EF، هشهای یکسانی را تولید کنند، عملا این سیستم کش، کارآیی خودش را از دست میدهد. برای رفع این مشکل پروژهی دیگری به نام EF cache ارائه شدهاست. این پروژه بسیار عالی طراحی شده و میتواند جهت ایده دادن به تیم EF نیز بکار رود. اما در آن فرض بر این است که شما میخواهید کل سیستم را در یک کش قرار دهید. وارد مکانیزم DBCommand و DataReader میشود و در آنجا کار کش کردن تمام کوئریها را انجام میدهد؛ مگر آنکه به آن اعلام کنید از کوئریهای خاصی صرفنظر کند.
با توجه به این مشکلات، روش بهتری برای تولید هش یک کوئری LINQ to Entities بر اساس کوئری واقعی SQL تولید شده توسط EF، پیش از ارسال آن به بانک اطلاعاتی به صورت زیر وجود دارد:
private static ObjectQuery TryGetObjectQuery<T>(IQueryable<T> source) { var dbQuery = source as DbQuery<T>; if (dbQuery != null) { const BindingFlags privateFieldFlags = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public; var internalQuery = source.GetType().GetProperty("InternalQuery", privateFieldFlags) .GetValue(source); return (ObjectQuery)internalQuery.GetType().GetProperty("ObjectQuery", privateFieldFlags) .GetValue(internalQuery); } return null; }
این اطلاعات، پایهی تهیهی کتابخانهی جدیدی به نام EFSecondLevelCache گردید. برای نصب آن کافی است دستور ذیل را در کنسول پاورشل نیوگت صادر کنید:
PM> Install-Package EFSecondLevelCache
var products = context.Products.Include(x => x.Tags).FirstOrDefault();
var products = context.Products.Include(x => x.Tags).Cacheable().FirstOrDefault(); // Async methods are supported too.
پس از آن نیاز است کدهای کلاس Context خود را نیز به نحو ذیل ویرایش کنید (به روز رسانی شدهی آن در اینجا):
namespace EFSecondLevelCache.TestDataLayer.DataLayer { public class SampleContext : DbContext { // public DbSet<Product> Products { get; set; } public SampleContext() : base("connectionString1") { } public override int SaveChanges() { return SaveAllChanges(invalidateCacheDependencies: true); } public int SaveAllChanges(bool invalidateCacheDependencies = true) { var changedEntityNames = getChangedEntityNames(); var result = base.SaveChanges(); if (invalidateCacheDependencies) { new EFCacheServiceProvider().InvalidateCacheDependencies(changedEntityNames); } return result; } private string[] getChangedEntityNames() { return this.ChangeTracker.Entries() .Where(x => x.State == EntityState.Added || x.State == EntityState.Modified || x.State == EntityState.Deleted) .Select(x => ObjectContext.GetObjectType(x.Entity.GetType()).FullName) .Distinct() .ToArray(); } } }
کدهای کامل این پروژه را از مخزن کد ذیل میتوانید دریافت کنید:
EFSecondLevelCache
پ.ن.
این کتابخانه هم اکنون در سایت جاری در حال استفاده است.
namespace ReUsableQueries.Model { public class Student { public int Id { get; set; } public string Name { get; set; } public string LastName { get; set; } public int Age { get; set; } [ForeignKey("BornInCityId")] public virtual City BornInCity { get; set; } public int BornInCityId { get; set; } } public class City { public int Id { get; set; } public string Name { get; set; } public virtual ICollection<Student> Students { get; set; } } }
using System.Data.Entity; using ReUsableQueries.Model; namespace ReUsableQueries.DAL { public class MyContext : DbContext { public DbSet<City> Cities { get; set; } public DbSet<Student> Students { get; set; } } }
public class Configuration : DbMigrationsConfiguration<MyContext> { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } protected override void Seed(MyContext context) { var city1 = new City { Name = "city-1" }; var city2 = new City { Name = "city-2" }; context.Cities.Add(city1); context.Cities.Add(city2); var student1 = new Student() {Name = "Shaahin",LastName = "Kiassat",Age=22,BornInCity = city1}; var student2 = new Student() { Name = "Mehdi", LastName = "Farzad", Age = 31, BornInCity = city1 }; var student3 = new Student() { Name = "James", LastName = "Hetfield", Age = 49, BornInCity = city2 }; context.Students.Add(student1); context.Students.Add(student2); context.Students.Add(student3); base.Seed(context); } }
var context = new MyContext(); var query= context.Students.Where(x => x.Name.Contains(name)).Where(x => x.LastName.Contains(lastName)).Where( x => x.Age == age);
var query= context.Students.Where(x => x.Name.Contains(name)).Where(x => x.LastName.Contains(lastName)).Where( x => x.Age == age).OrderBy(x=>x.LastName).Skip(skip).Take(take);
var query = context.Students.Where(x => x.Name.Contains(name)).Where(x => x.LastName.Contains(lastName)).Where ( x => x.Age == age).Where(x => x.BornInCityId == 1).OrderBy(x => x.Age);
namespace ReUsableQueries.Quries { public static class StudentQueryExtension { public static IQueryable<Student> FindStudentsByName(this IQueryable<Student> students,string name) { return students.Where(x => x.Name.Contains(name)); } public static IQueryable<Student> FindStudentsByLastName(this IQueryable<Student> students, string lastName) { return students.Where(x => x.LastName.Contains(lastName)); } public static IQueryable<Student> SkipAndTake(this IQueryable<Student> students, int skip , int take) { return students.Skip(skip).Take(take); } public static IQueryable<Student> OrderByAge(this IQueryable<Student> students) { return students.OrderBy(x=>x.Age); } } }
var query = context.Students.FindStudentsByName(name).FindStudentsByLastName(lastName).SkipAndTake(skip,take);
var query = context.Students.AsQueryable(); if (searchByName) { query= query.FindStudentsByName(name); } if (orderByAge) { query = query.OrderByAge(); } if (paging) { query = query.SkipAndTake(skip, take); } return query.ToList();