1.【使用EF Code-First方式和Fluent API来探讨EF中的关系】
或者: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 方式,这里我没有使用数据库迁移技术,这就就是为啥你每次运行单元测试之前,都要先删掉数据库的原因。
总结:翻译真是一件苦差事,后面的文章,我打算使用自己的语言来组织,写出来。一字一句的翻译,真的很浪费时间,虽然我想进一步加强英语能力,但,确实翻译耗费时间啊。但为了分享知识,我还是翻译出来了。希望大家喜欢。~~~