• Entity Framework应用:导航属性


    一、主键和外键

    关系型数据库中的一条记录中有若干个属性,若其中某一个属性组是能唯一标识一条记录,该属性组就可以称为主键。例如:

    学生版(学号、姓名、性别、班级)

    其中每个学生的学号是唯一的,学号就是一个主键。

    课程表(课程编号,课程名,学分)

    其中课程编号是唯一的,课程编号就是一个主键。

    成绩表(学号、课程号、成绩)

    成绩表中单独的一个属性无法唯一标识一条记录,学号和课程号的组合才能唯一标识一条记录,所以学号和课程号的属性组是一个主键。

    外键

    成绩表中的学号不是成绩表的主键,但它和学生表中的学号相对应,并且学生表中的学号是学生表的主键,则称成绩表中的学号是学生表的外键。同理:成绩表中的课程号是课程表的外键。

    EntityFramework中的导航属性即外键。下面通过例子讲解如何使用EF的导航属性。

    二、导航属性

    1、新建产品分类表,语句如下:

    1 CREATE table Category
    2 (
    3   CategoryId int primary key not null identity,
    4   CategoryName varchar(64)
    5 )

     新建产品明细表,其中CategoryId是外键

     1 CREATE TABLE [dbo].[ProductDetail](
     2     [ProductId] [int] IDENTITY(1,1) NOT NULL,
     3     [ProductName] [varchar](32) NULL,
     4     [Price] [decimal](9, 2) NULL,
     5     [CategoryId] [int] NULL,
     6 PRIMARY KEY CLUSTERED 
     7 (
     8     [ProductId] ASC
     9 )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    10 ) ON [PRIMARY]
    11 
    12 GO
    13 
    14 SET ANSI_PADDING OFF
    15 GO
    16 
    17 ALTER TABLE [dbo].[ProductDetail]  WITH CHECK ADD  CONSTRAINT [FK_Category] FOREIGN KEY([CategoryId])
    18 REFERENCES [dbo].[Category] ([CategoryId])
    19 GO
    20 
    21 ALTER TABLE [dbo].[ProductDetail] CHECK CONSTRAINT [FK_Category]
    22 GO

     分别往Category表和ProductDetail表中插入一些测试数据:

     1 --Category表插入数据
     2 INSERT INTO Category (CategoryName) 
     3 select '电子产品' union
     4 SELECT '家用电器' UNION
     5 SELECT '图书'
     6 
     7 --ProductDetail表插入数据
     8 INSERT INTO ProductDetail (ProductName,Price,CategoryId)
     9 SELECT '苹果6s手机',5633,1 UNION
    10 SELECT 'Dell电脑',6998,1 UNION
    11 SELECT '佳能相机',5633,1 UNION
    12 SELECT '海尔洗衣机',1234,2 UNION
    13 SELECT '格力空调',2344,2 UNION
    14 SELECT '美的冰箱',3218,2 UNION
    15 SELECT '白鹿原',342,3 UNION
    16 SELECT 'C#高级编程(第十版)',145,3 UNION
    17 SELECT '平凡的世界',231,3

     2、使用DataBase First模式生成edmx文件,然后查看Category表和ProductDetail表相对应的实体的定义

    Category表定义:

     1 //------------------------------------------------------------------------------
     2 // <auto-generated>
     3 //     此代码已从模板生成。
     4 //
     5 //     手动更改此文件可能导致应用程序出现意外的行为。
     6 //     如果重新生成代码,将覆盖对此文件的手动更改。
     7 // </auto-generated>
     8 //------------------------------------------------------------------------------
     9 
    10 namespace EFNavigateDemo
    11 {
    12     using System;
    13     using System.Collections.Generic;
    14     
    15     public partial class Category
    16     {
    17         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    18         public Category()
    19         {
    20             this.ProductDetails = new HashSet<ProductDetail>();
    21         }
    22     
    23         public int CategoryId { get; set; }
    24         public string CategoryName { get; set; }
    25     
    26         [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    27         public virtual ICollection<ProductDetail> ProductDetails { get; set; }
    28     }
    29 }

     Category实体类中有一个ProductDetail类型的集合属性,表示是导航属性。

    实体类型包含其他实体类型(POCO类)的属性(也可称为导航属性),且同时满足如下条件即可实现延迟加载:

    1.该属性的类型必须为public且不能为Sealed。

    2.属性标记为Virtual。

    ProductDetail实体类定义如下:

     1 //------------------------------------------------------------------------------
     2 // <auto-generated>
     3 //     此代码已从模板生成。
     4 //
     5 //     手动更改此文件可能导致应用程序出现意外的行为。
     6 //     如果重新生成代码,将覆盖对此文件的手动更改。
     7 // </auto-generated>
     8 //------------------------------------------------------------------------------
     9 
    10 namespace EFNavigateDemo
    11 {
    12     using System;
    13     using System.Collections.Generic;
    14     
    15     public partial class ProductDetail
    16     {
    17         public int ProductId { get; set; }
    18         public string ProductName { get; set; }
    19         public Nullable<decimal> Price { get; set; }
    20         public Nullable<int> CategoryId { get; set; }
    21     
    22         public virtual Category Category { get; set; }
    23     }
    24 }

    ProductDetail类里面有一个Category类型的属性。

    导航属性实现延迟加载的四种方式:

    1、方式一

     1 using (var dbContext = new CategoryEntities())
     2 {                
     3         dbContext.Configuration.LazyLoadingEnabled = true; // 默认是true,针对导航属性
     4          var categoryList = dbContext.Set<Category>().Where(p => p.CategoryId == 3);
     5          // 只会在数据库里面查询Category表,不会查询ProductDetail表
     6          foreach(var category in categoryList)
     7          {                
     8               Console.WriteLine("CategoryId:"+category.CategoryId+ ",CategoryName:"+category.CategoryName);
     9               // 这时才会去数据库查询ProductDetail表
    10               foreach (var product in category.ProductDetails)
    11               {                                     
    12                   Console.WriteLine("ProductName:"+product.ProductName);
    13               }
    14           }
    15 }

     分别在两处foreach循环的地方添加断点,然后运行程序查看数据库执行的SQL语句情况:

    执行到断点1时:

    这时查看数据库监控:

    继续执行到断点2:

    这时在查看数据库监控:

    会发现遍历ProductDetails属性时也会查询ProductDetail表。

    2、方式二

     1 using (var dbContext = new CategoryEntities())
     2 {
     3       dbContext.Configuration.LazyLoadingEnabled = false; // 不延迟加载,不会再次查询了
     4       var categoryList = dbContext.Set<Category>().Where(p => p.CategoryId == 3);
     5        // 只会在数据库里面查询Category表,不会查询ProductDetail表
     6        foreach (var category in categoryList)
     7        {
     8             Console.WriteLine("CategoryId:" + category.CategoryId + ",CategoryName:" + category.CategoryName);
     9             // 这时不会去数据库查询了,所以用户全是空的
    10             foreach (var product in category.ProductDetails)
    11             {
    12                Console.WriteLine("ProductName:" + product.ProductName);
    13             }
    14        }
    15 }

     这时还是采用和上面一样的方法加入断点,只需要查看第二次循环时的数据库监控情况即可:

    从上面的截图中看出,如果LazyLoadingEnabled设置为false,将不会再查询ProductDetail表的数据了。

    3、方式三

     1 // 显示加载
     2 using (var dbContext = new CategoryEntities())
     3 {
     4        // 不延迟加载,指定Include,一次性加载主表和从表的所有数据
     5        var categoryList = dbContext.Set<Category>().Include("ProductDetails").Where(p => p.CategoryId == 3);
     6        foreach (var category in categoryList)
     7        {
     8             Console.WriteLine("CategoryId:" + category.CategoryId + ",CategoryName:" + category.CategoryName);
     9             // 不会再查询
    10             foreach (var product in category.ProductDetails)
    11             {
    12                Console.WriteLine("ProductName:" + product.ProductName);
    13             }
    14        }
    15 }

     使用Include()方法会一次性加载所有的数据:

    4、方式四

    在上面的方式2中把LazyLoadingEnabled设置为false以后就不会再查询ProductDetail表的数据了,这时如果想要查询ProductDetail表的数据该怎么办呢?这时可以使用手动加载,代码如下:

     1 //LoadProperty 手动加载
     2 using (var dbContext = new CategoryEntities())
     3 {
     4       dbContext.Configuration.LazyLoadingEnabled = false; // 不延迟加载,不会再次查询了
     5       var categoryList = dbContext.Set<Category>().Where(p => p.CategoryId == 3);
     6       foreach (var category in categoryList)
     7       {
     8             Console.WriteLine("CategoryId:" + category.CategoryId + ",CategoryName:" + category.CategoryName);
     9             dbContext.Entry<Category>(category).Collection(p => p.ProductDetails).Load();// 集合显示加载
    10              foreach (var product in category.ProductDetails)
    11              {
    12                  Console.WriteLine("ProductName:" + product.ProductName);
    13              }
    14        }
    15 }

    添加断点:

     

    查看数据库监控:

    5、插入数据

    对于Category和ProductDetail表如何同时插入数据?先看下面的一段代码:

     1 using (var dbContext = new CategoryEntities())
     2 {
     3       using (TransactionScope trans = new TransactionScope())
     4       {
     5            Category category = new Category()
     6            {
     7                 CategoryName = "自行车"
     8            };
     9            dbContext.Categories.Add(category);
    10            dbContext.SaveChanges();//category.CategoryId赋值了
    11            ProductDetail product = new ProductDetail()
    12            {
    13                  ProductName = "美利达",
    14                  Price = 2312,
    15                  CategoryId = category.CategoryId
    16             };
    17 
    18             dbContext.ProductDetails.Add(product);
    19             dbContext.SaveChanges();
    20             trans.Complete();//提交事务
    21       }
    22 }

    在第一次SaveChanges()后面的一行代码加断点,查看Category信息:

    可以看到这是CategoryId已经有值了,查询数据库ProductDetail表:

    这时Product的信息已经插入到数据库中了,而且CategordId也是上面生成的CategoryId。

    但是这样会导致一种问题存在:如果第一次SaveChanges()成功,第二次SaveChanges()之前报错了,但是程序已经不能回滚了,这样就会导致数据不一致了。使用下面的代码进行优化:

     1 using (var dbContext = new CategoryEntities())
     2 {
     3       using (TransactionScope trans = new TransactionScope())
     4       {
     5            Category category = new Category()
     6            {
     7                 CategoryName = "汽车"
     8            };
     9 
    10            ProductDetail product = new ProductDetail()
    11            {
    12                  ProductName = "上海大众",
    13                  Price = 190090,
    14                  CategoryId = category.CategoryId
    15             };
    16 
    17             category.ProductDetails = new List<ProductDetail>() { product};
    18             dbContext.Categories.Add(category);
    19             dbContext.SaveChanges();
    20             trans.Complete();//提交事务
    21        }
    22 }

     经过这样修改以后可以保证数据的一致性了。这是情况只适合有导航属性的。

    示例代码下载地址:https://pan.baidu.com/s/1swge4txIlbBuSgs9GspC4g

  • 相关阅读:
    《数据库系统概论》 -- 3.2. 视图
    Uncaught SecurityError: Failed to execute 'replaceState' on 'History': A history state object with
    在node.js中使用mongose模块
    在centos7上作用mongodb
    Error: listen EADDRINUSE
    telnet: connect to address xxxxxxx: No route to host
    express-generator安装时出错,最后用VPS解决
    centos7中 npm install express 时Error: Cannot find module 'express'错误
    ubuntu1404服务器版中设置root用户
    python爬虫(1)
  • 原文地址:https://www.cnblogs.com/dotnet261010/p/9086090.html
Copyright © 2020-2023  润新知