或者:http://www.codeproject.com/Articles/796540/Relationship-in-Entity-Framework-Using-Code-First
In this article, you will learn about relationships in Entity Framework using the Code First Approach with Fluent API.
在這篇文章中,你將會學習到使用EF Code-First方式和Fluent API來探討EF中的關系(一對一,一對多,多對多)。
Introduction【介紹】
A relationship, in the context of databases, is a situation that exists between two relational database tables when one table has a foreign key that references the primary key of the other table. Relationships allow relational databases to split and store data in various tables, while linking disparate data items. For example, if we want to store information about a Customer
and his Order
, then we need to create two tables, one for the Customer
and another for the Order
. Both tables, Customer
and Order
, will have the relationship one-to-many so whenever we retrieve all orders of a customer
, then we can easily retrieve them.
對於關系,在數據庫上下文中,是以這樣的情況存在:關系數據庫中,有兩個數據庫關系表,其中一個表,有一個外鍵,並且這個外鍵指向另外一個表的主鍵。關系允許數據庫將數據,分開存儲數據到各種不同的表中,這樣我們就可以方便的查詢數據了。例如,如果我們想要存儲客戶的信息和訂單信息,這個時候,我們可以創建兩個數據表,一個是Customer一個是Order,這兩個表有一對多的關系【一個用戶有可以有多個訂單,一個訂單只屬於一個用戶所有】,所以不管什么時候,我們想要查詢一個用戶的所有訂單信息,我們就可以很容易的查詢到了。
There are several types of database relationships. In this article, I will cover the following:
數據庫中有幾種不同的關系,在這篇文章中,我將會講到下面這些:
- One-to-One Relationships【一對一關系】
- One-to-Many or Many to One Relationships【一對多或者多對一關系】
- Many-to-Many Relationships【多對多關系】
Entity Framework Code First allows us to use our own domain classes to represent the model that Entity Framework relies on to perform querying, change tracking and updating functions. The Code First approach follows conventions over the configuration, but it also gives us two ways to add a configuration on over classes. One is using simple attributes called DataAnnotations
and another is using Code First's Fluent API, that provides you with a way to describe configuration imperatively, in code. This article will focus on tuning up the relationship in the Fluent API.
EF Code First方式,允許我們使用自己的領域類來呈現模型,然后EF會基於這個模型進行查詢,跟蹤改變,做更新操作等。這個Code-First方式遵循約定大於配置,但是它同樣給了我們兩種方式,在領域類上添加配置信息。其中一個就是數據注解,另外一個就是使用Code-First's Fluent API。Fluent API 提供了一種以命令的方式,來描述配置。這篇文章中,我將會專注於使用Fluent API的方式。
To understand the relationship in the Entity Framework Code First approach, we create an entity and define their configuration using the Fluent API. We will create two class library projects, one library project (EF.Core
) has entities and another project (EF.Data
) has these entities configuration with DbContext
. We also create a unit test project (EF.UnitTest
) that will be used to test our code. We will use the following classes that are in a class diagram to explain the preceding three relationships.
為了理解Code-First方式,學習數據庫關系,我們創建了一個實體並且使用Fluent API來定義配置信息,同樣,我們將會創建兩個類庫文件,一個類庫文件【EF.Core】放實體,另一個類庫【EF.Data】放這些實體的配置文件,然后,我們還有一個單元測試項目【EF.UnitTest】,用來測試我們寫的代碼,我們將會使用下面圖表的類,來解釋三種數據庫關系。
As in the preceding class diagram, the BaseEntity
class is a base class that is inherited by each other class. Each derived entity represents each database table. We will use two derived entities combination from the left side to explain each relationship type and that's why we create six entities.
在上面的圖中,BaseEntity是基類,被所有其他類繼承,每一個子類代表一個數據表,我將會使用圖中,每兩個實體來解釋每種數據庫關系,這也是為什么我創建6個子類實體的原因。
So first of all, we create the BaseEntity
class that is inherited by each derived entity under the EF.Core
class library project.
所以,首先,我們在EF.Core類庫項目下,創建將會被其他類繼承的BaseEntity實體類。

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EF.Core { public class BaseEntity { /// <summary>
/// ID /// </summary>
public int ID { get; set; } /// <summary>
/// 添加時間 /// </summary>
public DateTime AddedDate { get; set; } /// <summary>
/// 修改時間 /// </summary>
public DateTime ModifiedDate { get; set; } /// <summary>
/// IP地址 /// </summary>
public string IP { get; set; } } }
We use navigation properties to access a related entity object from one to another. The navigation properties provide a way to navigate an association between two entity types. Every object can have a navigation property for every relationship in which it participates. Navigation properties allow you to navigate and manage relationships in both directions, returning either a reference object (if the multiplicity is either one or zero-or-one) or a collection (if the multiplicity is many).
Now let's see each relationship one-by-one.
在一個實體中,我們使用導航屬性,可以獲取到另外相關聯的實體,導航屬性提供了一個方式,來連接兩個有關聯的實體。對於每種關系的實體來說,每一個實體對象可以有一個導航屬性。導航屬性允許你雙向性的導航,並管理實體之間的關系。要么返回一個引用的實體對象【這種情況下的關系是一對一或者零對一】,要么返回一個集合【這種情況下的關系是一對多,或者多對多】。我們來分別看看每種數據庫關系吧。
Our Roadmap towards Learning MVC with Entity Framework【我們學習MVC和EF的路線】
- Relationship in Entity Framework Using Code First Approach With Fluent API【【使用EF Code-First方式和Fluent API來探討EF中的關系】】
- Code First Migrations with Entity Framework【使用EF 做數據庫遷移】
- CRUD Operations Using Entity Framework 5.0 Code First Approach in MVC【在MVC中使用EF 5.0做增刪查改】
- CRUD Operations Using the Repository Pattern in MVC【在MVC中使用倉儲模式,來做增刪查改】
- CRUD Operations Using the Generic Repository Pattern and Unit of Work in MVC【在MVC中使用泛型倉儲模式和工作單元來做增刪查改】
- CRUD Operations Using the Generic Repository Pattern and Dependency Injection in MVC【在MVC中使用泛型倉儲模式和依賴注入,來做增刪查改】
One-to-One Relationship【一對一關系】
Both tables can have only one record on either side of the relationship. Each primary key value relates to only one record (or no records) in the related table. Keep in mind that this kind of relationship is not very common and most one-to-one relationships are forced by business rules and don't flow naturally from the data. In the absence of such a rule, you can usually combine both tables into one table without breaking any normalization rules.
兩個表之間,只能由一個記錄在另外一個表中。每一個主鍵的值,只能關聯到另外一張表的一條或者零條記錄。請記住,這個一對一的關系不是非常的普遍,並且大多數的一對一的關系,是商業邏輯使然,並且數據也不是自然地。缺乏這樣一條規則,就是在這種關系下,你可以把兩個表合並為一個表,而不打破正常化的規則。
To understand one-to-one relationships, we create two entities, one is User
and another is UserProfile
. One user can have a single profile, a User
table that will have a primary key and that same key will be both primary and foreign keys for the UserProfile
table. Let’s see Figure 1.2 for one-to-one relationship.
為了理解一對一關系,我們創建兩個實體,一個是User另外一個是UserProfile,一個User只有單個Profile,User表將會有一個主鍵,並且這個字段將會是UserProfile表的主鍵和外鍵。我們看下圖:
Now we create both entities User
and UserProfile
in the EF.Core
project under the Data folder. Our User
class code snippet is as in the following:
現在我們將會在Data文件夾下,創建兩個實體,一個是User實體,另外一個是UserProfile實體。我們的User實體的代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EF.Core.Data { public class User:BaseEntity { /// <summary>
/// 用戶名 /// </summary>
public string UserName { get; set; } /// <summary>
/// 電子郵件 /// </summary>
public string Email { get; set; } /// <summary>
/// 密碼 /// </summary>
public string Password { get; set; } /// <summary>
/// 導航屬性--用戶詳情 /// </summary>
public virtual UserProfile UserProfile { get; set; } } }
The UserProfile
class code snippet is as in the following:
UserProfile實體的代碼快如下:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace EF.Core.Data { /// <summary>
/// 用戶詳情實體 /// </summary>
public class UserProfile:BaseEntity { /// <summary>
/// 姓 /// </summary>
public string FirstName { get; set; } /// <summary>
/// 名 /// </summary>
public string LastName { get; set; } /// <summary>
/// 地址 /// </summary>
public string Address { get; set; } /// <summary>
/// 導航屬性--User /// </summary>
public virtual User User { get; set; } } }
As you can see in the preceding two code snippets, each entity is using another entity as a navigation property so that you can access the related object from each other.
就像你看到的一樣,上面的兩個部分的代碼塊中,每個實體都使用彼此的實體,作為導航屬性,因此你可以從任何實體中訪問另外的實體。
Now, we define the configuration for both entities that will be used when the database table will be created by the entity. The configuration defines another class library project EF.Data
under the Mapping folder. Now create two configuration classes for each entity. For the User
entity, we create the UserMap
entity.
現在,我們將會為實體定義配置,這個配置將會在,為實體生產數據庫關系表的時候用到。我們把配置寫在另外的一個類庫中【EF.Data】,在Mapping文件夾下,分別為每個實體創建配置類,對於User實體,我們創建UserMap配置實體。
注意:我們要對EF.Data引入EF。
using EF.Core.Data; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.ModelConfiguration; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EF.Data.Mapping { public class UserMap:EntityTypeConfiguration<User> { public UserMap() { //配置主鍵
this.HasKey(s => s.ID); //給ID配置自動增長
this.Property(s => s.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); //配置字段
this.Property(s => s.UserName).IsRequired().HasColumnType("nvarchar").HasMaxLength(25); this.Property(s => s.Email).IsRequired().HasColumnType("nvarchar").HasMaxLength(25); this.Property(s => s.AddedDate).IsRequired(); this.Property(s => s.ModifiedDate).IsRequired(); this.Property(s => s.IP); //配置表
this.ToTable("User"); } } }
We will use the same way to create the configuration for other entities as for the User
.EntityTypeConfiguration
is an important class that allows configuration to be performed for an entity type in a model. This is done using the modelbuilder in an override of the OnModelCreate
method. The constructor of the UserMap
class uses the Fluent API to map and configure properties in the table. So let's see each method used in the constructor one-by-one.
我們將會使用同樣的方式,為另外一個實體UserProfile創建配置類,EntityTypeConfiguration類,是一個很重要的類,它可以為一個實體類,配置一個模型。這將會通過modelbuilder對象來做到,modelbuilder對象是在重寫方法OnModelCreate中的。UserMap類的構造函數,使用了Fluent API來映射,配置屬性。我們來看看Usermap 構造函數中的每個方法吧。
HasKey()
: TheHaskey()
method configures a primary key on table. 【HasKey方法,配置表的主鍵】Property()
: TheProperty
method configures attributes for each property belonging to an entity or complex type. It is used to obtain a configuration object for a given property. The options on the configuration object are specific to the type being configured.【Property方法配置每個實體的屬性】HasDatabaseGeneratedOption
: It configures how values for the property are generated by the database.【HasDatabaseGeneratedOption配置屬性列是否是自動生成的。】DatabaseGeneratedOption.Identity
:DatabaseGeneratedOption
is the database annotation. It enumerates a database generated 【自動生成列配置】option.DatabaseGeneratedOption.Identity
is used to create an auto-increment column in the table by a unique value.ToTable()
: Configures the table name that this entity type is mapped to.【Totable ,為實體配置表名稱】
Now create the UserProfile
configuration class, the UserProfileMap
class.
現在看看UserProfileMap配置類
using EF.Core.Data; using System; using System.Collections.Generic; using System.Data.Entity.ModelConfiguration; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EF.Data.Mapping { public class UserProfileMap:EntityTypeConfiguration<UserProfile> { public UserProfileMap() { this.HasKey(s=>s.ID); this.Property(s => s.FirstName).IsRequired(); this.Property(s => s.LastName).IsRequired(); this.Property(s => s.Address).HasMaxLength(100).HasColumnType("nvarchar").IsRequired(); this.Property(s => s.AddedDate).IsRequired(); this.Property(s => s.ModifiedDate).IsRequired(); this.Property(s => s.IP); //配置關系[一個用戶只能有一個用戶詳情!!!]
this.HasRequired(s => s.User).WithRequiredDependent(s => s.UserProfile); this.ToTable("UserProfile"); } } }
In the code snippet above, we defined a one-to-one relationship between both User
and UserProfiles
entities. This relationship is defined by the Fluent API using the HasRequired()
andWithRequiredDependent()
methods so these methods are as in the following:
在上面的代碼塊中,我們在User和UserProfile實體之間定義了一對一的關系,這個關系,是通過Fluent API中的HasRequired方法,和WithRequiredDependent方法來實現的。
HasRequired()
: Configures a required relationship from this entity type. Instances of the entity type will not be able to be saved to the database unless this relationship is specified. The foreign key in the database will be non-nullable. In other words,UserProfile
can’t be saved independently withoutUser
entity.【HasRequired方法,配置了實體的必須關系,實體的字段,只有在明確指定值的情況下,數據才會被允許插入數據庫中。數據庫中的外鍵,將會是不允許為空的。換句話說UserProfile實體在沒有UserEntity實體的時候,是不能獨立保存到數據庫中的。】WithRequiredDependent()
: (from the MSDN) Configures the relationship to be required: required without a navigation property on the other side of the relationship. The entity type being configured will be the dependent and contain a foreign key to the principal. The entity type that the relationship targets will be the principal in the relationship.【WithRequiredDependent方法,從MSDN中所了解的信息是,配置關系是必須的,需要沒有導航屬性在另外一邊。實體的類型將會是獨立的,並且包含一個主要的外鍵。】
Now define the connection string in App.config file under EF.Data
project so that we can create database with the appropriate name. The connectionstring
is:
現在,在EF.Data類庫中定義連接字符串配置文件信息,以便我們的程序可以創建數據庫,連接字符串信息是:
<connectionStrings>
<add name="DbConnectionString" connectionString="Server=.;database=EFRelationshipStudyDB;uid=sa;pwd=Password_1" providerName="System.Data.SqlClient"/>
</connectionStrings>
Now we create a context class EFDbContext
(EFDbContext.cs) that inherits the DbContext
class. In this class, we override the OnModelCreating()
method. This method is called when the model for a context class (EFDbContext
) has been initialized, but before the model has been locked down and used to initialize the context such that the model can be further configured before it is locked down. The following is the code snippet for the context class.
現在,我們創建一個數據庫上下文類EFDbContext,這個類繼承DbContext類,在這個數據庫上下文類中,我們重寫OnModelCreating方法,這個OnModelCreating方法,在數據庫上下文(EFDbContext)已經初始化的完成的時候,被調用。但是在model被鎖定之前,初始化數據庫上下文的時候,所以model能夠在被鎖定之前,進一步被配置。下面是數據庫上下文的代碼:
using System; using System.Collections.Generic; using System.Data.Entity; using System.Data.Entity.ModelConfiguration; using System.Linq; using System.Reflection; using System.Text; using System.Threading.Tasks; namespace EF.Data { public class EFDbContext:DbContext { public EFDbContext() : base("name=DbConnectionString") { } protected override void OnModelCreating(DbModelBuilder modelBuilder) { { var typesToRegister = Assembly.GetExecutingAssembly().GetTypes() .Where(type => !String.IsNullOrEmpty(type.Namespace)) .Where(type => type.BaseType != null && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>)); foreach (var type in typesToRegister) { dynamic configurationInstance = Activator.CreateInstance(type); modelBuilder.Configurations.Add(configurationInstance); } base.OnModelCreating(modelBuilder); } } } }
As you know, the EF Code First approach follows convention over configuration, so in the constructor, we just pass the connection string name same as an App.Config file and it connects to that server. In theOnModelCreating()
method, we used a reflection to map an entity to its configuration class in this specific project.
總所周知,EF Code First方法,遵循約定大於配置原則,所以在構造函數中,我們僅僅只需要傳遞連接字符串的名字,然后就可以連接到數據庫服務器了。在OnModelCreating方法中,我們使用了反射,來為每個實體生成配置類。
We create a Unit Test Project EF.UnitTest
to test the code above. We create a test class UserTest
that has a test method UserUserProfileTest()
. This method creates a database and populates User
andUserProfile
tables as per their relationship. The following is the code snippet for the UserTest
class.
現在,我們使用單元測試,來測試一下上面寫的代碼。創建一個UserTest單元測試類,寫一個UserUserProfileTest測試方法,這個方法創建數據庫,生成User和UserProfile數據庫表,下面是代碼:
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using System.Data.Entity; using EF.Data; using EF.Core.Data; namespace EF.UnitTest { [TestClass] public class UserTest { [TestMethod] public void UserUserProfileTest() { //配置數據庫初始化策略
Database.SetInitializer<EFDbContext>(new CreateDatabaseIfNotExists<EFDbContext>()); using(var db=new EFDbContext()) { //創建數據庫
db.Database.Create(); User userModel = new User() { UserName = "Daniel", Password = "123456", AddedDate = DateTime.Now, ModifiedDate = DateTime.Now, IP = "1.1.1.1", Email = "Daniel@163.com", //一個用戶,只有一個用戶詳情
UserProfile = new UserProfile() { FirstName="曹", LastName="操", AddedDate=DateTime.Now, ModifiedDate=DateTime.Now, IP="1.2.3.45", Address="寶安區 深圳 中國", } }; //設置用戶示例狀態為Added
db.Entry(userModel).State = System.Data.Entity.EntityState.Added; //保存到數據庫中
db.SaveChanges(); } } } }
Now, run the Test
method and you get your table in the database with data. Run a select
query in the database and get results like:
現在,運行測試方法,在數據庫中就會生成這樣的數據庫,查詢一下:
【不要忘記了,在單元測試的類庫項目中,也要寫連接字符串的信息】
然后看看數據庫中的數據吧:
從生成的數據庫中,我們可以看到,UserProfile表中的ID,即是主鍵也是外鍵,就是說,我們不能隨便在UserProfile表中插入數據,也就是說,插入的數據,要在User表中存在記錄。
還有個注意點:上面配置關系的時候,我們使用的是,
//配置關系[一個用戶只能有一個用戶詳情!!!]
this.HasRequired(s => s.User).WithRequiredDependent(s => s.UserProfile);
WithRequiredDependent現在,我們換成,WithRequiredPrincipal,結果生成的數據庫是:
可以看出來,這樣就弄反了。。。。ID列應該是在UserProfile表中既是主鍵也是外鍵。
現在看看一對多的關系吧:
One-to-Many Relationship【一對多關系】
The primary key table contains only one record that relates to none, one, or many records in the related table. This is the most commonly used type of relationship.
主鍵表的一個記錄,關聯到關聯表中,存在,沒有,或者有一個,或者多個記錄。這是最重要的也是最常見的關系。
To understand this relationship, consider an e-commerce system where a single user can make many orders so we define two entities, one for the customer
and another for the order
. Let’s take a look at the following figure:
為了更好的理解一對多的關系,可以聯想到電子商務系統中,單個用戶可以下很多訂單,所以我們定義了兩個實體,一個是客戶實體,另外一個是訂單實體。我們看看下面的圖片:
下面是Custiomer實體代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EF.Core.Data { public class Customer:BaseEntity { /// <summary>
/// 客戶名稱 /// </summary>
public string Name { get; set; } /// <summary>
/// 客戶電子郵件 /// </summary>
public string Emial { get; set; } /// <summary>
/// 導航屬性--Order /// </summary>
public virtual ICollection<Order> Orders { get; set; } } }
Order實體:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace EF.Core.Data { public class Order:BaseEntity { /// <summary>
/// 數量 /// </summary>
public byte Quantity { get; set; } /// <summary>
/// 價格 /// </summary>
public decimal Price { get; set; } /// <summary>
/// 客戶ID /// </summary>
public int CustomerId { get; set; } /// <summary>
/// 導航屬性--Customer /// </summary>
public virtual Customer Customer { get; set; } } }
You have noticed the navigation properties in the code above. The Customer
entity has a collection of Order
entity types and the Order
entity has a Customer
entity type property, that means a customer
can make many order
s.
你已經在上面的代碼中注意到了導航屬性,Customer實體有一個集合類型的Order屬性,Order實體有一個Customer實體的導航屬性,也就是說,一個客戶可以有很多訂單。
Now create a class, the CustomerMap
class in the EF.Data
project to implement the Fluent API configuration for the Customer
class.
現在在EF.Data項目中,定義一個CustomerMap類:
using EF.Core.Data; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.ModelConfiguration; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EF.Data.Mapping { public class CustomerMap:EntityTypeConfiguration<Customer> { public CustomerMap() { this.HasKey(s => s.ID); //properties
Property(t => t.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); Property(t => t.Name); Property(t => t.Email).IsRequired(); Property(t => t.AddedDate).IsRequired(); Property(t => t.ModifiedDate).IsRequired(); Property(t => t.IP); //table
ToTable("Customers"); } } }
OrderMap類:
using EF.Core.Data; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.ModelConfiguration; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EF.Data.Mapping { public class OrderMap:EntityTypeConfiguration<Order> { public OrderMap() { this.HasKey(s=>s.ID); //fields
Property(t => t.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); Property(t => t.Quanatity).IsRequired().HasColumnType("tinyint"); Property(t => t.Price).IsRequired(); Property(t => t.CustomerId).IsRequired(); Property(t => t.AddedDate).IsRequired(); Property(t => t.ModifiedDate).IsRequired(); Property(t => t.IP); //配置關系【一個用戶有多個訂單,外鍵是CusyomerId】
this.HasRequired(s => s.Customer).WithMany(s => s.Orders).HasForeignKey(s => s.CustomerId).WillCascadeOnDelete(true); //table
ToTable("Orders"); } } }
The code above shows that a Customer
is required for each order and the Customer
can make multiple orders and relationships between both made by foreign key CustomerId
. Here, we use four methods to define the relationship between both entities. The WithMany
method allows us to indicate which property in Customer
contains the Many relationship. We add to that the HasForeignKey
method to indicate which property ofOrder
is the foreign key pointing back to customer
. The WillCascadeOnDelete()
method configures whether or not cascade delete is on for the relationship.
上面的代碼表示:用戶在每個Order中是必須的,並且用戶可以下多個訂單,兩個表之間通過外鍵CustomerId聯系,我們使用了四個方法來定義實體之間的關系,Withmany方法允許多個。HasForeignKey方法表示哪個屬性是Order表的外鍵,WillCascadeOnDelete方法用來配置是否級聯刪除。
Now, we create another unit test class in the EF.UnitTest
Project to test the code above. Let’s see the test method that inserts data for the customer that has two orders.
現在,我們在EF.UniTest類庫項目中,創建另外的一個單元測試類。
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using EF.Core.Data; using System.Collections.Generic; using EF.Data; using System.Data.Entity; namespace EF.UnitTest { [TestClass] public class CustomerTest { [TestMethod] public void CustomerOrderTest() { Database.SetInitializer<EFDbContext>(new CreateDatabaseIfNotExists<EFDbContext>()); using (var context = new EFDbContext()) { context.Database.Create(); Customer customer = new Customer { Name = "Raviendra", Email = "raviendra@test.com", AddedDate = DateTime.Now, ModifiedDate = DateTime.Now, IP = "1.1.1.1", Orders = new List<Order>{ new Order { Quanatity =12, Price =15, AddedDate = DateTime.Now, ModifiedDate = DateTime.Now, IP = "1.1.1.1", }, new Order { Quanatity =10, Price =25, AddedDate = DateTime.Now, ModifiedDate = DateTime.Now, IP = "1.1.1.1", } } }; context.Entry(customer).State = System.Data.Entity.EntityState.Added; context.SaveChanges(); } } } }
運行測試。
來看看數據庫中的數據:
最后看看,多對多的關系吧:
Many-to-Many Relationship【多對多關系】
Each record in both tables can relate to any number of records (or no records) in the other table. Many-to-many relationships require a third table, known as an associate or linking table, because relational systems can't directly accommodate the relationship.
每條記錄在兩個表中,都可以關聯到另外一個表中的很多記錄【或者0條記錄】。多對多關系,需要第三方的表,也就是關聯表或者鏈接表,因為關系型數據庫不能直接適應這種關系。
To understand this relationship, consider an online course system where a single student
can join manycourses
and a course
can have many students
so we define two entities, one for the student
and another for the course
. Let’s see the following figure for the Many-to-Many relationship.
為了更好的理解多對多關系,我們想到,有一個選課系統,一個學生可以選秀很多課程,一個課程能夠被很多學生選修,所以我們定義兩個實體,一個是Syudent實體,另外一個是Course實體。我們來通過圖表看看,多對多關系吧:
Student實體:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EF.Core.Data { public class Student:BaseEntity { public string Name { get; set; } public byte Age { get; set; } public bool IsCurrent { get; set; } public virtual ICollection<Course> Courses { get; set; } } }
Course實體:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace EF.Core.Data { public class Course:BaseEntity { public string Name { get; set; } public Int64 MaximumStrength { get; set; } public virtual ICollection<Student> Students { get; set; } } }
StudentMap類:
using EF.Core.Data; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.ModelConfiguration; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EF.Data.Mapping { public class StudentMap:EntityTypeConfiguration<Student> { public StudentMap() { //key
HasKey(t => t.ID); //property
Property(t => t.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); Property(t => t.Name); Property(t => t.Age); Property(t => t.IsCurrent); Property(t => t.AddedDate).IsRequired(); Property(t => t.ModifiedDate).IsRequired(); Property(t => t.IP); //table
ToTable("Students"); //配置關系[多個課程,可以被多個學生選修] //多對多關系實現要領:hasmany,hasmany,然后映射生成第三個表,最后映射leftkey,rightkey
this.HasMany(s => s.Courses). WithMany(s => s.Students) .Map(s => s.ToTable("StudentCourse"). MapLeftKey("StudentId"). MapRightKey("CourseId")); } } }
The code snippet above shows that one student
can join many courses
and each course
can have manystudents
. As you know, to implement Many-to-Many relationships, we need a third table namedStudentCourse
. The MapLeftKey()
and MapRightKey()
methods define the key's name in the third table otherwise the key name is automatically created with classname_Id
. The Left key or first key will be that in which we are defining the relationship.
上面的代碼中,表示,一個學生可以選修多個課程,並且每個課程可以有很多學生,你知道,實現多對多的關系,我們需要第三個表,所以我們映射了第三個表,mapLeftkey和maprightkey定義了第三個表中的鍵,如果我們不指定的話,就會按照約定生成類名_Id的鍵。
Now create a class, the CourseMap
class, in the EF.Data
project to implement the Fluent API configuration for the Course
class.
現在看看CourseMap類:
using EF.Core.Data; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Data.Entity.ModelConfiguration; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EF.Data.Mapping { public class CourseMap:EntityTypeConfiguration<Course> { public CourseMap() { this.HasKey(t => t.ID);//少了一行代碼 //property
Property(t => t.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); Property(t => t.Name); Property(t => t.MaximumStrength); Property(t => t.AddedDate).IsRequired(); Property(t => t.ModifiedDate).IsRequired(); Property(t => t.IP); //table
ToTable("Courses"); } } }
在來創建一個單元測試類:
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; using EF.Core.Data; using System.Collections.Generic; using EF.Data; using System.Data.Entity; namespace EF.UnitTest { [TestClass] public class StudentTest { [TestMethod] public void StudentCourseTest() { Database.SetInitializer<EFDbContext>(new CreateDatabaseIfNotExists<EFDbContext>()); using (var context = new EFDbContext()) { context.Database.Create(); Student student = new Student { Name = "Sandeep", Age = 25, IsCurrent = true, AddedDate = DateTime.Now, ModifiedDate = DateTime.Now, IP = "1.1.1.1", Courses = new List<Course>{ new Course { Name = "Asp.Net", MaximumStrength = 12, AddedDate = DateTime.Now, ModifiedDate = DateTime.Now, IP = "1.1.1.1" }, new Course { Name = "SignalR", MaximumStrength = 12, AddedDate = DateTime.Now, ModifiedDate = DateTime.Now, IP = "1.1.1.1" } } }; Course course = new Course { Name = "Web API", MaximumStrength = 12, AddedDate = DateTime.Now, ModifiedDate = DateTime.Now, IP = "1.1.1.1", Students = new List<Student>{ new Student { Name = "Raviendra", Age = 25, IsCurrent = true, AddedDate = DateTime.Now, ModifiedDate = DateTime.Now, IP = "1.1.1.1", }, new Student { Name = "Pradeep", Age = 25, IsCurrent = true, AddedDate = DateTime.Now, ModifiedDate = DateTime.Now, IP = "1.1.1.1", } } }; context.Entry(student).State = System.Data.Entity.EntityState.Added; context.Entry(course).State = System.Data.Entity.EntityState.Added; context.SaveChanges(); } } } }
運行測試:
看看數據庫中的數據吧:
Conclusion【總結】
This article introduced relationships in the Entity Framework Code First approach using the Fluent API. I didn’t use database migration here; that is why you need to delete your database before running any test method of the unit.
這篇文章,介紹了EF CodeFirst 方式,這里我沒有使用數據庫遷移技術,這就就是為啥你每次運行單元測試之前,都要先刪掉數據庫的原因。
總結:翻譯真是一件苦差事,后面的文章,我打算使用自己的語言來組織,寫出來。一字一句的翻譯,真的很浪費時間,雖然我想進一步加強英語能力,但,確實翻譯耗費時間啊。但為了分享知識,我還是翻譯出來了。希望大家喜歡。~~~