کوئری نویسی در EF Core - قسمت دوم - کوئری‌های ساده
اندازه‌ی قلم متن
تخمین مدت زمان مطالعه‌ی مطلب: هشت دقیقه

پس از تشکیل ساختار بانک اطلاعاتی و مقدار دهی اولیه‌ی آن در قسمت قبل، در ادامه به بررسی نحوه‌ی انجام تعدادی کوئری‌های ساده و ابتدایی با EF Core خواهیم پرداخت. در قسمت‌های بعدی حالت‌های پیچیده‌تری را بررسی می‌کنیم.


مثال 1: تمام اطلاعات یک جدول را دریافت کنید.

هدف دریافت تمام اطلاعات جدول facilities است.


برای انجام اینکار فقط کافی‌است بر روی DbSet متناظر با آن، متد ToList فراخوانی شود:
var facilities = context.Facilities.ToList();
حاصل آن، کوئری زیر خواهد بود که در آن، تمام ستون‌های جدول Facilities به صورت خودکار قید می‌شوند:


یک نکته: به فراخوانی متد ToList، اصطلاحا materialization گفته می‌شود و هدف آن تبدیل یک IQueryable، به یک IEnumerable است. اطلاعات بیشتر


مثال 2: اطلاعات ستون‌های خاصی از یک جدول را دریافت کنید.

می‌خواهیم لیست نام امکانات مجموعه را به همراه هزینه‌ی مرتبط با آن‌ها، نمایش دهیم:
var facilities = context.Facilities.Select(x =>
                    new
                    {
                        x.Name,
                        x.MemberCost
                    }).ToList();
برای انتخاب ستون‌هایی خاص از یک جدول، نیاز است از متد Select استفاده کرد و سپس نام دقیق آن‌ها را ذکر نمود. در غیراینصورت همانند مثال1، تمام ستون‌ها بازگشت داده می‌شوند. در اینجا خروجی حاصل، یک anonymous list است که می‌توان آن‌را با یک کلاس و یا حتی یک tuple نیز جایگزین کرد.



مثال 3: نحوه‌ی بازگشت ردیف‌ها را کنترل کنید.

چگونه می‌توان لیست امکاناتی را بازگشت داد که برای کاربران رایگان نیستند؟
var facilities = context.Facilities.Where(x => x.MemberCost > 0).ToList();
برای فیلتر کردن ردیف‌هایی خاص می‌توان از متد Where استفاده کرد. در اینجا امکان نوشتن شرط مدنظر وجود دارد که به آن predicate هم گفته می‌شود و می‌تواند ترکیبی از چندین شرط نیز باشد. در این کوئری چون از متد Select استفاده نشده‌است، تمام ستون‌های جدول بازگشت داده می‌شوند:



مثال 4: نحوه‌ی بازگشت ردیف‌ها را کنترل کنید؛ قسمت دوم.

چگونه می‌توان لیست امکاناتی را بازگشت داد که برای کاربران رایگان نیستند و همچنین هزینه‌ی آن‌ها، 1/50 ام هزینه‌ی نگهداری ماهیانه‌ی آن‌ها است؟ خروجی این کوئری باید تنها به همراه ستون‌های FacId, Name, MemberCost, MonthlyMaintenance باشد.
var facilities = context.Facilities.Where(x => x.MemberCost > 0
                                                            && x.MemberCost < (x.MonthlyMaintenance / 50))
                                                    .Select(x =>
                                                        new
                                                        {
                                                            x.FacId,
                                                            x.Name,
                                                            x.MemberCost,
                                                            x.MonthlyMaintenance
                                                        }).ToList();


در این مثال نحوه‌ی ترکیب چند شرط را با هم در قسمت Where، مشاهده می‌کنید و همچنین با استفاده از متد Select، تعداد ستون‌های بازگشتی نیز کنترل شده‌اند.


مثال 5: جستجوهای ساده‌ی رشته‌ای

لیستی از امکاناتی را تهیه کنید که واژه‌ی «Tennis» در نام آن‌ها بکار رفته‌است.
var facilities = context.Facilities.Where(x => x.Name.Contains("Tennis")).ToList();
یک چنین جستجو‌هایی را می‌توان توسط متد Contains انجام داد که در EF-Core، خروجی زیر را تولید می‌کند:



مثال 6: ردیف‌هایی را که با چندین مقدار ممکن تطابق دارند، بازگشت دهید.

چگونه می‌توان امکانات دارای ID مساوی 1 و 5 را بازگشت داد؟ برای اینکار از ترکیب شرط‌ها با استفاده از OR استفاده نکنید.
int[] ids = { 1, 5 };
var facilities = context.Facilities.Where(x => ids.Contains(x.FacId)).ToList();
یک روش حل این مساله، رسیدن به یک کوئری دارای where facid = 1 or facid = 5 است. اگر تعداد این IDها بیشتر شد، روش Where In که بر روی یک لیست از آن‌ها کار می‌کند، مرسوم‌تر است که نحوه‌ی تهیه‌ی یک چنین کوئری‌هایی را با استفاده از تعریف یک آرایه و سپس فراخوانی متد Contains بر روی آن، در اینجا مشاهده می‌کنید.



