3.Code-First 约定(EF Code-First系列)

时间:2022-06-28 22:04:19

前面,我们已经了解了Code-First利用领域类,怎么为我们创建数据库的简单示例。现在我们来学习一下Code-First约定吧。

  • 什么是约定

约定说白了,就是基于一套规矩办事,这里就是基于你定义好的领域类,然后根据默认的规矩来配置概念模型。Code-First约定定义在这个命名空间下面:

System.Data.Entity.ModelConfiguration.Conventions

现在来大致浏览一下都有哪些约定吧:

  • 类型发现

在前面的章节中,我们在上下文类中创建了DbSet属性的类集合,然后Code-First会根据这个DbSet属性为我们创建数据表。

codeFirst会为我们包含任何引用类型到这些类集合中,甚至尽管这写引用类型定义在不同的程序集中。

例如:下面的代码中,Student实体包含了Teacher类的引用,然而上下文类总,却并未包含Teacher的类型的DBSet属性。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EF1
{
   public class Student
    {
        public int StudentId { get; set; }

        public string StudentName { get; set; }

        public DateTime DateOfBirth { get; set; }
        public byte[] Photo { get; set; }
        public decimal Height { get; set; }
        public float Weight { get; set; }

        public Standard Standard { get; set; }

        public Teacher Teacher { get; set; }

    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EF1
{
  public  class Teacher
    {
      public int TeacherId { get; set; }

      public string TeacherName { get; set; }
    }
}

数据上下文类中,并没有包含Teacher类型的DbSet属性:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EF1
{
   public class DbContextClass:DbContext
    {
       public DbContextClass()
           : base("ConnectionString")
       { }
       public DbSet<Student> Studnets { get; set; }

       public DbSet<Standard> Standards { get; set; }

    }
}

然后我们运行程序,运行成功之后,打开数据库,发现:

哈哈,是不是报错了?

3.Code-First 约定(EF Code-First系列)

具体的错误消息是:The model backing the 'DbContextClass' context has changed since the database was created. Consider using Code First Migrations to update the database (http://go.microsoft.com/fwlink/?LinkId=238269)

这是因为,我们改了实体,没有开启数据库迁移特性,不过还有另外一种解决办法,我这里,

我们改一下上下文类:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EF1
{
   public class DbContextClass:DbContext
    {
       public DbContextClass()
           : base("ConnectionString")
       { }
       public DbSet<Student> Studnets { get; set; }

       public DbSet<Standard> Standards { get; set; }

       protected override void OnModelCreating(DbModelBuilder modelBuilder)
       {
           Database.SetInitializer(new DropCreateDatabaseIfModelChanges<DbContextClass>());

           base.OnModelCreating(modelBuilder);
       }

    }
}

这里改动的语句:

Database.SetInitializer(new DropCreateDatabaseIfModelChanges<DbContextClass>());

意思是,当模型发生改变的时候,就删掉重新创建数据库。

现在我们运行程序,发现就能运行成功了。

然后我们看一下数据库:

3.Code-First 约定(EF Code-First系列)

多了一个Teacher表。

看一下Teacher的表结构:

3.Code-First 约定(EF Code-First系列)

然后看一下Student表的表结构:

3.Code-First 约定(EF Code-First系列)

Code-First可以推断类,尽管上下文中并没有包含Teacher的属性。

所以,我们总结一下类型发现的约定:

  • Code-First包含类型定义也就是DbSet属性在上下文类中;Code-First includes types defined as a DbSet property in context class.
  • Code-First包含引用类型在实体类中,尽管他们定义在不同的程序集下;Code-First includes reference types included in entity types even if they are defined in different assembly.
  • Code-First包含派生类,尽管这个类要定义成DbSet属性。(Code-First includes derived classes even if only the base class is defined as DbSet property.)
  • 主键约定

在之前的例子中,我们看见了Code-First为每个表都创建了主键,这个默认的规则就是,类名Id(大小写不敏感,即不区分大小写)。这个主键的类型可以是任何类型,但是如果主键的类型是numeric 或者GUID,它将会被配置成自增列。

然后,如果你定义主键的属性名字不是Id或者不是类名+ID,然后在运行的时候,就会报错。

我们看看下面的代码就知道了:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EF1
{
   public class Student
    {
        public int StdID { get; set; }

        public string StudentName { get; set; }

        public DateTime DateOfBirth { get; set; }
        public byte[] Photo { get; set; }
        public decimal Height { get; set; }
        public float Weight { get; set; }

        public Standard Standard { get; set; }

        public Teacher Teacher { get; set; }

    }
}

改完之后,我们运行程序,就会报错:

3.Code-First 约定(EF Code-First系列)

具体的错误消息:

One or more validation errors were detected during model generation:

EF1.Student: : EntityType 'Student' has no key defined. Define the key for this EntityType.
Studnets: EntityType: EntitySet 'Studnets' is based on type 'Student' that has no keys defined.

意思是没有定义主键》》》

如果你想要StdID成为主键,可以使用数据注解或者Fluent APIS,我们在后面将会学到。

  • 关系约定

通过导航属性,Code-First能够推断出,两个实体之间的关系,这个导航属性可以是简单的引用类型或者是集合类型,例如,我们在Student实体中定义了Standard导航属性,然后我们在Stanrard实体中定义了Icollection<Student>导航属性,所以,Code-First将会自动为我们创建一对多的关系。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EF1
{
   public class Student
    {
        public int StdID { get; set; }

        public string StudentName { get; set; }

        public DateTime DateOfBirth { get; set; }
        public byte[] Photo { get; set; }
        public decimal Height { get; set; }
        public float Weight { get; set; }

       /// <summary>
       /// 导航属性
       /// </summary>
        public Standard Standard { get; set; }

    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace EF1
{
   public class Standard
    {
       public int StandardId { get; set; }

       public string StandardName { get; set; }

       /// <summary>
       /// 集合类型的导航属性
       /// </summary>
       public ICollection<Student> Students { get; set; }
    }
}

然后我们运行程序成功之后,看到数据库:

3.Code-First 约定(EF Code-First系列)

Code-First约定,为我们创建了外键

<navigation property Name>_<primary key property name of navigation property type> e.g. Standard_StandardId.

  • 外键约定

我们已经知道了,当我们添加一个外键属性的时候,Code--First会我们创建一个外键。推荐做法是,包含一个外键属性的依赖关系。

请看下面的代码;

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EF1
{
   public class Student
    {
       public int StudentId { get; set; }

        public string StudentName { get; set; }

        public DateTime DateOfBirth { get; set; }
        public byte[] Photo { get; set; }
        public decimal Height { get; set; }
        public float Weight { get; set; }

       /// <summary>
       /// 导航属性
       /// </summary>
        public Standard Standard { get; set; }

       /// <summary>
       /// Foriegn key  for  Standard
       /// </summary>
        public int StandardId { get; set; }

    }
}

这样改动之后,我们在运行程序,发现怎么了?是不是又报错了?

3.Code-First 约定(EF Code-First系列)

这个是因为,外键表Standard中没有数据。

我们往Standard表中,插入一条数据,然后改一下Main函数里面的代码:再运行程序>>>>

3.Code-First 约定(EF Code-First系列)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EF1
{
    class Program
    {
        static void Main(string[] args)
        {

            Student stu = , StudentName = , Weight = , DateOfBirth = DateTime.Now,StandardId=1};

            using (var db = new DbContextClass())
            {
                db.Studnets.Add(stu);
                db.SaveChanges();
            }

            Console.WriteLine("添加成功");
            Console.ReadKey();
        }
    }
}

运行之后,是不是没有出错了?

现在让我们再来看一下数据库:

3.Code-First 约定(EF Code-First系列)

发现什么变化了么?是不是这个外键的名字不再是以外键属性的名字+外键实体的主键ID命名了?

并且你注意到了没有,这个外键属性也是不能为空的?

因为int类型是不能为空的!!

code-Forst可以基于外键属性的【为空性】(nullability )推断出多重性的关系,如果这个属性是可以为空的,然后这个关系可以看成是null,否则的话,就是not null,你可以修改

StandardId属性的类型从int变成Nullable<int>,然后运行程序的话,得到的外键就是可以为空了。

  • 复杂类型约定

Code-First可以为类创建复杂类型,可以不包含主键属性,并且不使用数据注解和Fluent APIs来注册主键

上面这写就是Code-First约定的大概内容了,这些约定可以被打破,通过使用数据注解和Fluent APIs,在EF6中,你可以自定义约定。

附上系列目录: