جدول اعضای این مجموعه، خود ارجاع دهنده طراحی شدهاست:
در اینجا RecommendedBy، یک کلید خارجی نال پذیر است که به Id همین جدول اشاره میکند. دو خاصیت دیگر تعریف شده، مکمل این خاصیت عددی، جهت سهولت کوئری نویسیهای EF-Core هستند که در قسمتهای قبل نیز تعدادی کوئری را در این زمینه مشاهده کردید؛ مانند:
- تولید لیست کاربرانی که کاربر دیگری را توصیه کردهاند.
- تولید لیست کاربران، به همراه توصیه کنندهی آنها.
- تولید لیست کاربران به همراه توصیه کنندهی آنها، بدون استفاده از جوین.
- هر کاربر چه تعداد کاربر دیگری را توصیه کردهاست؟
در این قسمت تعدادی مثال بازگشتی را میخواهیم بررسی کنیم.
مثال 1: رنجیرهی توصیه کنندگان کاربر با ID مساوی 27 را محاسبه کنید.
میخواهیم بدانیم چه کسی کاربر 27 را توصیه کرده و همچنین این کاربر نیز توسط چه شخص دیگری توصیه شده و به همین ترتیب تا بالاترین سطح ممکن.
روش معمول انجام این نوع کوئریها استفاده از «WITH Hierachy» است. اما اگر بخواهیم بدون SQL نویسی مستقیم اینکار را انجام دهیم، میتوان به صورت زیر عمل کرد:
این کوئری ابتدا تمام رکوردهای جدول کاربران را لیست میکند. سپس خاصیت Recommender هر کدام را تا n سطح مقدار دهی میکند (خود EF-Core اینکار را انجام میدهد). تمام این اتفاقات تا قسمت ToList آن رخ میدهند. پس از آن یک FirstOrDefault سمت کاربر را هم داریم (LINQ to Objects). هدف از آن، بازگشت تنها ریشهی مرتبط با ID=27 است و تمام Recommenderهای متصل به آن. این موارد را در تصویر ذیل بهتر میتوانید مشاهده کنید:
لیست تمام کاربران وجود دارند. سپس سیزدهمین مورد آن، همان کاربر 27 است که توسط کاربر 20 توصیه شده. کاربر 20 توسط کاربر 5 توصیه شده و کاربر 5 توسط کاربر 1 و پس از آن خاصیت Recommender نال است که به معنای پایان پیمودن این زنجیرهاست.
بنابراین مرحلهی بعدی پس از یافتن ریشهی کاربر 27، پیمودن خاصیتهای Recommender به صورت بازگشتی است؛ کاری شبیه به متد FindParents زیر:
که به صورت زیر میتواند مورد استفاده قرار گیرد:
مثال 2: زنجیرهی توصیه شدههای توسط کاربر با ID مساوی 1 را محاسبه کنید.
میخواهیم بدانیم کاربر 1، چه کسی را توصیه کرده و این کاربر نیز چه کاربر دیگری را توصیه کرده و به همین ترتیب تا پایینترین سطح ممکن.
این کوئری نیز شبیه به کوئری مثال قبلی است؛ با یک تفاوت. در اینجا Include(member => member.Children) هم ذکر شدهاست. هدف این است که EF-Core، خاصیت Children را تا n سطح ممکن به صورت خودکار مقدار دهی کند و این مورد دقیقا هدف اصلی مثال جاری است.
وجود Include، سبب تولید یک چنین کوئری میشود که در آن جدول کاربران با خودش جوین شدهاست:
پس از آن باید خاصیت member.Children را تا هر سطح ممکن به صورت بازگشتی پیمود تا به جواب اصلی این مثال رسید:
که به صورت زیر میتواند مورد استفاده قرار گیرد:
کدهای کامل این قسمت را در اینجا میتوانید مشاهده کنید.
namespace EFCorePgExercises.Entities { public class Member { // ... public virtual ICollection<Member> Children { get; set; } public virtual Member Recommender { set; get; } public int? RecommendedBy { set; get; } // ... } }
- تولید لیست کاربرانی که کاربر دیگری را توصیه کردهاند.
- تولید لیست کاربران، به همراه توصیه کنندهی آنها.
- تولید لیست کاربران به همراه توصیه کنندهی آنها، بدون استفاده از جوین.
- هر کاربر چه تعداد کاربر دیگری را توصیه کردهاست؟
در این قسمت تعدادی مثال بازگشتی را میخواهیم بررسی کنیم.
مثال 1: رنجیرهی توصیه کنندگان کاربر با ID مساوی 27 را محاسبه کنید.
میخواهیم بدانیم چه کسی کاربر 27 را توصیه کرده و همچنین این کاربر نیز توسط چه شخص دیگری توصیه شده و به همین ترتیب تا بالاترین سطح ممکن.
روش معمول انجام این نوع کوئریها استفاده از «WITH Hierachy» است. اما اگر بخواهیم بدون SQL نویسی مستقیم اینکار را انجام دهیم، میتوان به صورت زیر عمل کرد:
var id = 27; var entity27WithAllOfItsParents = context.Members .Where(member => member.MemId == id || member.Children.Any(m => member.MemId == m.RecommendedBy)) .ToList() //It's a MUST - get all children from the database .FirstOrDefault(x => x.MemId == id);// then get the root of the tree
این کوئری ابتدا تمام رکوردهای جدول کاربران را لیست میکند. سپس خاصیت Recommender هر کدام را تا n سطح مقدار دهی میکند (خود EF-Core اینکار را انجام میدهد). تمام این اتفاقات تا قسمت ToList آن رخ میدهند. پس از آن یک FirstOrDefault سمت کاربر را هم داریم (LINQ to Objects). هدف از آن، بازگشت تنها ریشهی مرتبط با ID=27 است و تمام Recommenderهای متصل به آن. این موارد را در تصویر ذیل بهتر میتوانید مشاهده کنید:
لیست تمام کاربران وجود دارند. سپس سیزدهمین مورد آن، همان کاربر 27 است که توسط کاربر 20 توصیه شده. کاربر 20 توسط کاربر 5 توصیه شده و کاربر 5 توسط کاربر 1 و پس از آن خاصیت Recommender نال است که به معنای پایان پیمودن این زنجیرهاست.
بنابراین مرحلهی بعدی پس از یافتن ریشهی کاربر 27، پیمودن خاصیتهای Recommender به صورت بازگشتی است؛ کاری شبیه به متد FindParents زیر:
namespace EFCorePgExercises.Exercises.RecursiveQueries { public static class RecursiveUtils { public static void FindParents(Member member, List<dynamic> actualResult) { if (member == null || member.Recommender == null) { return; } var item = member.Recommender; actualResult.Add(new { Recommender = item.MemId, item.FirstName, item.Surname }); if (item.Recommender != null) { FindParents(item, actualResult); } } } }
var actualResult = new List<dynamic>(); RecursiveUtils.FindParents(entity27WithAllOfItsParents, actualResult);
مثال 2: زنجیرهی توصیه شدههای توسط کاربر با ID مساوی 1 را محاسبه کنید.
میخواهیم بدانیم کاربر 1، چه کسی را توصیه کرده و این کاربر نیز چه کاربر دیگری را توصیه کرده و به همین ترتیب تا پایینترین سطح ممکن.
var id = 1; var entity1WithAllOfItsDescendants = context.Members .Include(member => member.Children) .Where(member => member.MemId == id || member.Children.Any(m => member.MemId == m.RecommendedBy)) .ToList() //It's a MUST - get all children from the database .FirstOrDefault(x => x.MemId == id);// then get the root of the tree
وجود Include، سبب تولید یک چنین کوئری میشود که در آن جدول کاربران با خودش جوین شدهاست:
SELECT [m].[MemId], [m].[Address], [m].[FirstName], [m].[JoinDate], [m].[RecommendedBy], [m].[Surname], [m].[Telephone], [m].[ZipCode], [m0].[MemId], [m0].[Address], [m0].[FirstName], [m0].[JoinDate], [m0].[RecommendedBy], [m0].[Surname], [m0].[Telephone], [m0].[ZipCode] FROM [Members] AS [m] LEFT OUTER JOIN [Members] AS [m0] ON [m].[MemId] = [m0].[RecommendedBy] WHERE ([m].[MemId] = 1) OR EXISTS (SELECT 1 FROM [Members] AS [m1] WHERE ([m].[MemId] = [m1].[RecommendedBy]) AND ([m].[MemId] = [m1].[RecommendedBy])) ORDER BY [m].[MemId], [m0].[MemId];
namespace EFCorePgExercises.Exercises.RecursiveQueries { public static class RecursiveUtils { public static void FindChildren(Member member, List<dynamic> actualResult) { if (member == null) { return; } foreach (var item in member.Children) { actualResult.Add(new { item.MemId, item.FirstName, item.Surname }); if (item.Children != null) { FindChildren(item, actualResult); } } } } }
var actualResult = new List<dynamic>(); RecursiveUtils.FindChildren(entity1WithAllOfItsDescendants, actualResult);
کدهای کامل این قسمت را در اینجا میتوانید مشاهده کنید.