第11章函數函數提供了一個有力代碼復用機制, 並且讓你的代碼保持簡潔和易懂。它們同樣也是EF運行時能利用的資料庫層代碼.函數有幾類: Rowset Functions, 聚合函數, Ranking Functions, 和標量值函數.函數要麼確定,要麼不確定。當用一些指定的值調用函數,而函數返回的結...
第11章函數
函數提供了一個有力代碼復用機制, 並且讓你的代碼保持簡潔和易懂。
它們同樣也是EF運行時能利用的資料庫層代碼.函數有幾類: Rowset Functions, 聚合函數, Ranking Functions, 和標量值函數.
函數要麼確定,要麼不確定。當用一些指定的值調用函數,而函數返回的結果總是一樣時,它就是確定的函數。當甚至用同樣的一些值調用時,而函數每次返回的結果也可能不一樣,它就是不確定的函數。
在前七小節,我們探討“模型定義”的函數,這些函數允許我們在概念層上創建。這些函數依照EF類型和你的模型實體來定義。這樣使得它們能便捷地通過數據存儲執行。
在剩下的小節,我們就展示如何使用被EF定義的函數和資料庫層的函數.
這些函數允許你影響已有的代碼,在EF運行時或更接近於你數據的資料庫層.
11-1. 從“模型定義”函數返回一個標量值
問題
想在概念模型定義一個函數,接受一個實體的實例,並且返回一個標量值.
解決方案
假設已有一個如Figure 11-1.所示模型
Figure 11-1. 一個產品和分類的模型
接下來創建一個接受一個Category 實體實例並返回給定Category 里所有product的平均單價:
1. 在解決方案資源管理器中,右擊 .edmx 文件,選擇“打開方式” ➤ XML 編輯器.
2.在.edmx文件的概念模型(conceptual models)節點<Schema>標簽里,插入Listing 11-1里的代碼,這樣就在模型里定義了函數。
Listing 11-1. Definition of the AverageUnitPrice() Function in the Model
<Function Name="AverageUnitPrice" ReturnType="Edm.Decimal">
<Parameter Name="category" Type="EFRecipesModel1101.Category" />
<DefiningExpression>
ANYELEMENT(Select VALUE Avg(p.UnitPrice) from EFRecipesEntities1101.Products as p where p.Category == category)
</DefiningExpression>
</Function>
3. 插入和查詢這個模型的代碼,如Listing 11-2所示.
class Program
{
static void Main(string[] args)
{
RunExample();
Console.WriteLine("\nPress any key to exit...");
Console.ReadKey();
}
static void RunExample()
{
using (var context = new EFRecipesEntities1101())
{
context.Database.ExecuteSqlCommand("delete from chapter11.product;delete from chapter11.category");
var c1 = new Category { CategoryName = "Backpacking Tents" };
var p1 = new Product
{
ProductName = "Hooligan",
UnitPrice = 89.99M,
Category = c1
};
var p2 = new Product
{
ProductName = "Kraz",
UnitPrice = 99.99M,
Category = c1
};
var p3 = new Product
{
ProductName = "Sundome",
UnitPrice = 49.99M,
Category = c1
};
context.Categories.Add(c1);
context.Products.Add(p1);
context.Products.Add(p2);
context.Products.Add(p3);
var c2 = new Category { CategoryName = "Family Tents" };
var p4 = new Product
{
ProductName = "Evanston",
UnitPrice = 169.99M,
Category = c2
};
var p5 = new Product
{
ProductName = "Montana",
UnitPrice = 149.99M,
Category = c2
};
context.Categories.Add(c2);
context.Products.Add(p4);
context.Products.Add(p5);
context.SaveChanges();
}
// with eSQL
using (var context = new EFRecipesEntities1101())
{
Console.WriteLine("Using eSQL for the query...");
Console.WriteLine();
string sql = @"Select c.CategoryName, EFRecipesModel1101
.AverageUnitPrice(c) as AveragePrice from
EFRecipesEntities1101.Categories as c";
var objectContext = (context as IObjectContextAdapter).ObjectContext;
var cats = objectContext.CreateQuery<DbDataRecord>(sql);
foreach (var cat in cats)
{
Console.WriteLine("Category '{0}' has an average price of {1}",
cat["CategoryName"], ((decimal)cat["AveragePrice"]).ToString("C"));
}
}
// with LINQ
using (var context = new EFRecipesEntities1101())
{
Console.WriteLine();
Console.WriteLine("Using LINQ for the query...");
Console.WriteLine();
var cats = from c in context.Categories
select new
{
Name = c.CategoryName,
AveragePrice = MyFunctions.AverageUnitPrice(c)
};
foreach (var cat in cats)
{
Console.WriteLine("Category '{0}' has an average price of {1}",
cat.Name, cat.AveragePrice.ToString("C"));
}
}
}
}
public class MyFunctions
{
[EdmFunction("EFRecipesModel1101", "AverageUnitPrice")]
public static decimal AverageUnitPrice(Category category)
{
throw new NotSupportedException("Direct calls are not supported!");
}
}
Listing 11-2.用 “模型定義” 的AverageUnitPrice()函數插入和查詢模型
輸出結果如下麵的 Listing 11-2所示:
Using eSQL for the query...
Category 'Backpacking Tents' has an average price of $79.99
Category 'Family Tents' has an average price of $159.99
Using LINQ for the query...
Category 'Backpacking Tents' has an average price of $79.99
Category 'Family Tents' has an average price of $159.99
它是如何工作的?
“模型定義”的函數,在概念層創建,並且用eSQL來寫. 當然, “模型定義”允許你引用你模型中的實體,就像我們這裡做的這樣,在函數的實現中引用了Category 實體和Product 實體以及它們之間的關係。函數帶來的額外好處是:我們不會被綁定在一個指定的存儲層上。把函數放在更低的層,甚至是資料庫驅動, 我們的程式也可以工作.
目前的設計器不支持“模型定義”函數,不像存儲過程,能被設計器支持,“模型定義”函數不會被模型瀏覽器顯示也不會出現在設計器的其它地方。設計器也不會檢查eSQL中的語法錯誤,只有在運行時才會報錯,但至少可打開.edmx來定義。
在Listing 11-2,代碼先插入兩個類別(category)和各自的一些產品(product).之後用兩種略微不同的方式查詢這些數據。在第一個查詢例子,我們創建eSQL語句來調用AverageUnitPrice() 函數.並執行查詢. 在查詢結果中的每行,我們取出第一列數據(category名稱)和第二列數據(每個類別產品的平均單價). 並且輸出。
第二個查詢例子,更有趣,我們在LINQ查詢中使用AverageUnitPrice()函數,不過需要先在另一個類里添加一個方法存根,方法用 [EdmFunction()] 特性裝飾, 把它標記為是一個“模型定義”函數. 運行時方法不可以調用它(一旦調用,方法中就顯式拋出異常). 因為我們只是返回一個標量值,所以這個方法簽名比較簡單(參數個數,類型,和返回值類型). 在In the LINQ 查詢中query, 我們獲取每個category並且把結果(category名稱,調用MyFunction類里AverageUnitPrice()方法返回的結果)映射到一個匿名類. 並且輸出。
DbContext是 ObjectContext輕量級的版本. 每當需要執行eSql (Entity SQL)時, 是必須使用ObjectContext 的. 因為我們要通過DbContext獲取ObjectContext (使用:(context as IObjectContextAdapter) ObjectContext).
“模型定義”函數的參數可以是:標量值,實體類,複雜類型,匿名類型,或是上述類型的集合).在本章的很多小節,我們就演示如何創建和使用這些類型參數的“模型定義”函數。
“模型定義” 函數的參數沒有方向性,沒有“輸出”參數,只有“輸入”參數,原因是“模型定義” 函數只是一個”組件”,並且能成為LINQ查詢的一部分。
在這個例子中,我們返回單一的標量decimal類型的值。因為Select查詢結果會被理解成一個集合,所以我們需要為返回的結果顯式地使用AnyElement運算符。EF不知道如何把一個集合映射成一個標量值,所以我們在這兒使用AnyElement運算符告訴它返回的結果只是一個元素。當Select結果只有一個元素的時候,我們也會運用該運算符告訴調用者它只是一個元素。
最佳實踐
“模型定義”函數提供了一個純凈和有效的概念模型的組成部分.下麵列幾個它的最佳實踐:.
>“模型定義”函數用eSQL 定義到概念層. 這使用我們可以從存儲模型細節中抽象出一個更完成的模型.
>你可以把LINQ或eSQL查詢中常用的表達定義成函數. 這樣使代碼組織結構更好並且可復用. 當然,如果使用LINQ, VS提供的智能感知和編譯時檢查,會讓代碼減少因為誤輸入帶來的問題.
>“模型定義”函數是一個”組件”,允許你把它當成一個組成部分用在更複雜的表達式中. 這樣可以合你代碼更簡單些,並具可維護性.
>“模型定義”函數能被用在有需要計算的地方,比如一個需要計算的屬性,當實體被實例化會帶來計算的消耗,不管你用沒用到這個屬性,而“模型定義”函數只是在你確實用到這個屬性時,它才去計算這個屬性值.