دوره آموزشی Entity FrameWork Core - قسمت 26
خوب Split queries یک ویژگی است که از Entity Framework Core 5 معرفی شد و به معنای امکان تقسیم یک پرس و جو به چندین کوئری SQL است.
برای درک تفاوت بین default query)single query) و Split query ، یک console application با استفاده از NET Core 6.0 ایجاد و بسته های زیر را نصب کنید:
در ادامه در کلاس DbContext، در متد OnConfiguring، گزینه Log into the console "تمام کوئری های تولید شده توسط EF Core" را فعال کنید:
public class EFContext : DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { const string strConnection = "Data source=(localdb)\\mssqllocaldb; Initial Catalog=DefaultQueryAndSplitQuery;Integrated Security=true;pooling=true;" optionsBuilder .UseSqlServer(strConnection) .EnableSensitiveDataLogging() .LogTo(Console.WriteLine, LogLevel.Information); } }
حالا اجازه دهید سناریویی را در نظر بگیریم که در آن دو موجودیت با رابطه یک به چند (1:N) داریم: "Student" و "Course"، که در آن یک دوره می تواند دانشجویان زیادی داشته باشد، و یک دانشجو فقط می تواند یک Course داشته باشد.
public class Course { public int CourseId { get; set; } public string Title { get; set; } public List<Student> Students { get; set; } }
و :
public class Student { public int StudentId { get; set; } public string Name { get; set; } public int CourseId { get; set; } public Course Course { get; set; } }
در ادامه مدل خود را با داده های آزمایشی پر می کنیم :
public class DbInitializer { private readonly ModelBuilder modelBuilder; public DbInitializer(ModelBuilder modelBuilder) { this.modelBuilder = modelBuilder; } public void Seed() { modelBuilder.Entity<Course>().HasData( new Course { CourseId = 25, Title = "C#" }, new Course { CourseId = 50, Title = "Java" } ); modelBuilder.Entity<Student>().HasData( new Student { StudentId = 10, Name = "Farshid azizi", CourseId = 25 }, new Student { StudentId = 20, Name = "reza ahmadi", CourseId = 25 }, new Student { StudentId = 30, Name = "elnaz Goli", CourseId = 50 } ); } }
و در کلاس EFContext کد زیر را نیز اضافه کنید :
protected override void OnModelCreating(ModelBuilder modelBuilder) { new DbInitializer(modelBuilder).Seed(); }
دستورات زیر را اجرا کنید :
add-migration DataSeeding
update-database
در این مثال، ما در حال ایجاد یک پرس و جوی LINQ با استفاده از Include، بدون استفاده از Slipt query، برای بازگرداندن Courseها و Studentها هستیم:
public class QueryWithoutSplitQuery { public void SingleQuery() { using var db = new EFContext(); var courses = db.Courses .Include(p => p.Students) .ToList(); foreach (var course in courses) { Console.WriteLine($"Course: {course.Title}"); foreach (var student in course.Students) { Console.WriteLine($"\tStudent: {student.Name}"); } } } }
به کلاس Program.cs بروید و یه نمونه از کلاس QueryWithoutSplitQuery ایجاد و در نهایت برنامه را اجرا کنید :
QueryWithoutSplitQuery ins1 = new QueryWithoutSplitQuery(); ins1.SingleQuery();
در این جا، EF Core یک پرس و جو ایجاد می کند که در آن اطلاعات برای دوره و دانش آموزان برگردانده می شود:
SELECT [c].[CourseId], [c].[Title], [s].[StudentId], [s].[CourseId], [s].[Name] FROM [Courses] AS [c] LEFT JOIN [Students] AS [s] ON [c].[CourseId] = [s].[CourseId] ORDER BY [c].[CourseId]
اگر یک دوره دارای چندین دانش آموز مرتبط باشد، ردیفهای مربوط به این Studentها اطلاعات Courseرا تکرار میکنند. این تکرار منجر به به اصطلاح مشکل "انفجار دکارتی/cartesian explosion" می شود. با بارگیری بیشتر روابط یک به چند، میزان داده های تکراری ممکن است افزایش یابد و بر عملکرد برنامه شما تأثیر منفی بگذارد.
از EF Core 5، ما این امکان را داریم که از متد AsSplitQuery برای تقسیم کوئری استفاده کنیم:
public class QueryWithSplitQuery { public void SplitQuery() { using var db = new EFContext(); var courses = db.Courses .Include(p => p.Students) .AsSplitQuery() .ToList(); foreach (var course in courses) { Console.WriteLine($"Course: {course.Title}"); foreach (var student in course.Students) { Console.WriteLine($"\tStudent: {student.Name}"); } } } }
به کلاس Program.cs بروید و یه نمونه از کلاس QueryWithSplitQuery ایجاد و در نهایت برنامه را اجرا کنید :
QueryWithSplitQuery ins2 = new QueryWithSplitQuery(); ins2.SplitQuery();
هنگام استفاده از متد AsSplitQuery در این حالت EF Core دو کوئری ایجاد می کند:
اولی( فقط اطلاعات مربوط به دوره ها برگردانده می شود.) :
SELECT [c].[CourseId], [c].[Title] FROM [Courses] AS [c] ORDER BY [c].[CourseId]
و دومی(فقط اطلاعات مربوط به دانش آموزان برگردانده می شود) :
SELECT [s].[StudentId], [s].[CourseId], [s].[Name], [c].[CourseId] FROM [Courses] AS [c] INNER JOIN [Students] AS [s] ON [c].[CourseId] = [s].[CourseId] ORDER BY [c].[CourseId]
همانطور که می بینیم، با SplitQuery نام دوره ها فقط یک بار برگردانده شد. این به این معنی است که حتی اگر پرسوجوهای پیچیدهتری با ستونهای بیشتر و روابط بیشتر داشته باشیم، دادهها تکراری نمیشوند، که منجر به پرس و جو بسیار کارآمدتر میشود.
هنگام استفاده از Split Query با Skip/Take، توجه ویژهای داشته باشید که query ordering خود را کاملاً منحصر به فرد کنید. عدم انجام این کار می تواند باعث برگرداندن داده های نادرست شود.به عنوان مثال، اگر نتایج فقط بر اساس تاریخ مرتب شوند، اما ممکن است چندین نتیجه با تاریخ یکسان وجود داشته باشد، هر یک از Split Queryها میتوانند نتایج متفاوتی را از پایگاه داده دریافت کنند. ordering بر اساس تاریخ و شناسه (یا هر ویژگی منحصر به فرد دیگر یا ترکیبی از پراپرتی ها) باعث می شود ordering کاملاً منحصر به فرد باشد و از این مشکل جلوگیری شود.توجه داشته باشید که پایگاه داده های رابطه ای هیچ ترتیبی را به طور پیش فرض اعمال نمی کنند، حتی در کلید اصلی.
موجودیتهای مرتبط یک به یک همیشه از طریق JOIN در همان query بارگیری میشوند، زیرا تأثیری بر عملکرد ندارد.
هر پرس و جو در حال حاضر مستلزم یک رفت و برگشت اضافی به پایگاه داده است و چندین رفت و برگشت شبکه به خودی خود می تواند عملکرد را کاهش دهد.
همچنین می توانید پرس و جوهای تقسیم شده را به عنوان پیش فرض برای Context برنامه خود پیکربندی کنید:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder .UseSqlServer( @"Server=(localdb)\mssqllocaldb;Database=EFQuerying;Trusted_Connection=True", o => o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)); }
هنگامی که پرسوجوهای تقسیمشده بهعنوان پیشفرض پیکربندی میشوند، همچنان میتوان پرسوجوهای خاصی را برای اجرا بهعنوان single query پیکربندی کرد:
using (var context = new EFContext()) { var courses = db.Courses .Include(p => p.Students) .AsSingleQuery() .ToList(); }
در حالی که Split query از مشکلات عملکرد مرتبط با JOIN ها و انفجار دکارتی جلوگیری می کند، همچنین دارای معایبی است:
متأسفانه، یک استراتژی مشخص برای بارگیری موجودیت های مرتبط وجود ندارد که متناسب با همه سناریوها باشد. مزایا و معایب پرس و جوهای Single و Split را با دقت در نظر بگیرید تا موردی را که متناسب با نیاز شماست انتخاب کنید.
بیشتر بخوانید : دوره آموزشی Entity FrameWork Core - قسمت 28
بیشتر بخوانید : دوره آموزشی Entity FrameWork Core
بیشتر بخوانید : نقشه راه توسعه دهندگان Asp.NET Core