User Define Function, 用戶(hù)自定義函數,簡(jiǎn)稱(chēng)UDF. 關(guān)于sql server中的udf,請大家參考
http://msdn.microsoft.com/msdnmag/issues/03/11/DataPoints/一文。本文主要闡述,在Linq To Sql中,如何使用UDF.
1,UDF 簡(jiǎn)介
UDF可以分為兩中類(lèi)型。一種為Scalar Valued Function,簡(jiǎn)稱(chēng)為SVF,是返回值類(lèi)型的UDF. 另一種為T(mén)able Valued Function 簡(jiǎn)稱(chēng)為T(mén)VF,是返回一個(gè)table的UDF. 人們通常喜歡拿UDF和Store Procedure做比較。其實(shí),他們各有千秋。UDF最多只能返回一個(gè)RowSet,而Store Procedure可以是多個(gè)。Store Procedure支持CUD操作,而UDF不支持。但是UDF在sql 中支持內聯(lián)查詢(xún),這個(gè)又是Sprocs所不能及的。因此Linq To Sql 也支持UDF的內聯(lián)查詢(xún)。
2,SVF
看下面這個(gè)例子。返回某個(gè)類(lèi)別產(chǎn)品最小的單元價(jià)格。
CREATE FUNCTION [dbo].[MinUnitPriceByCategory]
(@categoryID INT
)
RETURNS Money
AS
BEGIN
-- Declare the return variable here
DECLARE @ResultVar Money
-- Add the T-SQL statements to compute the return value here
SELECT @ResultVar = MIN(p.UnitPrice) FROM Products as p WHERE p.CategoryID = @categoryID
-- Return the result of the function
RETURN @ResultVar
END 用OR Designer(請參考
OR工具介紹 )將其映射為Dbml。如下
<Function Name="dbo.MinUnitPriceByCategory" Method="MinUnitPriceByCategory" IsComposable="true">
<Parameter Name="categoryID" Type="System.Int32" DbType="Int" />
<Return Type="System.Decimal" />
</Function> 在這里,筆者將帶著(zhù)大家開(kāi)始習慣用dbml來(lái)維護數據庫的映射,而不是code.在beta2發(fā)布后,有人很快就能發(fā)現mapping code無(wú)法編譯了。因為接口改動(dòng)了。好,回歸正題。
無(wú)論是UDF還是Sprocs都會(huì )被映射為Function. 而IsComposable="true"是UDF獨有的一個(gè)特性,是標志UDF身份的,Linq用它來(lái)區別Sprocs和UDF。這個(gè)字段說(shuō)明,該函數是支持內聯(lián)查詢(xún)的。Name則是其在數據庫中的名稱(chēng)。再來(lái)看其生成的code.
[Function(Name="dbo.MinUnitPriceByCategory", IsComposable=true)]
public System.Nullable<decimal> MinUnitPriceByCategory([Parameter(DbType="Int")] System.Nullable<int> categoryID)
{
return ((System.Nullable<decimal>)(this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), categoryID).ReturnValue));
} Linq To Sql將Sprocs和UDF映射成DataContext類(lèi)里的方法的形式,這樣用戶(hù)就可以像調用函數那樣,調用該UDF。因為這個(gè)例子是SVF,所以,返回decimal類(lèi)型的值。再來(lái)看它的應用。剛才說(shuō)過(guò),可以像函數那樣調用它。比如:
int result = db.IntSVF(variable);
int result = db.IntSVF(constant); 再就讓我們來(lái)看幾個(gè)內聯(lián)的查詢(xún)的。所謂內聯(lián)(in-line),就是說(shuō),你可以把UDF當作一個(gè)表(TVF),或一個(gè)變量(SVF),寫(xiě)在sql語(yǔ)句里。比如:
SELECT * FROM Products AS t0
WHERE t0.UnitPrice = dbo.MinUnitPriceByCategory(t0.CategoryID) 在這個(gè)sql語(yǔ)句中,就調用了上面那個(gè)UDF。同樣Linq To Sql也支持這樣操作??梢詫?xiě)為
var q = from p in db.Products
where p.UnitPrice == db.MinUnitPriceByCategory(p.CategoryID)
select p; 大家可以看看其生成的Sql是不是和上面的一樣。再舉一個(gè)UDF的例子
CREATE FUNCTION [dbo].[TotalProductUnitPriceByCategory]
(@categoryID int)
RETURNS Money
AS
BEGIN
-- Declare the return variable here
DECLARE @ResultVar Money
-- Add the T-SQL statements to compute the return value here
SELECT @ResultVar = (Select SUM(UnitPrice)
from Products
where CategoryID = @categoryID)
-- Return the result of the function
RETURN @ResultVar
END 計算某類(lèi)產(chǎn)品的單價(jià)總和。這次,我們在select字句里調用它
SELECT CategoryID, [dbo].[TotalProductUnitPriceByCategory](CategoryID) AS [TotalUnitPrice]
FROM Categories 其同樣功能的Linq語(yǔ)句為:
var q = from c in db.Categories
select new {c.CategoryID, TotalUnitPrice = db.TotalProductUnitPriceByCategory(c.CategoryID)}; 其實(shí),對于SVF,可以放在除from等與table有關(guān)語(yǔ)句之外的任何地方。比如Order by, Group by等。同樣Linq全部支持。如例
var q = from p in db.Products
where p.UnitsOnOrder >= db.SVF(p.UnitsInStock)
group p by db.SVF(p.CategoryID) into g
order by db.SVF(g.Key)
select db.SVF(g.Key); 當然,這個(gè)純粹是給個(gè)例子,并沒(méi)有太多實(shí)際意義。
3,系統函數的映射
目前為止,無(wú)論是OR designer還是SqlMetal均不支持對系統函數的映射。筆者也只是嘗試著(zhù),手工映射,并成功調用。我們拿Var函數舉例。Var是求方差。讓我們來(lái)模仿上面那個(gè)dbml來(lái)改寫(xiě)自己的dbml。我們將要多money類(lèi)型做求var值。并且希望能夠調用sql server提供的var函數。那就需要我們將映射的名稱(chēng)改成var,并且改動(dòng)參數和返回值類(lèi)型。其最后的dbml為:
<Function Name="Var" Method="Var" IsComposable="true">
<Parameter Name="para" Type="System.Decimal" DbType="Money" />
<Return Type="System.Decimal" />
</Function> 其生成的code為:
[Function(IsComposable=true)]
public System.Nullable<decimal> Var([Parameter(DbType="Money")] System.Nullable<decimal> para)
{
return ((System.Nullable<decimal>)(this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod())), para).ReturnValue));
} 將該方法,放在DataContext的一個(gè)partial類(lèi)中,我們并不想破壞其原來(lái)的mapping 文件,所以,單獨放在一個(gè)partial類(lèi)中。而后,我們嘗試著(zhù)Linq To Sql中調用該函數
var q = (from p in db.Products
select db.Var(p.UnitPrice)).ToList(); 其生成的sql為
SELECT CONVERT(Decimal(29,4),Var([t0].[UnitPrice])) AS [value]
FROM [dbo].[Products] AS [t0]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel 我們就這樣騙過(guò)了Linq To Sql的Run-Time。 成功調用sql server提供var函數。再比如,有人習慣于用NEWID()隨機排序,達到取隨機記錄的目的。其原始sql為:
SELECT TOP 10 * FROM TABLE1 ORDER BY NEWID(); 那用Linq To Sql該如何來(lái)做這個(gè)事情呢?不好意思,目前還不支持對系統函數的映射。那就手工來(lái)做吧。
因為NewId返回uniqueidentifier類(lèi)型,我們將這個(gè)函數定義為
[Function(Name = "NewID", IsComposable = true)]
public Guid NewID()
{
return ((Guid)(this.ExecuteMethodCall(this, ((MethodInfo)(MethodInfo.GetCurrentMethod()))).ReturnValue));
}
調用時(shí),可以
var q = db.Table1.OrderBy(p => db.NewID()).Take(10).ToList(); 這只是一個(gè)小技巧,并不說(shuō)明,所有的函數都可以這么做。
4 TVF
返回一個(gè)table 的UDF稱(chēng)為T(mén)VF.看下面例子
CREATE FUNCTION [dbo].[ProductsUnderThisUnitPrice]
(@price Money
)
RETURNS TABLE
AS
RETURN
SELECT *
FROM Products as P
Where p.UnitPrice < @priceTVF在sql中支持from,join,union等操作。同樣,這些操作在Linq To Sql中一樣支持。該TVF的dbml為:
<Function Name="dbo.ProductsUnderThisUnitPrice" Method="ProductsUnderThisUnitPrice" IsComposable="true">
<Parameter Name="price" Type="System.Decimal" DbType="Money" />
<ElementType Name="ProductsUnderThisUnitPriceResult">
<Column Name="ProductID" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
<Column Name="ProductName" Type="System.String" DbType="NVarChar(40) NOT NULL" CanBeNull="false" />
<Column Name="SupplierID" Type="System.Int32" DbType="Int" CanBeNull="true" />
<Column Name="CategoryID" Type="System.Int32" DbType="Int" CanBeNull="true" />
<Column Name="QuantityPerUnit" Type="System.String" DbType="NVarChar(20)" CanBeNull="true" />
<Column Name="UnitPrice" Type="System.Decimal" DbType="Money" CanBeNull="true" />
<Column Name="UnitsInStock" Type="System.Int16" DbType="SmallInt" CanBeNull="true" />
<Column Name="UnitsOnOrder" Type="System.Int16" DbType="SmallInt" CanBeNull="true" />
<Column Name="ReorderLevel" Type="System.Int16" DbType="SmallInt" CanBeNull="true" />
<Column Name="Discontinued" Type="System.Boolean" DbType="Bit NOT NULL" CanBeNull="false" />
</ElementType>
</Function> ElementType子項說(shuō)明了其返回一個(gè)table.映射為類(lèi)的名稱(chēng)為ProductsUnderThisUnitPriceResult。其映射的code中,不光是一個(gè)函數,還有一個(gè)對應的返回類(lèi)。當然,這個(gè)返回類(lèi)的定義,可以由用戶(hù)自己指定。此處不講。我們使用默認的類(lèi)。我們先來(lái)看一個(gè)from的例子
var q = from p in db.ProductsUnderThisUnitPrice(10.25M)
select p; 你可以就把該udf當作一個(gè)普通的表來(lái)使用。再舉一個(gè)join操作Linq To Sql的例子
var q = from c in db.Categories
join p in db.ProductsUnderThisUnitPrice(8.50M) on c.CategoryID equals p.CategoryID into prods
from p in prods
select new {c.CategoryID, c.CategoryName, p.ProductName, p.UnitPrice}; 因為,sql中支持TVF的in-line操作,所以L(fǎng)inq To Sql完全支持其對等的操作。他們所生成的sql語(yǔ)句不再列出。
總結:
通過(guò)本文,我們可以看出Linq To Sql完全融入了Sql中UDF,包括對其內聯(lián)操作的支持。對于某些特殊需求,用戶(hù)可以手工將函數映射為code,但這并不說(shuō)明,任何函數都適用。