Entity Framework 6 Recipes 2nd Edition(11-1)译 -> 从“模型定义”函数返回一个标量值

时间:2023-12-19 10:14:56

11函数

函数提供了一个有力代码复用机制, 并且让你的代码保持简洁和易懂。

它们同样也是EF运行时能利用的数据库层代码.函数有几类: Rowset Functions, 聚合函数, Ranking Functions, 和标量值函数.

函数要么确定,要么不确定。当用一些指定的值调用函数,而函数返回的结果总是一样时,它就是确定的函数。当甚至用同样的一些值调用时,而函数每次返回的结果也可能不一样,它就是不确定的函数。

在前七小节,我们探讨“模型定义”的函数,这些函数允许我们在概念层上创建。这些函数依照EF类型和你的模型实体来定义。这样使得它们能便捷地通过数据存储执行。

在剩下的小节,我们就展示如何使用被EF定义的函数和数据库层的函数.

这些函数允许你影响已有的代码,在EF运行时或更接近于你数据的数据库层.

11-1. 从“模型定义”函数返回一个标量值

问题

想在概念模型定义一个函数,接受一个实体的实例,并且返回一个标量值.

解决方案

假设已有一个如Figure 11-1.所示模型

Entity Framework 6 Recipes 2nd Edition(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提供的智能感知和编译时检查,会让代码减少因为误输入带来的问题.

>“模型定义”函数是一个”组件”,允许你把它当成一个组成部分用在更复杂的表达式中. 这样可以合你代码更简单些,并具可维护性.

>“模型定义”函数能被用在有需要计算的地方,比如一个需要计算的属性,当实体被实例化会带来计算的消耗,不管你用没用到这个属性,而“模型定义”函数只是在你确实用到这个属性时,它才去计算这个属性值.

附:创建示例用到的数据库的脚本文件