‫فواید LINQ و LINQ-to-Objects

اگر عادت کنید به استفاده از LINQ بعداً می‌توانید از یکی از مزایای مفید آن استفاده کنید. مدتی پیش مجبور شدم یک سری بهینه‌سازی‌هایی روی دسترسی به دیتابیس انجام دهم. یکی از موارد رایجی که برای بهینه‌سازی پیدا کردم entityهایی بودند که با یک query لینک از دیتابیس فراخوانی می‌شدند. این entityها تعداد رکورد محدودی داشتند، معمولاً خیلی کم تغییر می‌کردند ولی استفاده کنندگان زیادی داشتند. مثلاً فرض کنید آیتم‌های یک منو که در تمام صفحات برنامه مورد نیاز هستند.

در مورد این entityها به ذهنم رسید که همه آنها در ابتدای کار برنامه به جای ثابتی در حافظه load کرده و سپس هر وقت که با آنها کار داشتم از آنجا استفاده کنم. خوشبختانه چون قبلاً از LINQ برای query زدن استفاده کرده بودم، روال query زدن از objectهای موجود در حافظه چندان سخت نبود. کافی بود در query دیتابیسی مقدار in را به جای خواندن از دیتابیس با آن Array یا Collection موجود در حافظه عوض کنم و بعضی ملاحظات LINQ-to-Objects را رعایت کنم. به این ترتیب هم روش query زدنم عوض نشد و هم مجبور نشدم برای استخراج اطلاعات از حافظه از روش‌های foreach و loop و… استفاده کنم.

‫افزایش Performance در NHibernate با استفاده از اپراتورهای Projection

یکی از راه‌های توصیه شده برای افزایش Performance در Queryهای دیتابیس، پرهیز از استفاده‌های بی‌مورد از select * from some_table می‌باشد. راه حل توصیه شده استفاده از projection و برگرداندن صرفاً ستون‌های مورد نیاز از دیتابیس است. مثلاً باید از select col1, col2 from some_table استفاده شود.

در همین راستا LINQ-to-NHibernate هم پشتیبانی خوبی از اپراتورهای Projection در LINQ دارد. به نمونه زیر توجه کنید:

var query = from
entity in ActiveRecordLinq.AsQueryable()
where
entity.Prop1 == "123"
select
new SomeEntityDTO(Prop1, Prop2);

List list = query.ToList();
اسکریپت SQL تولید شده از query بالا فقط شامل Prop1 و Prop2 خواهد بود. توجه شود query بالا با استفاده Castle ActiveRecord نوشته شده ولی با LINQ-to-NHibernate اصلی هیچ فرقی ندارد.

‫کتاب LINQ in Action

یکی از عادت‌های من در این وبلاگ نوشتن درباره‌ی کتاب‌هایی است که خوانده‌ام. یکی از کتاب‌هایی که مدت‌ها پیش خواندن آن را شروع کردم کتاب LINQ in Action است. این کتاب را برای تکمیل اطلاعاتم راجع به LINQ شروع کردم چون حس می‌کردم چیزهایی که راجع به LINQ از کتاب Pro C# 2008 and the .NET 3.5 Platform یاد گرفته بودم کافی نیست.

با توجه به این که هدف من از یادگیری LINQ صرفاً استفاده از LINQ-to-NHibernate بود، در نتیجه فقط ۵ فصل اول کتاب که راجع به مقدمات LINQ و Linq-to-Objects را مطالعه و بقیه فصول (۸ فصل) که راجع به LINQ-to-XML و LINQ-to-SQL بودند را بی‌خیال شدم. البته این کتاب کمی قدیمی است و در سال ۲۰۰۸ منتشر شده یعنی زمانی که LINQ-to-EF وجود نداشته است.

چیزی که من از خواندن همان ۵ فصل متوجه شدم این بود که یادگیری کامل LINQ بیشتر در کار عملی امکان پذیر است تا مطالعه صرف. از طرفی مطالب مربوط به LINQ به زعم من خیلی فرار هستند و اگر در در عمل و به طور مداوم استفاده نشوند خیلی زود فراموش می‌گردند.

نمی‌دانم از ۲۰۰۸ تا حالا کتاب بهتری برای LINQ منتشر شده یا نه ولی به هر صورت خواندن این کتاب را توصیه می‌کنم به کسانی که می‌خواهد LINQ به طور علمی و کاملاً دقیق بیاموزند.

پ.ن.: در این لینک هم چند کتاب دیگر برای LINQ معرفی شده.

‫IEnumerable و IQueryable