مثال 7: نتایج بازگشت داده شده را طبقه بندی کنید.

گزارشی از امکانات را تهیه کنید که در آن اگر هزینه‌ی نگهداری ماهیانه‌ی امکاناتی بیشتر از 100 دلار بود، به صورت expensive و در غیراینصورت cheap، طبقه بندی شوند.
var facilities = context.Facilities
                        .Select(x =>
                                    new
                                    {
                                        x.Name,
                                        Cost = x.MonthlyMaintenance > 100 ? "expensive" : "cheap"
                                    }).ToList();
می‌توان بر روی هر کدام از ستون‌های ذکر شده‌ی در متد Select، شرط‌هایی را نیز اعمال کرد و توانایی آن تنها به ذکر نام ستون‌ها خلاصه نمی‌شود. برای مثال در اینجا اگر MonthlyMaintenance بیشتر از مقداری بود، برچسب خاصی بجای این مقدار اصلی، نمایش داده می‌شود و چون خروجی نهایی محاسباتی آن دیگر یک ستون اصلی جدول نیست، نیاز است نام دلخواهی را برای آن انتخاب کرد که در کوئری نهایی به صورت AS Cost ظاهر می‌شود؛ البته می‌توان اینکار را در مورد ستون Name نیز انجام داد و در صورت لزوم، نام ستون دلخواه دیگری را برای آن قید کرد.



مثال 8: کار با تاریخ و زمان

لیست کاربرانی را بازگشت دهید که پس از September 2012 عضو این مجموعه شده‌اند. این گزارش باید تنها به همراه ستون‌های MemId, Surname, FirstName, JoinDate باشد.
var date = new DateTime(2012, 09, 01);
var members = context.Members.Where(x => x.JoinDate >= date)
                                            .Select(x =>
                                                        new
                                                        {
                                                            x.MemId,
                                                            x.Surname,
                                                            x.FirstName,
                                                            x.JoinDate
                                                        }).ToList();
در EF Core امکان مقایسه‌ی معمولی خواصی از نوع DateTime با وهله‌ای/مقداری از این نوع وجود دارد که در نهایت یک چنین خروجی را تولید می‌کند:



مثال 9: نتایج تکراری را از اطلاعات بازگشتی حذف کرده و آن‌ها را مرتب کنید.

گزارشی را تهیه کنید که در آن تنها فیلد Surname مرتب شده‌ی کاربران وجود دارد. از لیست Surnameها، تنها 10 مورد غیر تکراری را بازگشت دهید.
var members = context.Members.OrderBy(x => x.Surname)
                                            .Select(x =>
                                                        new
                                                        {
                                                            x.Surname
                                                        })
                                            .Distinct()
                                            .Take(10)
                                            .ToList();
با استفاده از متد OrderBy، می‌توان نتایج حاصل از کوئری را بر اساس خاصیت مشخصی مرتب کرد. سپس تعداد ستون‌های بازگشتی، توسط متد Select مشخص شده‌اند و در آخر متد Distinct سبب بازگشت موارد غیرتکراری شده (به SELECT DISTINCT ترجمه می‌شود) و متد Take، تعداد ردیف‌های بازگشت داده شده را محدود می‌کند (به SELECT  TOP 10 ترجمه می‌شود).



مثال 10: نتایج چند کوئری را با هم ترکیب کنید.

لیست نام‌های امکانات و نام‌های اشخاص را با هم ترکیب کنید.
var names = context.Members.Select(m => m.Surname).ToList()
                            .Union(context.Facilities.Select(f => f.Name).ToList()) // For now we have to use `.ToList()` here
                            .ToList();
برای ترکیب نتایج کوئری حاصل از دو جدول یا بیشتر از union استفاده می‌شود (در قالب یک کوئری):
SELECT surname
FROM members
UNION
SELECT name
FROM facilities;
 اما ... EF-Core 3x فعلا از آن به صورت تولید تنها یک کوئری SQL پشتیبانی نمی‌کند. به همین جهت در اینجا ترکیبی از LINQ to Entities و LINQ to Objects را مشاهده می‌کنید. هر جائیکه متد ToList ذکر شده، یعنی تبدیل LINQ to Entities به نتیجه‌ی حاصل یا همان materialization و از اینجا به بعد با داشتن لیستی از اشیاء درون حافظه‌ای می‌توان از LINQ to Objects استفاده کرد که استفاده‌ی از تمام امکانات زبان #C در آن میسر است.
یعنی در مثال فوق، دوبار رفت و برگشت به بانک اطلاعاتی صورت گرفته (به ازای هر ToList ذکر شده) و سپس نتیجه‌ی حاصل، در سمت کلاینت با هم Union شده‌اند و نه در سمت دیتابیس.


مثال 11: محاسبات تجمعی ابتدایی

زمان ثبت نام آخرین عضو مجموعه چیست؟

برای حل این مثال می‌توان از روش‌های مختلفی استفاده کرد:

