‫تعیین نوع fetch در Search APIهای NHibernate

برای آن که NHibernate بتواند به طور بهینه از Caching استفاده نماید باید Fetch در Entityها و Queryها به طور مناسبی تعریف شده باشد. Fetch به طور کلی به NHibernate می‌گوید sql دریافت اطلاعات از دیتابیس را چطور تولید کند. یک select کلی از همه جداول با استفاده از outer joinهای متعدد یا selectهای جداگانه به ازای هر جدول. مزیت عمده select جداگانه وقتی است که آن اطلاعات از قبل در Cache موجود باشند.

Fetch در Queryهای دیتابیسی NHibernate و APIهای مختلف جستجوی آن روش‌های متفاوتی دارد:

  • در HQL با استفاده join fetch انجام می‌شود. ±
  • در Linq-to-NH با استفاده از Expand و Fetch انجام می‌شود. ±
  • در QueryOver با Fetch انجام می‌شود (مشابه linq-to-nh). ±
  • در ICriteria با SetFetchMode انجام می‌شود. ±

‫افزایش 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 اصلی هیچ فرقی ندارد.

Limitations of LINQ-to-NHibernate

Some applications are using NHibernate 2.1.2 yet. So they are forced to use old LINQ-to-NHibernate that comes with NHibernate 2.1.2 and can’t benefit new LINQ provider in NH 3.

There are two annoying problems with old LINQ-to-NHibernate. The first is inheritance related queries: “is” operator can’t be used. The second problem is not supporting “distinct”. For the first problem I added a read-only formula field based on discriminator column to “hbm” mapping file. After this, a query can use this fake field instead of “is” operator. But for the second problem I didn’t find any solution. Possibly I should use ICriteria instead of LINQ-to-NHibernate.

More info: link 1, link 2, link 3.

‫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 و دیتابیس بود، فراخوانی شوند نه آن که صرفاً آن بخش خاص بازیابی شوند.

Access NHibernate.ISession from Castle ActiveRecord

If you are a Castle ActiveRecord user you may currently encountered situations that it is needed to do some specific operation that is possible just via NHibernate or even just need to direct access to NHibernate’s ISession. For example if you want to use LINQ-to-NHibernate or run SQL query with Castle ActiveRecord, there is no way other than direct access to ISession.

To access NHibernate’s ISession you must utilize ActiveRecordMediator.Execute() or ActiveRecordBase.Execute(). This method accepts a NHibernateDelegate delegate. Then through this delegate you have access to NHibernate’s ISession. Consider following code that uses LINQ-to-NHibernate through Castle ActiveRecord:

[ActiveRecord(Lazy = true)]
public class Car: ActiveRecordBase
{
public static List GetCars()
{
return (List)Execute(
delegate(ISession session, object instance)
{
//here you have direct access to ISession

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

return q.ToList();
}, null);
}
}

For more examples go to this, this and this link.

Paging methods

ASP.NET GridView and other similar controls that support paging needs special methods that return only the requested slice of data and  the count of total data. As I’m a lover of NHibernate/Castle ActiveRecord I have gathered paging techniques in NHibernate HQL, LINQ-to-NHibernate and Castle ActiveRecord:

NHIbernate HQL:

public IList GetData(int page, int pageSize, ref long count)
{
    IList result = null;
    string query_string = “from my_class”;
    IMultiQuery query = session.CreateMultiQuery()
        .Add(session.CreateQuery(query_string)
        .SetFirstResult((page – 1) * pageSize)
        .SetMaxResults(pageSize))
        .Add(“select count(*) ” + query_string);
    IList list = query.List();
    result = (IList)list[0];
    count = (long)((IList)list[1])[0];
    return result;
}

LINQ-to-NHibernate:

internal List GetData(int page, int pageSize)
{

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

    return q
        .Skip((page – 1) * pageSize)
        .Take(pageSize)
        .ToList();
}

Castle ActiveRecord:

public static Company[] FindAll(int maximumRows, int startRowIndex, string sortExpression)
{
    Order[] orders;

    if (string.IsNullOrEmpty(sortExpression))
        orders = new Order[0];
    else
    {
        orders = new Order[1];
        const string DESC = ” DESC”;
        if (sortExpression.EndsWith(DESC))
            orders[0] = Order.Desc(sortExpression.Replace(DESC, string.Empty));
        else
            orders[0] = Order.Asc(sortExpression);
    }

    return SlicedFindAll(startRowIndex, maximumRows, orders);
}

public static int TotalCount()
{
    return Count();
}

For more info refer to this link, link, link and this link.

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 نیز عرضه شده و مورد استفاده دوستان می‌باشد.