实体框架和SQLite,最终方法

时间:2020-08-19 20:23:44

标签: c# entity-framework sqlite

我正试图使Entity Framework(6.4.4。2020年夏季的最新版本)与SQLite(1.0.113.1,也是2020年夏季的最新版本)一起工作。

我发现了很多有关如何执行此操作的信息,但是这些信息并不总是有用的,它们经常相互矛盾。

现在我知道了如何做,我决定记下我是怎么做的。

问题描述了类和表,答案描述了如何做。

我描述了一个学校的数据库,其中每个学校有零个或更多的学生和老师(一对多),每个学生和每个老师都有一个地址(一对一),老师教的零个或更多学生,而学生由零个或更多的老师教(多对多)

所以我有几个表:

  • 一个简单的地址:地址
  • 一个简单的例子:学校
  • 对进入的学校具有外键的学生
  • 对他们所在的学校有外键的老师。
  • TeachersStudents:实现学生与教师之间多对多关系的连接表

课程:

地址和学校:

public class Address
{
    public long Id { get; set; }
    public string Street { get; set; }
    public int Number { get; set; }
    public string Ext { get; set; }
    public string ExtraLine { get; set; }
    public string PostalCode { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
}

public class School
{
    public long Id { get; set; }
    public string Name { get; set; }

    // Every School has zero or more Students (one-to-many)
    public virtual ICollection<Student> Students { get; set; }

    // Every School has zero or more Teachers (one-to-many)
    public virtual ICollection<Teacher> Teachers { get; set; }
}

师生:

public class Teacher
{
    public long Id { get; set; }
    public string Name { get; set; }

    // Every Teacher lives at exactly one Address
    public long AddressId { get; set; }
    public virtual Address Address { get; set; }

    // Every Teacher teaches at exactly one School, using foreign key
    public long SchoolId { get; set; }
    public virtual School School { get; set; }

    // Every Teacher Teaches zero or more Students (many-to-many)
    public virtual ICollection<Student> Students { get; set; }
}

public class Student
{
    public long Id { get; set; }
    public string Name { get; set; }

    // Every Student lives at exactly one Address
    public long AddressId { get; set; }
    public virtual Address Address { get; set; }

    // Every Student attends exactly one School, using foreign key
    public long SchoolId { get; set; }
    public virtual School School { get; set; }