الف) استفاده از متد تجمعی Max برای یافتن بزرگترین مقدار JoinDate
var latest = context.Members.Max(x => x.JoinDate);


متد Max برای خواص nullable می‌تواند null را بازگشت دهد و همچنین اگر این مجموعه دارای مقداری نباشد و آن خاصیت نیز nullable نباشد، استثنای Sequence contains no element را صادر می‌کند. می‌توان این استثناء را به صورت زیر با استفاده از متد DefaultIfEmpty کنترل کرد:
var latest2 = context.Members.Select(m => m.JoinDate).DefaultIfEmpty().Max();
که به صورت خاص زیر ترجمه می‌شود:
SELECT MAX([m].[JoinDate])
FROM   (SELECT NULL AS [empty]) AS [empty]
       LEFT OUTER JOIN
       [Members] AS [m]
       ON 1 = 1;
یا حتی می‌توان JoinDate را که nullable نیست، به صورت nullable معرفی کرد و سبب شد تا در صورت عدم وجود ردیفی در جدول، نال بازگشت داده شود:
var latest3 = context.Members.Max(m => (DateTime?)m.JoinDate) ?? DateTime.Now;
این روش همان کوئری «SELECT MAX([m].[JoinDate]) FROM [Members] AS [m]» را تولید می‌کند و کنترل استثنای آن در سمت کلاینت صورت می‌گیرد.

ب) بجای استفاده از متد Max می‌توان ابتدا رکوردها را بر اساس JoinDate به صورت نزولی مرتب کرد و سپس اولین عضو حاصل را بازگشت داد؛ چون اکنون بر اساس مرتب سازی صورت گرفته، در بالای لیست قرار دارد:
var latest4 = context.Members.OrderByDescending(m => m.JoinDate).Select(m => m.JoinDate).FirstOrDefault();



مثال 12: مثالی دیگر از محاسبات تجمعی ابتدایی

در مثال قبلی، نام و نام خانوادگی آخرین شخص ثبت نام شده را نیز به گزارش اضافه کنید؛ یعنی Select انجام شده شامل x.FirstName, x.Surname, x.JoinDate باشد.

یک روش انجام اینکار، همان کوئری ب مثال قبلی است که اینبار فقط Select آن فرق می‌کند:
var lastMember = context.Members.OrderByDescending(m => m.JoinDate)
                            .Select(x => new { x.FirstName, x.Surname, x.JoinDate })
                            .FirstOrDefault();


روش دیگر آن نوشتن یک sub-query در قسمت Where است:
var members = context.Members.Select(x => new { x.FirstName, x.Surname, x.JoinDate })
                                    .Where(x => x.JoinDate == context.Members.Max(x => x.JoinDate))
                                    .ToList();
می‌توان ردیفی را بازگشت داد که JoinDate آن همان بزرگترین مقدار JoinDate جدول کاربران است. یک چنین کوئری خاصی که به همراه دوبار فراخوانی context است، با فراخوانی ToList انتهایی، تنها یک کوئری را تولید می‌کند:



کدهای کامل این قسمت را در اینجا می‌توانید مشاهده کنید.
  • #
    ‫۴ سال قبل، سه‌شنبه ۲۱ مرداد ۱۳۹۹، ساعت ۱۴:۳۹
    یک نکته‌ی تکمیلی
    در مثال‌های این سری، سطرهایی مانند سطر زیر را در انتهای تمام آزمون‌های نوشته شده مشاهده می‌کنید:
    facilities.Should().BeEquivalentTo(expectedResult);
    این قابلیت توسط کتابخانه‌ی Fluent Assertions میسر شده‌است که نه فقط مقایسه‌ی مجموعه‌ها را از هر نوعی میسر می‌کند بلکه در صورت شکست آزمون، دقیقا مشخص می‌کند که کدام سطرها با هم یکی نیستند و گزارش کاملی را ارائه می‌دهد.
    متدهای توکاری مانند Assert.Equal کار بررسی reference equality را انجام می‌دهند که برای اینکار مناسب نیستند و یا برای متدهای خاصی مانند CollectionAssert.AreEqual باید حتما پیاده سازی IComparer وجود داشته باشد:
    CollectionAssert.AreEqual(list1, resultList, new CustomComparer())
    اما متد BeEquivalentTo حتی با anonymous types و anonymous collections هم بدون نیاز به تنظیم خاصی کار می‌کند و وابستگی خاصی به فریم ورک آزمون واحد جاری ندارد.
  • #
    ‫۲ سال و ۴ ماه قبل، دوشنبه ۱۲ اردیبهشت ۱۴۰۱، ساعت ۰۴:۲۷
     عملگر All در LINQ این امکان را فراهم می‌کند ، که کنترل کنیم آیا همه عناصر یک مجموعه یک شرط خاص را پاس می‌کنند : 
    int[] scores;
    scores = new[] {1, 2, 3, 4};
    scores.All(x=> x > 0); //true
    scores = new[] {-1, -2, 3, 4};
    scores.All(x => x > 0);//false