EntityFramework 实体关系数据模型(DO.NET Entity Framework)
ADO.NET Entity Framework 是微软以 ADO.NET 为基础所发展出来的对象关系对应 (O/R Mapping) 解决方案。该框架曾经为.NET Framework的一部分,但version 6之后从.NET Framework分离出来。
在项目中使用Entity Framework
要得到最高版本的Entity Framework 程序包,可以通过扩展与更新或者右击项目-引用-管理NuGet程序包。
创建Entity实体关系模型
右击项目 - 添加 - 数据 - ADO.NET实体数据模型
来自数据库的EF设计器:通过已经事先创建的数据库和表,EF框架自动根据数据库生成表实体。
空EF设计器模型:通过EF设计器的可视化界面设计实体关系图,EF框架自动根据关系图生成数据库、表和对应的实体类。
Code First模型:通过全手工创建实体,运行代码后自定生成数据库和表。
添加完成后在项目中会包含一个后缀为edmx文件,此文件描述模型的架构。这三个文件分别是模型的XML表示,数据库构架的XML表示、映射表与实体的关系的XML表示。
xxx.context.tt(数据库操作上下文)
xxx.tt(实体模型)
Code First模式
DbContext类(System.Data.Entity.DbContext)
这是一个操作实体的上下文对象,它比ObjectContex更轻量级,推荐使用DbContext。DbContext内部维护了数据库连接、管理实体数据、生成数据库操作语句、将数据持久化到数据库。它包含所有的实体对象的集合(可查询数据集),通过它的泛型集合属性DbSet<T>存储这些数据集。你所创建的类是如何成为Entity实体的呢?很简单,完成以下三步:
1.创建一个类库,命名为DataAccess,用以表示数据访问层,创建一个BreakAwayContext的类,使其从DbContext派生。键入以下代码:
using System.Data.Entity;
using Model;
namespace DataAcess
{
public class BreakAwayContext : DbContext
{
public BreakAwayContext ( ) : base ( "name=DBConnection" ) { } //name=配置文件中的数据库连接字符串的名称
public DbSet<Destination> Destinations { get; set; } //你创建的实体类Destination,实体代表了一张表,此处通过DbSet泛型集合表示该表的所有记录
public DbSet<Lodging> Lodgings { get; set; } //你创建的实体类Lodging,实体代表了一张表,此处通过DbSet泛型集合表示该表的所有记录
//使用此静态构造函数指定数据库的初始化方式,无论如何请先关闭打开的数据库、关闭vs中的服务器资源管理器中的数据库连接,否则以下任务不会完成
static BreakAwayContext ( )
{
Database.SetInitializer ( new CreateDatabaseIfNotExists<BreakAwayContext> ( ) ); //默认,参数可为null,当数据库不存在时,自动创建数据库
Database.SetInitializer ( new DropCreateDatabaseAlways<BreakAwayContext> ( ) ); //无论怎样,删除同名数据库,再重新创建
Database.SetInitializer ( new DropCreateDatabaseIfModelChanges<BreakAwayContext> ( ) ); //如果实体模型发生改变,则先删除同名数据库,再重新创建
}
}
}
DataAccess
2.创建一个类库,命名为Model,用以表示实体层,创建两个实体类:Destination和Lodging。前者表示旅行的目的地,后者表示住宿。键入以下代码:
namespace Model
{
/// <summary>
/// 目的地
/// </summary>
public class Destination
{
public int DestinationId { get; set; }
public string Name { get; set; }
public string Country { get; set; }
public string Description { get; set; }
public byte [ ] Photo { get; set; }
//Lodgings集合是Lodging类,该表会被识别为外键表,因为这表示了一个Destination(目的地)对应多个Lodgings(住宿)的关系。
public List<Lodging> Lodgings { get; set; }
}
}
Destination
namespace Model
{
/// <summary>
/// 住宿
/// </summary>
public class Lodging
{
public int LodgingId { get; set; }
public string Name { get; set; }
public string Owner { get; set; }
public bool IsResort { get; set; }
//作为外键引用了Destination表的主键,此处可以写成public Destination Destination,效果是一样的,都会被识别为对Destination表的主键的引用
public int DestinationId { get; set; }
}
}
Lodging
3.创建一个控制台应用程序,命名为BreakAwayConsole,用以表示显示层。添加对Entity framework的引用,这样会自动在App.config中生成默认的数据库配置信息节点:<entityFramework>,将其全部删除,再添加一个ConnectionString,configSections好像必须是configuration的第一个配置节,所以ConnectionString配置节只能放在configSections后面。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<connectionStrings>
<add name="DBConnection"
connectionString="Data Source=(local); Database=stuEntity; User ID=sa; Password=123456; MultipleActiveResultSets=True"
providerName="System.Data.SqlClient" />
</connectionStrings>
</configuration>
app.config
4.添加DataAccess对Model的引用,添加BreakAwayConsole对DataAccess和Model的引用后,在控制台中创建一条记录并插入数据库,此时DbContext会自动为你创建数据库和表。
using DataAccess;
using Model;
namespace BreakAwayConsole
{
class Program
{
static void Main ( string [ ] args )
{
var destination = new Destination ( )
{
Country = "美国" ,
Description = "风城" ,
Name = "芝加哥"
};
using ( var context = new BreakAwayContext ( ) )
{
context.Destinations.Add ( destination );
context.SaveChanges ( );
}
}
}
}
控制台.program
ctrl+F5运行程序,等待片刻数据库就创建完毕了。
字段属性映射的默认约定
属性与主键约定:Id或类名Id将默认自动映射为主键。
属性与字符约定:字符类型的属性默认自动映射为nvarchar(max) null。
属性与bool约定:布尔值类型的属性默认自动映射为bit
属性与byte数组约定:byte数组类型的属性默认自动映射为varbinary ( max)
集合属性与主外键约定:泛型集合中的类会被视为外键表,外键表总是有一个外键引用了当前具有泛型集合的类,外键可以是一个int也可以直接是它所引用的类类型。如下:
public int DestinationId { get; set; }
public Destination Destination { get; set; }
配置实体字段属性
有两种方式可为字段增加描述性配置,Data Annotations和Fluent API,前者使用简单,后者提供更强大的清洁的配置。(如果不喜欢到处贴标签)
1.Data Annotations配置(System.ComponentModel.DataAnnotations)
[Table ( "Animal" )]
//将类映射为Animal表
[Key]
//主键,且种子增量每次+1,即自动增长,增量为1
[Key, DatabaseGenerated ( System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity )]
//如果主键类型是Guid,则必须配置此特性,否则不会自动生成唯一标识
[Required]
//不允许空值
[MaxLength ( 255 )]
//最大长度
[MinLength ( 10 )]
//最小长度
[Timestamp]
//并发时间戳,为开放式并发环境配置时间戳。一个实体只能有一个byte [ ] 类型的属性可设置此特性,Sql Server称其为TimeStamp(时间戳),其他数据库称其为RowVersion(行版本)
[ConcurrencyCheck]
//并发非时间戳,当并发冲突发生时,这将为并发提供检查确保不会发生异常。对应的字段类似:public int SocialSecurityNumber { get; set; }
配置实体字段属性
2.Fluent API配置 (System.Data.Entity.ModelConfiguration和System.Data.Entity)
System.Data.Entity.ModelConfiguration.EntityTypeConfiguration<T>泛型类用以配置实体的属性,将其映射为数据库表字段的特性。为此,你应该创建一个表示为实体类增加配置的类,接着通过在构造函数中初始化这些配置信息。然后重写DbContext的OnModelCreating()方法,该方法会在创建表之前将配置信息应用到数据库,所以将每一个实体类的配置信息注册在modelBuilder.Configurations集合中即可。
ToTable ( tableNameString)
//设置映射为数据库的表名
HasKey ( lambda)
//主键,且种子增量每次+1,即自动增长,增量为1
HasPrecision ( n1, n2)
//为decimal类型的属性保留有效位数和小数位数,n1为有效位数,n2为小数位数
Property ( lambda).HasDatabaseGeneratedOption ( System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity )
//如果主键类型是Guid,则必须配置此项,否则不会自动生成唯一标识
Property ( lambda).IsRequired ( )
//允许空
Property ( lambda).HasMaxLength ( )
//字符长度
Property ( lambda).HasColumnType ( "image" )
//列类型为二进制图像数据
Property ( lambda).IsRowVersion ( )
//并发时间戳,为开放式并发环境配置时间戳。一个实体只能有一个byte [ ] 类型的属性可设置此特性,Sql Server称其为TimeStamp(时间戳),其他数据库称其为RowVersion(行版本)
Property ( lambda).IsConcurrencyToken ( )
//并发非时间戳,当并发冲突发生时,这将为并发提供检查,确保不会发生异常。对应的属性字段的类型为int
配置实体字段属性
using System.Data.Entity.ModelConfiguration;
using System.Data.Entity;
using Model;
namespace DataAcess
{
//Destination表的列配置
public class DestinationConfiguration : EntityTypeConfiguration<Destination>
{
public DestinationConfiguration ( )
{
ToTable ( "Destination" );
HasKey ( d => d.DestinationId );
Property ( d => d.Name ).HasMaxLength ( 255 ).IsRequired();
Property ( d => d.Description ).HasMaxLength ( 255 ).IsRequired ( );
Property ( d => d.Photo ).HasColumnType ( "image" );
}
}
//Lodging表的列配置
public class LodgingConfiguration : EntityTypeConfiguration<Lodging>
{
public LodgingConfiguration ( )
{
//……
}
}
public class BreakAwayContext : DbContext
{
//使用此静态构造函数指定数据库的初始化方式,无论如何请先关闭数据库,否则以下任务不会完成
static BreakAwayContext ( )
{
Database.SetInitializer ( new CreateDatabaseIfNotExists<BreakAwayContext> ( ) ); //默认,参数可为null,当数据库不存在时,自动创建数据库
Database.SetInitializer ( new DropCreateDatabaseAlways<BreakAwayContext> ( ) ); //无论怎样,删除同名数据库,再重新创建
Database.SetInitializer ( new DropCreateDatabaseIfModelChanges<BreakAwayContext> ( ) ); //如果实体模型发生改变,则先删除同名数据库,再重新创建
}
public BreakAwayContext ( ) : base ( "name=DBConnection" ) { } //name=配置文件中的数据库连接字符串的名称
public DbSet<Destination> Destinations { get; set; } //你创建的实体类Destination,实体代表了一张表,此处通过DbSet泛型集合表示该表的所有记录
public DbSet<Lodging> Lodgings { get; set; } //你创建的实体类Lodging,实体代表了一张表,此处通过DbSet泛型集合表示该表的所有记录
//实体被映射为表之前OnModelCreating会被运行,这将进行列配置检查并应用列配置
protected override void OnModelCreating ( DbModelBuilder modelBuilder )
{
modelBuilder.Configurations.Add ( new DestinationConfiguration ( ) ); //注册Destination表的列配置
modelBuilder.Configurations.Add ( new LodgingConfiguration ( ) ); //注册Lodging表的列配置
}
}
}
示例
实体关系的默认约定
假设有X和Y两个类。
//一对一:两个类分别包含对方的一个引用
namespace Model
{
public class X
{
public int XId { get; set; } //主键
public Y y { get; set; } //引用Y
}
public class Y
{
public int YId { get; set; } //主键
public X x { get; set; } //引用X
}
}
一对一
// 一对多:两个类中分别包含一个集合属性和一个引用
namespace Model
{
public class X
{
public int XId { get; set; } //主键
public List<Y> Ys { get; set; } //一对多,一个X对多个Y
}
public class Y
{
public int YId { get; set; } //主键
public X x { get; set; } // 作为外键引用X
}
}
一对多
//多对多:两个类分别包含对方的一个集合属性
namespace Model
{
public class X
{
public int XId { get; set; } //主键
public List<Y> Ys { get; set; }
}
public class Y
{
public int YId { get; set; } //主键
public List<X> Xs { get; set; }
}
}
多对多
配置实体关系
HasRequired ( lambdaForForeignKey )
//设置当前表的哪个字段为必须的外键,该字段不可以为null
HasOptional( lambdaForForeignKey )
//设置当前表的哪个字段为不必须的外键,该字段可以为null
//示例:
//通常情况下应在外键表写关系
HasRequired ( b => b.User );//当前表的哪个字段作为必须的外键并且是不可以为null的
HasOptional ( b => b.User ); //当前表的哪个字段作为不必须的外键并且是可以为null的
HasRequired ( b => b.User ).WithMany ( ); //当前表的哪个字段作为必须的外键并且是不可以为null的(HasRequired),该外键表对主键表的引用也可以是多个外键引用同一个主键(WithMany)
配置实体关系
示例
//Lodging表的列配置
public class LodgingConfiguration : EntityTypeConfiguration<Lodging>
{
public LodgingConfiguration ( )
{
HasRequired ( l=>l.Destination ); //外键不可以为null
Property ( l=>l.Name ).HasMaxLength ( 20 ).IsRequired ( ); //可空字段
}
}
示例