    // Every Student is taught by zero or more Teachers (many-to-many)
    public virtual ICollection<Teacher> Teachers { get; set; }
}

最后是DbContext:

public class SchoolDbContext : DbContext
{
    public DbSet<Address> Addresses { get; set; }
    public DbSet<School> Schools { get; set; }
    public DbSet<Student> Students { get; set; }
    public DbSet<Teacher> Teachers { get; set; }
}

使用实体框架时,无需在DbContext中定义Junction表TeachersStudents。当然,这并不意味着您将不需要它。

如果您使用Microsoft SQL Server,这足以让实体框架识别表以及表之间的关系。

A,使用SQLite还不够。

所以:如何使它工作。继续回答!

1 个答案:

答案 0 :(得分:1)

因此,我使用Visual Studio创建了一个空解决方案,并添加了一个DLL项目:SchoolSQLite。 为了查看是否可行,我还添加了一个控制台应用程序,该应用程序将使用实体框架访问数据库。

为完整起见,我添加了一些单元测试。这超出了这个答案的范围。

在DLL项目中,我使用References-Manage NUGET Packages搜索System.Data.SQLite。这是同时添加实体框架和SQLite所需代码的版本。如果需要:请更新到最新版本。

添加问题中描述的班级:地址,学校,老师,学生,SchoolDbContext。

现在出现了我最困难的部分:控制台应用程序文件App.Config中的连接字符串。

要使其正常运行,我需要在App.Config中包含以下部分:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
    <configSections>
        <!-- For more information on Entity Framework configuration, visit ... -->
        <section name="entityFramework"
        type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, 
        Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
        requirePermission="false"/>
    </configSections>

稍后在App.Config中的EntityFramework部分:

<entityFramework>
  <providers>
    <provider invariantName="System.Data.SqlClient" 
      type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer"/>
    <provider invariantName="System.Data.SQLite.EF6" 
      type="System.Data.SQLite.EF6.SQLiteProviderServices, System.Data.SQLite.EF6"/>
  </providers>
</entityFramework>

<system.data>
  <DbProviderFactories>
    <remove invariant="System.Data.SQLite.EF6" />
    <add name="SQLite Data Provider"
       invariant="System.Data.SQLite.EF6"
       description=".NET Framework Data Provider for SQLite"
       type="System.Data.SQLite.SQLiteFactory, System.Data.SQLite" />
  </DbProviderFactories>
</system.data>

最后是连接字符串。我的数据库所在的文件是C:\Users\Harald\Documents\DbSchools.sqlite。当然,您可以选择自己的位置。

<connectionStrings>
  <add name="SchoolDbContext"
     connectionString="data source=C:\Users\Haral\Documents\DbSchools.sqlite"
     providerName="System.Data.SQLite.EF6" />

(到其他数据库的连接字符串可能更多)

这应该可以编译,但是您还不能访问数据库。 2020年夏季实体框架不会创建表,因此您必须自己进行操作。

由于我认为这是SchoolDbContext的一部分,所以我添加了一个方法。为此,您需要对SQL有一点了解,但是我认为您了解了要点:

protected void CreateTables()
{
    const string sqlTextCreateTables = @"
        CREATE TABLE IF NOT EXISTS Addresses
        (
            Id INTEGER PRIMARY KEY NOT NULL,
            Street TEXT NOT NULL,
            Number INTEGER NOT NULL,
            Ext TEXT,
            ExtraLine TEXT,
            PostalCode TEXT NOT NULL,
            City TEXT NOT NULL,
            Country TEXT NOT NULL
        );
        CREATE INDEX IF NOT EXISTS indexAddresses ON Addresses (PostalCode, Number, Ext);

        CREATE TABLE IF NOT EXISTS Schools
        (
           Id INTEGER PRIMARY KEY NOT NULL,
           Name TEXT NOT NULL
        );

        CREATE TABLE IF NOT EXISTS Students
        (
            Id INTEGER PRIMARY KEY NOT NULL,
            Name TEXT NOT NULL,
            AddressId INTEGER NOT NULL,
            SchoolId INTEGER NOT NULL,

            FOREIGN KEY(AddressId) REFERENCES Addresses(Id)  ON DELETE NO ACTION,
            FOREIGN KEY(SchoolId) REFERENCES Schools(Id) ON DELETE CASCADE
        );

        CREATE TABLE IF NOT EXISTS Teachers
        (
            Id INTEGER PRIMARY KEY NOT NULL,
            Name TEXT NOT NULL,

            AddressId INTEGER NOT NULL,
            SchoolId INTEGER NOT NULL,

            FOREIGN KEY(AddressId) REFERENCES Addresses(Id)  ON DELETE NO ACTION,
            FOREIGN KEY(SchoolId) REFERENCES Schools(Id) ON DELETE CASCADE
        );

        CREATE TABLE IF NOT EXISTS TeachersStudents
        (
            TeacherId INTEGER NOT NULL,
            StudentId INTEGER NOT NULL,

            PRIMARY KEY (TeacherId, StudentId)
            FOREIGN KEY(TeacherId) REFERENCES Teachers(Id) ON DELETE NO ACTION,
            FOREIGN KEY(StudentId) REFERENCES Students(Id) ON DELETE NO ACTION
        )";

    var connectionString = this.Database.Connection.ConnectionString;
    using (var dbConnection = new System.Data.SQLite.SQLiteConnection(connectionString))
    {
        dbConnection.Open();
        using (var dbCommand = dbConnection.CreateCommand())
        {
            dbCommand.CommandText = sqlTextCreateTables;
            dbCommand.ExecuteNonQuery();
        }
    }
}

有些事情值得一提:

