Entity Framework导航属性介绍

2022-04-17 11:21:51

一、主键和外键

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

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

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

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

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

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

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

外键

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

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

二、导航属性

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

CREATE table Category(  CategoryId int primary key not null identity,  CategoryName varchar(64))

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

CREATE TABLE [dbo].[ProductDetail](   mqvdjsEDit [ProductId] [int] IDENTITY(1,1) NOT NULL,    [ProductName] [varchar](32) NULL,    [Price] [decimal](9, 2) NULL,    [CategoryId] [int] NULL,PRIMARY KEY CLUSTERED(    [ProductId] ASC)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]) ON [PRIMARY]GOSET ANSI_PADDING OFFGOALTER TABLE [dbo].[ProductDetail]  WITH CHECK ADD  CONSTRAINT [FK_Category] FOREIGN KEY([CategoryId])REFERENCES [dbo].[Category] ([CategoryId])GOALTER TABLE [dbo].[ProductDetail] CHECK CONSTRAINT [FK_Category]GO

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

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

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

Category表定义:

//------------------------------------------------------------------------------// <auto-generated>//     此代码已从模板生成。////     手动更改此文件可能导致应用程序出现意外的行为。//     如果重新生成代码,将覆盖对此文件的手动更改。// </auto-generated>//------------------------------------------------------------------------------namespace EFNavigateDemo{    using System;    using System.Collections.Generic;    public partial class Category    {        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]        public Category()        {            this.ProductDetails = new HashSet<ProductDetail>();        }        public int CategoryId { get; set; }        public string CategoryName { get; set; }        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]        public virtual ICollection<ProductDetail> ProductDetails { get; set; }    }}

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

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

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

ProductDetail实体类定义如下:

//------------------------------------------------------------------------------// <auto-generated>//     此代码已从模板生成。////     手动更改此文件可能导致应用程序出现意外的行为。//     如果重新生成代码,将覆盖对此文件的手动更改。// </auto-generated>//------------------------------------------------------------------------------namespace EFNavigateDemo{    using System;    using System.Collections.Generic;    public partial class ProductDetail    {        public int ProductId { get; set; }        public string ProductName { get; set; }        public Nullable<decimal> Price { get; set; }        public Nullable<int> CategoryId { get; set; }        public virtual Category Category { get; set; }    }}

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

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

1、方式一

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

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

执行到断点1时:

EntityFramework导航属性介绍

这时查看数据库监控:

EntityFramework导航属性介绍

继续执行到断点2:

EntityFramework导航属性介绍

这时在查看数据库监控:

EntityFramework导航属性介绍

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

2、方式二

using (var dbContext = new CategoryEntities()){      dbContext.Configuration.LazyLoadingEnabled = false; // 不延迟加载,不会再次查询了      var categoryList = dbContext.Set<Category>().Where(p => p.CategoryId == 3);       // 只会在数据库里面查询Category表,不会查询ProductDetail表           ProductDetail product = new ProductDetail()           {                 ProductName = "美利达",                 Price = 2312,                 CategoryId = category.CategoryId            };            dbContext.ProductDetails.Add(product);            dbContext.SaveChanges();            trans.Complete();//提交事务      }}

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

EntityFramework导航属性介绍

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

EntityFramework导航属性介绍

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

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

using (var dbContext = new CategoryEntities()){      using (TransactionScope trans = new TransactionScope())      {           Category category = new Category()           {                CategoryName = "汽车"           };           ProductDetail product = new ProductDetail()           {                 ProductName = "上海大众",                 Price = 190090,                 CategoryId = category.CategoryId            };            category.ProductDetails = new List<ProductDetail>() { product};            dbContext.Categories.Add(category);            dbContext.SaveChanges();            trans.Complete();//提交事务       }}

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

示例代码下载地址:点此下载

到此这篇关于Entity Framework导航属性的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。