大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。
当前系列: ADO&EF 修改讲义

EF6和EF core大同小异,区别在于:

Nuget导入

SqlServer:EntityFramework(没有core)

mysql:MySql.Data.EntityFramework.dll:


DbContext

不能override/没有OnConfiguring()方法,需要通过构造函数来指定数据库。最常用的方式是:

首先在配置文件(App.config/Web.config中添加连接字符串(复习:获取connection字符串

<connectionStrings>
  <add name="bang" providerName="System.Data.SqlClient" connectionString="Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=17bang;Integrated Security=True;"/>
</connectionStrings>

注意:

  • providerName是必须的,指定使用哪一种数据库(延伸:provider模式)
  • 可以添加多个connectionStrings节点,用name区分
然后base中使用连接字符串的name:
public SqlDbContext() : base("name=bang")
其中name=也可以省略:
public SqlDbContext() : base("bang")

如果配置文件中没有连接字符串的配置,且VS中已配置了LocalDB,就会以该字符串为数据库名自动新建数据库。

此外,也可以直接传入连接字符串:

public SqlDbContext() : 
    base(@"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=18bang;Integrated Security=True;")

mysql需要在DbContext类上加特性:

[DbConfigurationType(typeof(MySqlEFConfiguration))]
public class MysqlDbContext : DbContext



Migration

因为在演示EFcore的时候已经安装了Migration Tool,所以可以直接检查EF6的migration能否使用:

GET-HELP about_EntityFramework6

返回以上图形,表示一切OK。

EF6上需要首先执行Enable-Migrations

Enable-Migrations会生成Migrations\Configuration.cs文件。

PS:EF6也为我们提供了自动Migration的选项。

    public Configuration()
    {
        AutomaticMigrationsEnabled = false;
    } 
但一般不建议使用,因为无法追踪版本(参考:https://stackoverflow.com/questions/11806570/automaticmigrationsenabled-false-or-true) 

运行时会出现如下高亮提示:

Both Entity Framework 6 and Entity Framework Core are installed. The Entity Framework 6 tools are running. Use 'EntityFrameworkCore\Update-Database' for Entity Framework Core.这告诉我们:

  1. EF6和EF core都被安装了
  2. 当前运行的是EF6
  3. 如果要运行core的话,需要加前缀……(反之亦然),比如:
    • EntityFrameworkCore\Add-Migration
    • EntityFramework6\Add-Migration

然后其他命令和EF core一样,

  • Add-Migration生成文件:
  • Update-Database建库建表(包含_MigrationHistory表)

但如果要Update-Database到某个特定版本的话,需要加

  • 参数-TargetMigration,比如:Update-Database -TargetMigration init
而且没有
  • Remove-Migration命令,需要的话直接删Migrations文件吧,^_^
  • Script-Migration命令,而是利用:Update-Database -Script -SourceMigration: xxx -TargetMigration: xxx

API建库建表

只是方法名/类名的区别:

SqlDbContext context = new SqlDbContext();
Database database = context.Database;

database.Delete();
database.Create();


Logger

可以直接给Database.Log赋值:(更多详见文档
    public SqlContext() : base("17bang")
    {
        //从控制台输出
        Database.Log = Console.WriteLine;
        //从VS的out window输出
        Database.Log = s => Debug.Write(s);
        //写入磁盘文件:c:\\ef.log
        Database.Log = s => File.AppendAllText("c:\\ef.log", s);
    }

@想一想@:为什么不能直接

Database.Log = Debug.Write;
因为特性:
[Conditional("DEBUG")]
public static void Write(string message)


关联

EF6的继承和EF core大致相同,略

[ComplexType]

对应Owned Entity,添加该特性的类不会独立映射成一张表,而是若干列。

[ComplexType]
public class Bed /*: BaseEntity 不需要Id */ 

然后注意引用它是不能让它为NULL值,否则会报错:

public  Bed SleepIn { get; set; } = new Bed();

其他可参考这里……

Has/With

普通/默认的1:n和n:n和EF core没有区别。

但是1:1需要显式的指定主外键关系。

EF6中没有HasOne()和WithOne(),而是配合使用:

  • HasRequired()/HasOptional()
  • WithRequired()/WithOptional()
  • WithRequiredDependent()/WithRequiredPrincipal()

细分1:1和1:0/1,并指定主外键关系。比如,Student之前Bed在后

  • 一个学生必有一张床,一张床必有一个学生(一一对应),就只能是HasRequired().WithRequiredXXX(),XXX指定谁做(自增)主键谁做外键
    modelBuilder.Entity<Student>()
        .HasRequired(s => s.SleepIn)    //Student需要SleepIn才能持久化
        //.WithRequiredDependent()      //Bed做为Dependent(依赖,主键值由Student赋予) 
        .WithRequiredPrincipal()        //Bed做为Principal(PK,Identity)
  • 一个学生必有一张床,但不是每一张床都有一个学生(可以空着),就应该是HasRequired().WithOptional()
    modelBuilder.Entity<Student>()
        .HasRequired(s => s.SleepIn)
        .WithOptional(b => b.Student)  //参数不能省略

注意:这时候能显式声明影子属性,比如SleepInId,StudentId……否则会将他们视为普通属性映射

ThenInclude()替代

没有ThenInclude(),只有Select()

    context.Teachers.Where(t => t.Id > 5)
        .Include(t => t.Students
            .Select(ts => ts.SleepIn).Select(b=>b.Room))
        .Include(t => t.Students
            .Select(ts => ts.StudyIn))
        .FirstOrDefault();

延迟加载

默认开启,只要关联属性上标记了virtual关键字


跨DbContext冲突

当项目变得复杂时,因为各种各样的原因,可能出现把一个DbContext中取出来的entity,放入另外一个DbContext中处理。比如:

private static Teacher getTeacher(int id)
{
    using (SqlDbContext context = new SqlDbContext())
    {
        context.Teachers.Find(id);
SqlDbContext context = new SqlDbContext();
context.Find<Major>(4).Teacher = getTeacher(1);

我们是想将已有的Teacher赋值给Major,但EF6却是新建了一个Teacher。@想一想@:为什么?因为对于major所在的DbContex而言,另一个DbContext生成的teacher是“全新”的(即使有Id)

在SaveChanges()的时候,EF6和EF core使用了不同的机制来确定一个entity的状态:

  • EF core:只要有Id,就不会再insert
  • EF6:不是自己从数据库中取出来的,就没有ChangeTracker的追踪,就会将其视为Added——除非Add()或Attach()
但对于Web项目而言,更好的解决方案是ContextPerRequest


其他

DateTime

C#中的DateTime如果没有赋值的话,就是0000年1月1日 0时0分;

在EF6中DateTime自动被映射成datetime类型,最小值1900-01-01 00:00:00,无法存放上述日期值。

所以要么指定映射类型:

modelBuilder.Entity<Student>()
    .Property(s => s.Enroll).HasColumnType("datetime2");
要么设置为可空类型(推荐):
public DateTime? Enroll { get; set; }

DbContext和DbSet

比EF core少一些方法:

  • 只能从DbSet调用Find()/Remove()/Add()等方法,所以DbContext.Set<T>()方法用处更多了
  • DbSet里只有Attach(),没有Update()方法

Sql语句

(对应FromRawSql()等)只有名为SqlQuery()的方法,可以传入自定义的sql语句和参数:

context.Students.SqlQuery("sql语句", new SqlParameter(
context.Database.SqlQuery<Student>("sql语句", new SqlParameter(

在EF的Linq语句后调用ToString()方法,可以查看其SQL语句:

string sql = context.Students.Where(s=>s.Age > 18).ToString();


作业

基于EF6上完成之前作业

学习笔记
源栈学历
今天学习不努力,明天努力找工作

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 ADO&EF 中继续学习:

下一课: 已经是最后一课了……

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码