اگر می‌خواهید از یک کوئری LINQ به عنوان خروجی یک متود استفاده کنید دو راه برای آن وجود دارد. یکی آن که خروجی متود را از نوع IEnumerable تعریف کنیم و یکی دیگر آن که خروجی آن را از نوع IQueryable تعریف کنیم. استفاده از IEnumerable به معنی پایین آمدن Performance است. به همین دلیل بهتر است از IQueryable استفاده کنیم. نکته‌ی جالب‌تری که فهمیدم این است که اگر از IEnumerable به عنوان خروجی متود استفاده شود دیگر نمی‌توان از متودهایی نظیر Skip و Take که در Data Pagination استفاده زیادی دارند استفاده کرد. به عنوان مثال به کد زیر دقت کنید:
IEnumerable MakeQuery()
{
  var query = from m in session.Linq()
              select m;
  return query;
}

List m1()
{
  return MakeQuery()
    .Skip(10)
    .Take(20)
    .ToList();
}
توابع Skip و Take در متود m1 بی‌تاثیر هستند. البته نه این که عمل نمی‌کنند بلکه باعث می‌شوند کل دیتا از منبع مورد نظر، که در مورد من Linq-to-NHibernate و دیتابیس بود، فراخوانی شوند نه آن که صرفاً آن بخش خاص بازیابی شوند.

My first experience with LINQ to NHibernate

Weeks ago we decided to upgrade to NHibernate 2.1.2
to be beneficiary of LINQ-to-NHibernate and other new features of last version of NHibernate. After introducing LINQ technology in .NET 3.0 many people were thinking the lack of a LINQ provider for NHibernate until when Ayende Rahien introduced the existence of LINQ-to-NHibernate.

My colleague Masoud and I started to replace a group of old NHibernate queries with LINQ-to-NHibernate queries. We progressed the task quickly and finished the entire task in few days. But very soon realized that writing LINQ-to-NHibernate queries is not as easy as we thought. Our queries were based on my knowledge of LINQ-to-Objects but many features working in LINQ-to-Objects was not working in LINQ-to-NHibernate. For example short circuit evaluation was not working. We tried to solve problems specially with help of StackOverflow. And here is some of encountered problems and their solution:

1. (TargetInvocationException): Exception has been thrown by the target of an invocation., (NullReferenceException): Object reference not set to an instance of an object., Occurs when a compare in LINQ throws an exception, this exception is wrapped in a TargetInvocationException exception and rethrowed. Like: l.Sender.Role.Contains(senderRoleSearch.Trim()) when senderRoleSearch == null

 2. Working with Enums is a bit strange. If you have a mapping like this:


then you will encounter some odd errors like:l.LetterType == LetterType.Internal results in “(QueryException): Type mismatch in NHibernate.Criterion.SimpleExpression: LetterType expected type System.Int32, actual type Faraconesh.EnterpriseAppUnits.OfficeAutomation.BusinessEntities.LetterType,. In this case you should remove type=”int” from the mapping. solution. For more information go here.

 3. When you have such a query:

var q=from l in session.Linq<Letter>

where crit == null ? true : l.Subject.Contains(crit.Trim())

select l;

You will probably encounter with “null object reference” because LINQ has not short-circuit evaluation. For more info go here and here.

4. Dynamic query:
While using nulls and nullables is not possible due to case 3 (above), You can use dynamic queries like this:

var q = from l in session.Linq()
select l;

if (serialNumberSearch != null)
q = q.Where(l => l.SerialNumber == serialNumberSearch.Trim());

5. Can not call methods: We were not able to find a solution.

 6. Subqueries does not work. Here is a our work-around.

Dealing with subqueries in LINQ-to-NHibernate

Some days ago I was in a situation that needed to use sub queries in LINQ-to-NHibernate. But as LINQ-to-NHibernate does not support sub queries (because of Criteria does not support it) I asked help from StackOverflow. The best response was that it’s better to use HQL and forget about LINQ-to-NHibernate in the case of subqueries. Unfortunately that wasn’t a good solution for me, because we were hoping using LINQ-to-NHibernate, we will get rid of weakly typed HQL queries.


In order to find a work around, my colleague Masoud Ramezani suggested to use 2 LINQ queries to get results. One LINQ-to-NHibernate query for no sub query related section of the query and a second LINQ-to-Object query for sections that contain sub queries. Following 2 snippets shows complete query and divided query that act as a work around to sub query problem.

