نکاتی مفید در حین کار با jQuery
برای انجام عملیات پرس و جوی LINQ با استفاده از روش پردازش موازی به راحتی میتوان الحاقیه AsParallel را به هر دادهای از نوع IEnumerable<T> افزود:
var data = new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; // پرس و جوی عادی var q1 = from i in data select i; // پرس و جو به شیوه موازی var q2 = from i in data.AsParallel() select i;
الحاقیه .AsParallel() در پرس و جویq2 نسخه موازی LINQ را بر روی متغیر data اجرا میکند و اگر همه چیز به صورت صحیح انجام شود هر دو پرس و جو باید نتایج یکسانی داشته باشند، اما نتایج عبارتند از :
//نتیجه اجرای q1 0 1 2 3 4 5 6 7 8 9 10 //نتیجه اجرای q2 0 6 1 7 2 8 3 9 4 10 5
همانطور که ملاحظه میکنید ترتیب واقعی نتایج اجرای پرس و جوها با یکدیگر متفاوتاند و نکته جالبتر آنکه با هر بار اجرای برنامه نتیجه اجرای پرس و جوی q2 با نتیجه سری قبل خودش متفاوت است که این تفاوت به چگونگی تقسیم بندی انجام کار میان هستههای سی پی یو، بستگی دارد. نکته بسیار مهم آن است که عملیات پردازش موازی خود را ملزم به حفظ ترتیب دادهها نمیداند مگر آنکه مجبورش کنیم و این رفتار پردازش موازی به دلیل بالا بردن راندمان عملیات است در نتیجه انجام پرس و جوهای موازی توسط الحاقیه .AsParallel() خیلی هم ساده نیست و ممکن است منجر به تولید نتایج ناخواسته شود.
حال اگر چگونگی ترتیب دادهها، برایمان مهم است به دو روش میتوانیم آن را انجام دهیم:
1- افزودن عبارت orderby به پرس و جو
2- استفاده از الحاقیه AsOrdered
var q3 = from i in data.AsParallel() orderby i select i; var q4 = from i in data.AsParallel().AsOrdered() select i;
که نتیجه انجام هر دو پرس و جوی بالا یکی خواهد بود. حال مسأله دیگر این است که آیا همیشه استفاده از پردازش موازی مفید خواهد بود یا خیر؟پاسخ این سؤال وابسته است به نوع مسأله و حجم داده مورد نظر و مشخصات سیستمی که قرار است از آن کد استفاده کند. چگونگی اندازه سرعت و مقدار مصرف حافظه در اجرای چهار پرس و جوی فوق در کامپیوتر من با پردازنده Intel Q9550 به شکل زیر است:
ابتدا مدلهای برنامه را در نظر بگیرید که از یک صورتحساب، به همراه ریز قیمتهای آیتمهای مرتبط با آن تشکیل شده است:
public class Bill { public int Id { set; get; } public string Name { set; get; } public virtual ICollection<Transaction> Transactions { set; get; } } public class Transaction { public int Id { set; get; } public DateTime AddDate { set; get; } public int Amount { set; get; } [ForeignKey("BillId")] public virtual Bill Bill { set; get; } public int BillId { set; get; } }
public class MyContext : DbContext { public DbSet<Bill> Bills { get; set; } public DbSet<Transaction> Transactions { get; set; } }
public class Configuration : DbMigrationsConfiguration<MyContext> { public Configuration() { AutomaticMigrationsEnabled = true; AutomaticMigrationDataLossAllowed = true; } protected override void Seed(MyContext context) { var bill1 = new Bill { Name = "bill-1" }; context.Bills.Add(bill1); for (int i = 0; i < 11; i++) { context.Transactions.Add(new Transaction { AddDate = DateTime.Now.AddDays(-i), Amount = 1000000000 + i, Bill = bill1 }); } base.Seed(context); } }
اولین مثال: یک جمع ساده
public static class Test { public static void RunTests() { Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Configuration>()); using (var context = new MyContext()) { var sum = context.Transactions.Sum(x => x.Amount); Console.WriteLine(sum); } } }
خروجی SQL کوئری فوق به نحو زیر است:
SELECT [GroupBy1].[A1] AS [C1] FROM ( SELECT SUM([Extent1].[Amount]) AS [A1] FROM [dbo].[Transactions] AS [Extent1] ) AS [GroupBy1]
Arithmetic overflow error converting expression to data type int.
راه حل:
نیاز است جمع را بر روی Int64 بجای Int32 انجام دهیم:
var sum2 = context.Transactions.Sum(x => (Int64)x.Amount);
SELECT [GroupBy1].[A1] AS [C1] FROM ( SELECT SUM( CAST( [Extent1].[Amount] AS bigint)) AS [A1] FROM [dbo].[Transactions] AS [Extent1] ) AS [GroupBy1]
مثال دوم: سیستم باید بتواند با نبود رکوردها نیز صحیح کار کند
برای نمونه کوئری زیر را بر روی بازهای که سیستم عملکرد نداشته است، در نظر بگیرید:
var date = DateTime.Now.AddDays(10); var sum3 = context.Transactions .Where(x => x.AddDate > date) .Sum(x => (Int64)x.Amount);
SELECT [GroupBy1].[A1] AS [C1] FROM ( SELECT SUM( CAST( [Extent1].[Amount] AS bigint)) AS [A1] FROM [dbo].[Transactions] AS [Extent1] WHERE [Extent1].[AddDate] > @p__linq__0 ) AS [GroupBy1]
The cast to value type 'Int64' failed because the materialized value is null. Either the result type's generic parameter or the query must use a nullable type.
var date = DateTime.Now.AddDays(10); var sum3 = context.Transactions .Where(x => x.AddDate > date) .Sum(x => (Int64?)x.Amount) ?? 0;
مثال سوم: حالتهای خاص استفاده از خواص راهبری
کوئری زیر را در نظر بگیرید:
var sum4 = context.Bills.First().Transactions.Sum(x => (Int64?)x.Amount) ?? 0;
SELECT [Extent1].[Id] AS [Id], [Extent1].[AddDate] AS [AddDate], [Extent1].[Amount] AS [Amount], [Extent1].[BillId] AS [BillId] FROM [dbo].[Transactions] AS [Extent1] WHERE [Extent1].[BillId] = @EntityKeyValue1
راه حل کنونی:
var entry = context.Bills.First(); var sum5 = context.Entry(entry).Collection(x => x.Transactions).Query().Sum(x => (Int64?)x.Amount) ?? 0;
SELECT [GroupBy1].[A1] AS [C1] FROM ( SELECT SUM( CAST( [Extent1].[Amount] AS bigint)) AS [A1] FROM [dbo].[Transactions] AS [Extent1] WHERE [Extent1].[BillId] = @EntityKeyValue1 ) AS [GroupBy1]
نکاتی که در اینجا ذکر شدند در مورد تمام توابع تجمعی مانند Sum، Count، Max و Min و غیره صادق هستند و باید به آنها نیز دقت داشت.