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

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

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

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

 

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

 

 

posted @ 2016-01-24 02:27  kid1412  阅读(294)  评论(0编辑  收藏  举报