<br />internal List<Letter> SearchLetter(DateTime? fromDate, DateTime? toDate, string receiverOrganizationSearch, SendingInformationDAO sendingInformationDAO)<br />{<br />List<Letter> list = null;<br />var q = from l in session.Linq<Letter>()<br />    where<br />    l.Sec.ID == currentSec.ID &&<br />    (l.Date >= fromDate && l.Date <= toDate)  &&<br />    Yas1(sendingInformationDAO.RetrieveReceivers(l), receiverOrganizationSearch)<br />    select l;<br />return q.ToList<Letter>();<br />}<br />





second code:

<br />internal List<Letter> SearchLetter(DateTime? fromDate, DateTime? toDate, string receiverOrganizationSearch, SendingInformationDAO sendingInformationDAO)<br />{<br />  List<Letter> list = null;<br />  var q = from l in session.Linq<Letter>()<br />    where<br />    l.Sec.ID == currentSec.ID &&<br />    (l.Date >= fromDate && l.Date <= toDate)<br />    select l;<br /><br />  List<Letter> first = q.ToList<Letter>();<br /><br />  var q2 = from l in first<br />           select l;<br /><br />  if (receiverOrganizationSearch != null)<br />    q2 = q2.Where(l => Yas1(sendingInformationDAO.RetrieveReceivers(l), receiverOrganizationSearch));<br /><br />  return q2.ToList<Letter>();<br />}<br />





Yas1 method:

<br />private bool Yas1(EntityCollection<SendingInformation> collection, string org_name)<br />{<br />if (collection == null)<br />    return false;<br /><br />    org_name = org_name.Trim();<br /><br />    foreach (SendingInformation item in collection)<br />    if (item != null && item.To != null && item.To.Organization != null &&<br />                    item.To.Organization.Name != null && item.To.Organization.Name.Contains(org_name))<br />    return true;<br /><br />    return false;<br />}<br />



Please consider this approach works only if you use dynamic queries. If someone puts Yas1 directly in LINQ-to-Object query, that will not work as expected.

UPDATE (11/15/2010) :


LINQ-to-NHibernate does support sub-queries in where clause. For more info go to this and this link. There is also limitations.

‫معرفی LINQ

چند روز پیش مطلبی در همین جا نوشتم به عنوان «داستان بی‌سوادی ما». در آنجا از دست خودم شاکی بودم که چرا به اندازه کافی به روز نیستم و بعضی چیزها را به موقع یاد نمی‌گیرم. تعدادی از دوستان هم با بنده همدردی کرده و گفته بودند آنها هم از همین مشکل رنج می‌برند. حال می‌خواهم در ادامه همان داستان عرض کنم تا دیر نشده به یادگیری کامل LINQ و به کارگیری آن بپردازند. LINQ یک ابزار خیلی خوب برای نوشتن کوئری‌های استخراج اطلاعات از SQL، NHibernate، XML، Object و غیره است که دارای مزایای زیادی است از جمله:

۱- همه چیز Strongly Typed است در نتیجه بسیاری از خطاها در همان زمان کامپایل کشف می‌شوند نه در زمان اجرا. مثلا فرض کنید یک کوئری SQL را به صورت استرینگ در داخل کد سی‌شارپ به کار برده‌اید. اگر این کوئری ایرادی داشته باشد تا زمان اجرا (Runtime) مشخص نخواهد شد. چون کامپایلر سی‌شارپ محتوای داخل استرینگ‌ها را مورد بررسی قرار نمی‌دهد.
۲- با کمک LINQ می‌توان کار با SQL، XML و غیره را در هم آمیخته و مقدار زیادی در نوشتن کد صرفه جویی کرد. مثلا می‌شود یک کوئری LINQ برای استخراج اطلاعات از SQL Server نوشته و در دل همان کوئری یک XML هم ساخت.
۳- کدهای LINQ خواناتر و «قابل نگهداری‌تر» از کدهای SQL، HQL، NHibernate Criteria و غیره می‌باشد.
۴- با یادگیری فقط یک زبان/فناوری به اسم LINQ می‌توان از یادگیری چندین زبان/فناوری/API از جمله XML DOM، SQL، NHibernate Criteria و… خلاص شد.
۵-

پ.ن.۱: برای یادگیری LINQ توصیه می‌کنم از کتاب Manning LINQ in Action استفاده کنید.
پ.ن.۲: برای آشنایی اولیه با LINQ و زیبایی‌های آن از وبلاگ وحید نصیری استفاده فرمایید.
پ.ن.۳: LINQ از نسخه ۳٫۵ به دات‌نت فریمورک اضافه شده است.
پ.ن.۴: مدتی است که LINQ to NHibernate نیز عرضه شده و مورد استفاده دوستان می‌باشد.