一、主鍵和外鍵
關系型資料庫中的一條記錄中有若幹個屬性,若其中某一個屬性組是能唯一辨別一條記錄,該屬性組就可以稱為主鍵。例如:
學生版(學号、姓名、性别、班級)
其中每個學生的學号是唯一的,學号就是一個主鍵。
課程表(課程編号,課程名,學分)
其中課程編号是唯一的,課程編号就是一個主鍵。
成績表(學号、課程号、成績)
成績表中單獨的一個屬性無法唯一辨別一條記錄,學号和課程号的組合才能唯一辨別一條記錄,是以學号和課程号的屬性組是一個主鍵。
外鍵
成績表中的學号不是成績表的主鍵,但它和學生表中的學号相對應,并且學生表中的學号是學生表的主鍵,則稱成績表中的學号是學生表的外鍵。同理:成績表中的課程号是課程表的外鍵。
EntityFramework中的導航屬性即外鍵。下面通過例子講解如何使用EF的導航屬性。
二、導航屬性
1、建立産品分類表,語句如下:
CREATE table Category
(
CategoryId int primary key not null identity,
CategoryName varchar(64)
)
建立産品明細表,其中CategoryId是外鍵
CREATE TABLE [dbo].[ProductDetail](
[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]
GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[ProductDetail] WITH CHECK ADD CONSTRAINT [FK_Category] FOREIGN KEY([CategoryId])
REFERENCES [dbo].[Category] ([CategoryId])
GO
ALTER TABLE [dbo].[ProductDetail] CHECK CONSTRAINT [FK_Category]
GO
分别往Category表和ProductDetail表中插入一些測試資料:
--Category表插入資料
INSERT INTO Category (CategoryName)
select '電子産品' union
SELECT '家用電器' UNION
SELECT '圖書'
--ProductDetail表插入資料
INSERT INTO ProductDetail (ProductName,Price,CategoryId)
SELECT '蘋果6s手機',5633,1 UNION
SELECT 'Dell電腦',6998,1 UNION
SELECT '佳能相機',5633,1 UNION
SELECT '海爾洗衣機',1234,2 UNION
SELECT '格力空調',2344,2 UNION
SELECT '美的冰箱',3218,2 UNION
SELECT '白鹿原',342,3 UNION
SELECT 'C#進階程式設計(第十版)',145,3 UNION
SELECT '平凡的世界',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時:
這時檢視資料庫監控:
繼續執行到斷點2:
這時在檢視資料庫監控:
會發現周遊ProductDetails屬性時也會查詢ProductDetail表。
2、方式二
using (var dbContext = new CategoryEntities())
{
dbContext.Configuration.LazyLoadingEnabled = false; // 不延遲加載,不會再次查詢了
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);
// 這時不會去資料庫查詢了,是以使用者全是空的
foreach (var product in category.ProductDetails)
{
Console.WriteLine("ProductName:" + product.ProductName);
}
}
}
這時還是采用和上面一樣的方法加入斷點,隻需要檢視第二次循環時的資料庫監控情況即可:
從上面的截圖中看出,如果LazyLoadingEnabled設定為false,将不會再查詢ProductDetail表的資料了。
3、方式三
// 顯示加載
using (var dbContext = new CategoryEntities())
{
// 不延遲加載,指定Include,一次性加載主表和從表的所有資料
var categoryList = dbContext.Set<Category>().Include("ProductDetails").Where(p => p.CategoryId == 3);
foreach (var category in categoryList)
{
Console.WriteLine("CategoryId:" + category.CategoryId + ",CategoryName:" + category.CategoryName);
// 不會再查詢
foreach (var product in category.ProductDetails)
{
Console.WriteLine("ProductName:" + product.ProductName);
}
}
}
使用Include()方法會一次性加載所有的資料:
4、方式四
//LoadProperty 手動加載
using (var dbContext = new CategoryEntities())
{
dbContext.Configuration.LazyLoadingEnabled = false; // 不延遲加載,不會再次查詢了
var categoryList = dbContext.Set<Category>().Where(p => p.CategoryId == 3);
foreach (var category in categoryList)
{
Console.WriteLine("CategoryId:" + category.CategoryId + ",CategoryName:" + category.CategoryName);
dbContext.Entry<Category>(category).Collection(p => p.ProductDetails).Load();// 集合顯示加載
foreach (var product in category.ProductDetails)
{
Console.WriteLine("ProductName:" + product.ProductName);
}
}
}
添加斷點:
檢視資料庫監控:
5、插入資料
對于Category和ProductDetail表如何同時插入資料?先看下面的一段代碼:
using (var dbContext = new CategoryEntities())
{
using (TransactionScope trans = new TransactionScope())
{
Category category = new Category()
{
CategoryName = "自行車"
};
dbContext.Categories.Add(category);
dbContext.SaveChanges();//category.CategoryId指派了
ProductDetail product = new ProductDetail()
{
ProductName = "美利達",
Price = 2312,
CategoryId = category.CategoryId
};
dbContext.ProductDetails.Add(product);
dbContext.SaveChanges();
trans.Complete();//送出事務
}
}
在第一次SaveChanges()後面的一行代碼加斷點,檢視Category資訊:
可以看到這是CategoryId已經有值了,查詢資料庫ProductDetail表:
這時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();//送出事務
}
}
經過這樣修改以後可以保證資料的一緻性了。這是情況隻适合有導航屬性的。