  • 表地址有一个额外的索引,因此使用PostalCode +门牌号(+扩展名)搜索地址会更快。 “您的邮政编码是什么?” “好吧,这是5473TB,房子号6”。索引将立即显示完整的地址。
  • 尽管SchoolDbcontext并未提及联结表TeachersStudents,但我仍然需要创建它。 [TeacherId,StudentId]组合将是唯一的,因此可以用作主键
  • 如果学校被删除,则其所有的师生也需要被删除:DECATE CASCADE
  • 如果老师离开了学校,应该不会伤害学生。如果学生离开学校,则老师会继续教学:不删除任何动作

应用程序启动后第一次执行实体框架查询时,将调用方法OnModelCreating。因此,这是检查表是否存在的好时机,如果不存在,请创建它们。

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    this.CreateTables();

当然,您应该使用OnModelCreating通知实体框架有关您的表以及表之间的关系。可以在创建表后完成。

继续OnModelCreating:

    this.OnModelCreatingTable(modelBuilder.Entity<Address>());
    this.OnModelCreatingTable(modelBuilder.Entity<School>());
    this.OnModelCreatingTable(modelBuilder.Entity<Teacher>());
    this.OnModelCreatingTable(modelBuilder.Entity<Student>());

    this.OnModelCreatingTableRelations(modelBuilder);

    base.OnModelCreating(modelBuilder);
}

对于那些了解实体框架的人来说,对这些表进行建模非常简单。

地址;简单表的示例

private void OnModelCreatingTable(EntityTypeConfiguration<Address> addresses)
{
    addresses.ToTable(nameof(SchoolDbContext.Addresses)).HasKey(address => address.Id);
    addresses.Property(address => address.Street).IsRequired();
    addresses.Property(address => address.Number).IsRequired();
    addresses.Property(address => address.Ext).IsOptional();
    addresses.Property(address => address.ExtraLine).IsOptional();
    addresses.Property(address => address.PostAlCode).IsRequired();
    addresses.Property(address => address.City).IsRequired();
    addresses.Property(address => address.Country).IsRequired();

    // The extra index, for fast search on [PostalCode, Number, Ext]
    addresses.HasIndex(address => new {address.PostAlCode, address.Number, address.Ext})
        .HasName("indexAddresses")
        .IsUnique();
    }

学校也很简单:

    private void OnModelCreatingTable(EntityTypeConfiguration<School> schools)
    {
        schools.ToTable(nameof(this.Schools))
            .HasKey(school => school.Id);
        schools.Property(school => school.Name)
            .IsRequired();
    }

师生:他们需要学校的外键,每所学校的学生/老师为零或更多:

private void OnModelCreatingTable(EntityTypeConfiguration<Teacher> teachers)
{
    teachers.ToTable(nameof(SchoolDbContext.Teachers))
            .HasKey(teacher => teacher.Id);
    teachers.Property(teacher => teacher.Name)
            .IsRequired();

    // Specify one-to-many to Schools using foreign key SchoolId
    teachers.HasRequired(teacher => teacher.School)
            .WithMany(school => school.Teachers)
            .HasForeignKey(teacher => teacher.SchoolId);
}

private void OnModelCreatingTable(EntityTypeConfiguration<Student> students)
{
    students.ToTable(nameof(SchoolDbContext.Students))
            .HasKey(student => student.Id);
    students.Property(student => student.Name)
            .IsRequired();

    // Specify one-to-many to Schools using foreign key SchoolId        
    students.HasRequired(student => student.School)
            .WithMany(school => school.Students)
            .HasForeignKey(student => student.SchoolId);
}

注意:默认情况下:如果学校被删除,它将级联下降:所有教师和学生都将被删除。

只剩下一个表关系:联结表。如果我愿意的话,我可以在这里定义学校与教师之间以及学校与学生之间的一对多关系。在定义教师和学生时,我已经做到了。因此,这里不需要它们。我留下了代码,作为示例,如果您想将它们放在这里。

private void OnModelCreatingTableRelations(DbModelBuilder modelBuilder)
{
    //// School <--> Teacher: One-to-Many
    //modelBuilder.Entity<School>()
    //    .HasMany(school => school.Teachers)
    //    .WithRequired(teacher => teacher.School)
    //    .HasForeignKey(teacher => teacher.SchoolId)
    //    .WillCascadeOnDelete(true);

    //// School <--> Student: One-To-Many
    //modelBuilder.Entity<School>()
    //    .HasMany(school => school.Students)
    //    .WithRequired(student => student.School)
    //    .HasForeignKey(student => student.SchoolId)
    //    .WillCascadeOnDelete(true);

    // Teacher <--> Student: Many-to-many
    modelBuilder.Entity<Teacher>()
       .HasMany(teacher => teacher.Students)
       .WithMany(student => student.Teachers)
       .Map(manyToMany =>
       {
           manyToMany.ToTable("TeachersStudents");
           manyToMany.MapLeftKey("TeacherId");
           manyToMany.MapRightKey("StudentId");
       });
}

many-to-many mapping is explained here

现在我们快完成了。我们要做的就是确保不会删除和重新创建数据库。通常在以下位置完成:

Database.SetInitializer<SchoolDbContext>(null);

因为我想隐藏我们使用SQLite的功能,所以我将其添加为SchoolDbContext的一种方法:

public class SchoolDbContext : DbContext
{
    public static void SetInitializeNoCreate()
    {
        Database.SetInitializer<SchoolDbContext>(null);
    }

    public SchoolDbContext() : base() { }
    public SchoolDbContext(string nameOrConnectionString) : base(nameOrConnectionString) { }

    // etc: add the DbSets, OnModelCreating and CreateTables as described earlier
}

我有时看到人们在构造函数中设置初始化程序:

public SchoolDbContext() : base()
{
    Database.SetInitializer<SchoolDbContext>(null);
}

但是,此构造函数将经常被调用。我认为每次这样做都有些浪费。

当然,有些模式可以在首次构造SchoolDbContext时自动设置一次初始化程序。为了简单起见,我在这里没有使用它们。

控制台应用程序

static void Main(string[] args)
{
    Console.SetBufferSize(120, 1000);
    Console.SetWindowSize(120, 40);

    Program p = new Program();
    p.Run();

    // just for some need ending:
    if (System.Diagnostics.Debugger.IsAttached)
    {
        Console.WriteLine();
        Console.WriteLine("Fin");
        Console.ReadKey();
    }
}

Program()
{
    // Set the database initializer:
    SchoolDbContext.SetInitializeNoCreate();
}

现在有趣的部分:添加学校,添加一些老师,添加一些学生并给他一些老师。

void Run()
{
    // Add a School:
    School schoolToAdd = this.CreateRandomSchool();
    long addedSchoolId;
    using (var dbContext = new SchoolDbContext())
    {
        var addedSchool = dbContext.Schools.Add(schoolToAdd);
        dbContext.SaveChanges();
        addedSchoolId = addedSchool.Id;
    }

添加老师:

    Teacher teacherToAdd = this.CreateRandomTeacher();
    teacherToAdd.SchoolId = addedSchoolId;

    long addedTeacherId;
    using (var dbContext = new SchoolDbContext())
    {
        var addedTeacher = dbContext.Teachers.Add(teacherToAdd);
        dbContext.SaveChanges();
        addedTeacherId = addedTeacher.Id;
    }

添加学生。

Student studentToAdd = this.CreateRandomStudent();
studentToAdd.SchoolId = addedSchoolId;

long addedStudentId;
using (var dbContext = new SchoolDbContext())
{
    var addedStudent = dbContext.Students.Add(studentToAdd);
    dbContext.SaveChanges();
    addedStudentId = addedStudent.Id;
}

几乎完成了:只有师生之间的多对多关系:

学生决定由老师教书

using (var dbContext = new SchoolDbContext())
{
    var fetchedStudent = dbContext.Find(addedStudentId);
    var fetchedTeacher = dbContext.Find(addedTeacherId);

    // either Add the Student to the Teacher:
    fetchedTeacher.Students.Add(fetchedStudent);

    // or Add the Teacher to the Student:
    fetchedStudents.Teachers.Add(fetchedTeacher);
    dbContext.SaveChanges();
}

我还试图从学校撤职,发现这并没有伤害学生。另外,如果学生离开学校,则老师会继续教学。最后:如果我删除一所学校,所有的学生和老师都会被删除。

因此,现在我向您展示了一些简单的表格,例如地址和学校;具有一对多关系的表:教师和学生;以及多对多关系:StudentsTeachers。

我没有显示一种关系:自引用:指向同一表中另一个对象的外键。在School数据库中,我无法为此提供一个很好的例子。如果有人有个好主意,请编辑此答案并添加自引用表。

希望这对您有用。

相关问题