Igor Kulman

LINQ: zrýchlenie databázových dopytov pomocou Compiled Queries

· Igor Kulman

Ak používate .NET a jazyk C#, určite poznáte a hojne využívate aj jednu z jeho hlavných predností, a to LINQ. Použitie LINQ je síce veľmi jednoduché, preklad do SQL pri LINQ to SQL však niečo stojí a pri náročných použitiach môžete pozorovať nízky výkon vašej aplikácie. Odpoveďou na problémy s výkonnosťou sú Compiled Queries.

Majme statickú triedu DB a v nej metódu ProcessUsers(List users)

internal static void ProcessUsers(List users)
{ 
  DateTime d = new DateTime();
  foreach (LdapUser user in users)
  {
    User dbUser = (from u in db.Users where u.dxrUid == user.dxrUid select u).SingleOrDefault();
    if (dbUser==null) 
    {
      dbUser = new User();
      db.Users.InsertOnSubmit(dbUser);
      ...
      db.SubmitChanges(); 
    }

    UserInTime dbUserInTime = (from ut in db.UserInTimes where ut.UserId == dbUser.Id orderby ut.StartDate descending select ut).FirstOrDefault();
    if (dbUserInTime == null)
    {
      dbUserInTime = new UserInTime();
      db.UserInTimes.InsertOnSubmit(dbUserInTime);
      dbUserInTime.StartDate = DateTime.Now;
      ...
      db.SubmitChanges();
    } else if (...)
    {
      UserInTime dbUserInTimeNew = new UserInTime();
      ...
      db.SubmitChanges();
    }
  }
}

Úlohou tejto metódy je pre každého zamestnanca, ktorý príde z LDAPu skontrolovať, či sa záznam o ňom nachádza v DB (ak nie, vytvorí sa) a či sa na ňom niečo zmenilo (ak áno, vytvorenie nového záznamu s platnosťou od aktuálneho času a novými údajmi). Ako si môžete všimnúť, dopyty na získanie zamestnanca a jeho najnovšieho záznamu sa opakujú v každom kroku cyklu, ich opakovaný preklad do SQL a následne vyhodnotenie nie sú práve efektívne.

Správnou optimalizáciou je použitie tzv. Compiled Queries pomocou statickej triedy CompiledQuery a jej metódy Compile, ktorej použitie si môžete predstaviť ako

CompiledQuery.Compile((DataContext, arg0, arg1 ...) => linq query)

teda v našom prípade by to pre výber zamestnanca bolo

CompiledQuery.Compile((DataContext db, string dxrUid) => (from u in db.GetTable<user>() where u.dxrUid == dxrUid select u).SingleOrDefault())

Na takúto Complied Query potom môžeme volať pomocou definovanej funkcie (alebo skôr referencie), najlepšie uzavretej v novej statickej triede

internal static class DBQuery
{
  internal static Func<datacontext,string> getUser = CompiledQuery.Compile((DataContext db, string dxrUid) => (from u in db.GetTable<user>() where u.dxrUid == dxrUid select u).SingleOrDefault());
  internal static Func<datacontext,int> getUserInTime = CompiledQuery.Compile((DataContext db, int UserId) => (from ut in db.GetTable<userintime>() where ut.UserId == UserId orderby ut.StartDate descending select ut).FirstOrDefault());
}

V pôvodnej metóde ostáva už iba nahradiť

User dbUser = DBQuery.getUser(db, user.dxrUid);
...
UserInTime dbUserInTime = DBQuery.getUserInTime(db,dbUser.Id);

za

internal static void ProcessUsers(List users)
{ 
  DateTime d = new DateTime();
  foreach (LdapUser user in users)
  {
    User dbUser = (from u in db.Users where u.dxrUid == user.dxrUid select u).SingleOrDefault();
    if (dbUser==null) 
    {
      dbUser = new User();
      db.Users.InsertOnSubmit(dbUser);
      ...
      db.SubmitChanges(); 
    }

    UserInTime dbUserInTime = (from ut in db.UserInTimes where ut.UserId == dbUser.Id orderby ut.StartDate descending select ut).FirstOrDefault();
    if (dbUserInTime == null)
    {
      dbUserInTime = new UserInTime();
      db.UserInTimes.InsertOnSubmit(dbUserInTime);
      dbUserInTime.StartDate = DateTime.Now;
      ...
      db.SubmitChanges();
    } else if (...)
    {
      UserInTime dbUserInTimeNew = new UserInTime();
      ...
      db.SubmitChanges();
    }
  }
}

Nárast výkonu môže byť až päťnásobný. Tento postup sa oplatí používať na všetky mnohonásobne opakované dopyty.