LINQ: zrýchlenie databázových dopytov pomocou Compiled Queries
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
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.