常用SQL語法與LINQ比對,以及效能比較
常用SQL語法與LINQ比對,以及效能比較
簡介 |
介紹常用的SQL語法在採用LINQ時的表現方式,以及如何避免不恰當的寫法照成DB效能不佳 |
作者 |
陳仲和 |
string queryString = "SELECT a.[FirstName], a.[MiddleName],a.[LastName], d.City,d.AddressLine1,d.AddressLine2 "
+ "FROM[Person].[Person] a "
+ "join Person.BusinessEntity b on a.BusinessEntityID= b.BusinessEntityID "
+ "join Person.BusinessEntityAddress c on a.BusinessEntityID = c.BusinessEntityID "
+ "left join person.Address d on c.AddressID = d.AddressID";
var person = (from a in db.Person
join b in db.BusinessEntity on a.BusinessEntityID equals b.BusinessEntityID
join c in db.BusinessEntityAddress on a.BusinessEntityID equals c.BusinessEntityID
join d in db.Address on new { AddressID = c.AddressID} equals new { AddressID = d.AddressID} into ps
from d in ps.DefaultIfEmpty()
select new {
a.FirstName,
a.MiddleName,
a.LastName,
d.City,
d.AddressLine1,
d.AddressLine2
})
;
在此範例中因為欄位AddressLine1只是被拿來參照用,可以為Null,故採用Left Join的方式取得資料,在LINQ中可以看到是採用匿名類別方式產生屬性為AddressID的物件來做比較並將結果存放入ps變數中,之後再將可table d default可為empty的方式將AddressLine1與AddressLine2的方式取出,即可完成LINQ的寫法。
2.Case When..Else..End
在SQL裏,將欄位查詢時順便做比較而給予新的值的方式也是常用的寫法,範例如下
2.1. SQL範例
queryString = "select a.FirstName, a.LastName, e.DepartmentID, e.GroupName,"
+ "case when d.StartTime >='07:00:00' and d.EndTime<= '15:00:00' then 'Day' Else 'Night' end as Shift, "
+ "d.StartTime,d.EndTime "
+ "from Person.Person a "
+ "join HumanResources.Employee b on a.BusinessEntityID = b.BusinessEntityID "
+ "join HumanResources.EmployeeDepartmentHistory c on b.BusinessEntityID = c.BusinessEntityID "
+ "join HumanResources.Shift d on c.ShiftID = d.ShiftID "
+ "join HumanResources.Department e on c.DepartmentID = e.DepartmentID ";
2.2. LINQ範例
var shift = (from e in db.Person
join f in db.Employee on e.BusinessEntityID equals f.BusinessEntityID
join g in db.EmployeeDepartmentHistory on f.BusinessEntityID equals g.BusinessEntityID
join h in db.Shift on g.ShiftID equals h.ShiftID
join i in db.Department on g.DepartmentID equals i.DepartmentID
select new
{
e.FirstName,
e.LastName,
i.DepartmentID,
i.GroupName,
Shift = h.StartTime >= new TimeSpan(7, 0, 0) && h.EndTime <= new TimeSpan(15, 0, 0)
? "Day" : "Night",
h.StartTime,
h.EndTime
}
);
Case When在LINQ上的寫法反而很簡單,就是大家常用的IF..ELSE寫法,只是要注意的是因為是物件導向寫法,比較時還要多處理物件型態的一致才有辦法做比較,在這個案例中是將上下班時間區分出早班與晚班的方式。
3.SQL Function
在SQL執行時也會常常用到內建的Function做資料轉換與計算,LINQ也有提供相對應的寫法
3.1.SQL範例
queryString = "select a.FirstName, a.LastName,b.BirthDate, b.HireDate, "
+ "DATEDIFF(year, b.BirthDate, getdate()) as yearsOld "
+ "from Person.Person a "
+ "join HumanResources.Employee b on a.BusinessEntityID = b.BusinessEntityID "
+ "where b.HireDate >= '2009-01-01' ";
3.2. LINQ範例
var compareDate = DateTime.Parse("2009-01-01");
var years = (from j in db.Person
join k in db.Employee on j.BusinessEntityID equals k.BusinessEntityID
where k.HireDate >= compareDate
select new
{
j.FirstName,
j.LastName,
k.BirthDate,
k.HireDate,
yearsOld = (int?)SqlFunctions.DateDiff("year", k.BirthDate, SqlFunctions.GetDate())
}
);
這裡用到的是計算兩個DateTime型別的差值的SQL Function DATEDIFF(),此範例是以現在日期的年減掉出生日的年來取得職員的歲數。首先,在LINQ裡首先要先using System.Data.Entity.SqlServer;然後就可以採用MSSQL裡內建的Function。此外本次範例還有加上Where條件,一樣是要注意比較欄位時的條件型態需要一致。
四、測試結果與結論
1. 利用SqlDataReader查詢結果
2.利用LINQ透過ORM查詢結果
在SQL執行放置Start Time與End Time是夾在command.ExecuteReader();執行的前後,而LINQ執行時是夾在.ToList()執行的前後,由上表可以看出至少目前比較常用的SQL語法,採用LINQ寫法上在執行時並沒有比較慢進而造成系統的延宕,故在此個人還是做個結論:
採用SQL或是LINQ作為關聯式資料庫的基本查詢理論上應差異不大,花時間辯證哪個比較好用不如發時間注意自己的查詢條件是否得當,尤其是開發人員應當對要查詢的來源資料要有一定暸解,並且對查詢的結果要自己大概預估一下會是如何的結果,在此分享幾個查詢關聯式資料庫時該注意的幾個觀念
1.查詢時是否有用到Index查詢?
2.是否有採用Where條件限制資料的查詢,尤其是查詢history或是Log table更要小心,避免Full table scan查詢。
3.Join Table時除了key值相等條件之外,是否還可以加上限制條件讓被Join的資料取樣可以愈少愈好?
4.查詢的資料如果常常新刪修的話,是否有定期做Index reorganize或是Index rebuild?
就上述幾點跟各位先進分享。
手癢來分享一下幾個用法。
queryString 多行,可以使用 @"" Literals string 來處理,可以直接斷行並保持指令碼格式,這樣會比較好看。(註1)
單行則可以用使 $"" interpolated string。(註2)
要留意一點,如果目標 Database 是 Oracle 時,使用 Literals string 來傳遞 sql 字串時,斷行及大量空白字串可能會在 db 端產生不正確的 sql script,造成程式錯誤。
另外,在無法或不想使用 EF 的場合,可以使用有官方支援的 3rd Extension, Dapper,底層同樣是 ADO.Net,高效好用。(註3)
組 sql 指令碼時,請一定要留意 sql injection 的問題。
#1. https://csharpindepth.com/Articles/Strings
#2. https://dapper-tutorial.net/dapper
#3. https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated