前言
之前有学过EF一段时间那时EF才4.0似乎还不太稳定,而现在EF都已7.0版本,同时AspNet Identity都与此大有关联,看来是大势所趋于是开始学习EF,在学EF过程中也遇到一些小问题,特此录下,以备忘!
数据库和表基本创建
为了更好的循序渐进稍微概括下典型创建EF Code First过程(看之即懂,懂即略过)
第一步先定义两个类,如下:
public class Student { public Student() { } public int StudentID { get; set; } public string StudentName { get; set; } } public class Standard { public Standard() { } public int StandardId { get; set; } public string StandardName { get; set; } }
第二步:继承EF上下文DbContext
public class SchoolContext : DbContext { public SchoolContext("name=DBConnectionString") { } public DbSet<Student> Students { get; set; } public DbSet<Standard> Standards { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); } }
学生上下文中构造函数中的name去读取如下配置文件来命名数据库名称: DBByConnectionString
<add name="DBConnectionString" connectionString="Data Source=.;Initial Catalog=DBByConnectionString;Integrated Security=true" providerName="System.Data.SqlClient"/> </connectionStrings>
然后在控制台中通过EF上下文添加数据并保存,如下:
using (var ctx = new SchoolContext()) { Student stud = new Student() { StudentName = "New Student" }; ctx.Students.Add(stud); ctx.SaveChanges(); }
最终生成数据库以及表如下图:
上述创建数据库的过程只需注意:可以手动通过添加构造函数的name来命名数据库名称或者无需添加name那么生成的数据库名称是以上下文中的命名空间+上下文类来命名数据库名称。
数据库创建以及表一劳永逸配置
下面创建方法是看过园友hystar(EF教程)而写的,确实是好方法,就搬过来了,为什么说一劳永逸呢?不明白的话,可以去看看他的文章!首先添加两个类Student(学生类)和Course(课程类)。
public class Student { public int ID { get; set; } public string Name { get; set; } public int Age { get; set; } public virtual Course Course { get; set; } } public class Course { public int StudentID { get; set; } public string Name { get; set; } public virtual Student Student { get; set; } }
添加EFDbContext类并继承DbContext上下文,代码如下:
public class EntityDbContext : DbContext { public EntityDbContext() : base("name=test2") { } /// <summary> /// 通过反射一次性将表进行映射 /// </summary> /// <param name="modelBuilder"></param> protected override void OnModelCreating(DbModelBuilder modelBuilder) { var typesRegister = 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 typesRegister) { dynamic configurationInstance = Activator.CreateInstance(type); modelBuilder.Configurations.Add(configurationInstance); } } }
由于是手动命名数据库名称,当然得读取配置文件
<connectionStrings> <add name="test2" connectionString="Data Source=(localdb)\v11.0;Initial Catalog=test2;Integrated Security=true" providerName="System.Data.SqlClient"/> </connectionStrings>
上述配置要添加的数据库建立在VS2013自带的实例中!我们首先初始化数据库看看:
EntityDbContext ctx = new EntityDbContext();
结果运行就出现如下经典错误:
在与SQLServer建立连接时出现与网络相关的或特定与实例的错误.未找到或无法访问服务器.请验证实例名称是否正确并且SQL SERVER已配置允许远程链接provide:命名管道提供程序,error:40 -无法打开到SQL Server的连接)
那肯定是无法连接到 (localdb)\v11.0 ,于是当我在服务器打开添加连接中添加服务器名为 (localdb)\v11.0 时也是无法响应,连接不到!最终通过SqlLocalDB命令在Command Prompt(命令行)中输入
SqlLocalDB.exe start v11.0
启动该实例才算完事,主要原因是安装了SQL 2012默认启动的实例该SQL 2012而VS 2013中的实例被停止运行得手动启动,如果要查看其信息来查看是否已经启动,通过以下命令即可:
SqlLocalDB.exe info v11.0
VS2013中默认的实例应该是(localdb)\v11.0,如果在服务器中添加连接输入(localdb)\v11.0是错误的,你可以通过上述 SqlLocalDB.exe info v11.0 命令复制并添加如图的字符串即可
似乎只要第一次启动了,以后每次都会连接上,不会再出现如上问题!
上述中我们对于EF上下文不用每次都初始化数据库,在EF中初始化数据库有三种策略:
CreateDatabaseIfNotExists:该项也是默认初始化数据库的一项,要是数据库不存在就创建数据库。
DropCreateDatabaseIfModelChanges:只要数据模型发生了改变就重新创建数据库。
DropCreateDatabaseAlways:只要每次初始化上下文时就创建数据库。
鉴于此我们在EFDbContext中采用第二种策略。创建一个初始化类的策略 EFDbContextInit
/// <summary> /// 当对象实体对象发生改变时重生创建数据库 /// </summary> public class EntityDbContextInit : DropCreateDatabaseIfModelChanges<EntityDbContext> { protected override void Seed(EntityDbContext context) { base.Seed(context); } }
在EFDbContext静态构造函数中进行初始化此方法:
static EntityDbContext() { Database.SetInitializer<EntityDbContext>(new EntityDbContextInit()); }
自此EFDbContext构建完毕!下面就是模型映射了,我们假设学生和课程是1:1关系,则我们添加的两个实体映射如下:
StudentMap(学生类实体映射)
public class StudentMap : EntityTypeConfiguration<Student> { public StudentMap() { ToTable("Student"); HasKey(d => d.ID); //HasRequired(p => p.Course).WithRequiredDependent(i => i.Student); //HasRequired(p => p.Course).WithOptional(i => i.Student); HasRequired(p => p.Course).WithRequiredPrincipal(p => p.Student); HasOptional(p => p.Course).WithRequired(p => p.Student); /* 对于上述映射关系不太理解的话可以去上述给出链接文章。我只说明怎么去很好的理解这两组的意思,第一组 WithRequiredDependent 和第二组 WithRequiredPrincipal 一个是Dependent是依赖的意思说明后面紧接着的Student是依赖对象,而前面的Course是主体,而Principal
首先的意思,说明后面紧接着的是Student是主体,而Course是依赖对象。很显然在这个关系中课程是依赖学生的。所以映射选第二组 */ } }
CourseMap(课程类映射)
public class CourseMap : EntityTypeConfiguration<Course> { public CourseMap() { ToTable("Course"); HasKey(p => p.StudentID); } }
接下来我们进行添加数据并保存通过如下代码:
EntityDbContext ctx = new EntityDbContext(); var s = new Student() { Name = "1", Age = 12, Course = new Course() { Name = "12" } }; ctx.Set<Student>().Add(s); ctx.SaveChanges();
数据添加和保存都已通过,接下来进行查询数据,查询数据有两种方式:
(1)直接通过EF中Set()方法获得数据集合
(2)通过EF中SqlQuery()方法通过sql语句查询
如要获得上述学生数据列表集合,可以通过如下操作:
EntityDbContext ctx = new EntityDbContext(); var list = ctx.Set<Student>().ToList(); 或者 SqlParameter[] parameter = { }; var list = ctx.Database.SqlQuery<Student>("select * from student", parameter);
于是我监视下返回的list集合中的数据类型,如图
oh,shit!和我们实际的实体类型不符,通过EF产生的却是 DynamicProxies ,于是到Sytem.Data.Entity类下去看看是个什么类型,居然没找到,估计看这单词意思就是运行时产生的动态代理对象。那么,你觉得是不是没什么影响了???那影响可大了,请看下面操作:
var list = ctx.Set<Student>().ToList(); var jsonString = JsonConvert.SerializeObject(list);
我尝试将其序列化看看结果,一运行,oh,no!错误如下:
这意思是检测到在Course里面有Student属性,而Student类里又有Course这就相当于自己引用自己,导致了循环引用就成了死循环。(这就是因为 DynamicProxies 导致的结果)所以当前要将其代理对象转换为我们的实体对象即可。
则通过Select()方法投影将其代码进行改造后如下:
var list = ctx.Set<Student>().Include(p => p.Course).ToList().Select(entity => new Student() { ID = entity.ID, Name = entity.Name, Age = entity.Age }).ToList(); 或者 var list = ctx.Set<Student>().Include("Course").ToList().Select(entity => new Student() { ID = entity.ID, Name = entity.Name, Age = entity.Age }).ToList();
对象转换成功,如下:
序列化成功结果如下:
【注意】你用EF获得数据集合后得 ToList() 因为此时集合对象为代理对象,否则进行转换将报错,代码如下:
var list = ctx.Set<Student>().Include(p=>p.Course).Select(entity => new Student() { ID = entity.ID, Name = entity.Name, Age = entity.Age }).ToList();
报错如下:
上述转换也叫DTO(Data Transfer Objects)数据转换为对象,像这种情况在EF中很常见。下面给出老外的用两张图在两个常见的场景来展现关于DTO的概念:
Getting Information: DAL=>BLL=>GUI
Insert Information: GUI=>BLL=>DAL