【问题标题】:Entity Framework and SQLite, the ultimate how-to实体框架和 SQLite,终极指南
【发布时间】:2021-08-07 18:45:18
【问题描述】:

我正在尝试让 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 中定义连接表 TeachersStudents。当然这并不意味着你不需要它。

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

唉,使用 SQLite 这还不够。

所以:如何让它工作。快来回答吧!

【问题讨论】:

    标签: c# entity-framework sqlite


    【解决方案1】:

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

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

    在 DLL 项目中,我使用References-Manage NUGET Packages 搜索System.Data.SQLite。这是添加 Entity Framework 和 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();
            }
        }
    }
    

    有些事情值得一提:

    • 表格地址有一个额外的索引,因此使用邮政编码 + 门牌号(+ 分机号)搜索地址会更快。 “你的邮政编码是多少?” “嗯,它是 5473TB,门牌号 6”。索引将立即显示完整的地址。
    • 虽然 SchoolDbcontext 没有提到连接表 TeachersStudents,但我仍然需要创建它。 [TeacherId, StudentId] 的组合是唯一的,因此可以用作主键
    • 如果学校被删除,则需要删除其所有教师和学生:ON DELETE CASCADE
    • 如果教师离开学校,学生不应受到伤害。如果学生离开学校,教师会继续教学:ON DELETE NO ACTION

    当您的应用程序启动后第一次执行实体框架查询时,会调用方法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 neat 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();
    }
    

    我还尝试将教师从学校中移除,并且发现这并没有伤害学生。此外,如果学生离开学校,教师将继续教学。最后:如果我删除学校,所有学生和教师都会被删除。

    所以现在我向你展示了:

    • 简单的表格,例如地址和学校;
    • 具有一对多关系的表:教师和学生;
    • 多对多关系:StudentTeachers。

    我没有展示一个关系:自引用:同一个表中另一个对象的外键。我无法在学校数据库中找到一个很好的例子。如果有人有好主意,请编辑此答案并添加自引用表。

    希望这对你有用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-24
      • 2013-04-13
      • 2014-07-28
      • 2010-10-30
      • 2019-09-06
      • 2013-12-26
      • 2021-12-14
      • 2013-06-25
      相关资源
      最近更新 更多