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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM