实体框架和 SQLite,终极操作方法

2023-11-27

我正在尝试让 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 这还不够。

那么:如何让它发挥作用。来回答吧!


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

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

在我使用的DLL项目中References-Manage NUGET Packages寻找System.Data.SQLite。该版本添加了实体框架和 SQLite 所需的代码。如果需要:更新到最新版本。

添加问题中描述的类:Address、School、Teacher、Student、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 向实体框架通知您的表以及表之间的关系。这可以在创建表之后完成。

继续模型创建:

    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");
       });
}

The 这里解释了多对多映射

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

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();
}

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

现在我向您展示了:

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

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

希望这对您有用。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

实体框架和 SQLite,终极操作方法 的相关文章

  • C 编程 - 文件 - fwrite

    我有一个关于编程和文件的问题 while current NULL if current gt Id Doctor 0 current current gt next id doc current gt Id Doctor if curre
  • WCF RIA 服务 - 加载多个实体

    我正在寻找一种模式来解决以下问题 我认为这很常见 我正在使用 WCF RIA 服务在初始加载时将多个实体返回给客户端 我希望两个实体异步加载 以免锁定 UI 并且我想利用 RIA 服务来执行此操作 我的解决方案如下 似乎有效 这种方法会遇到
  • GLKit的GLKMatrix“列专业”如何?

    前提A 当谈论线性存储器中的 列主 矩阵时 列被一个接一个地指定 使得存储器中的前 4 个条目对应于矩阵中的第一列 另一方面 行主 矩阵被理解为依次指定行 以便内存中的前 4 个条目指定矩阵的第一行 A GLKMatrix4看起来像这样 u
  • 在哪里可以找到列出 SSE 内在函数操作的官方参考资料?

    是否有官方参考列出了 GCC 的 SSE 内部函数的操作 即 头文件中的函数 除了 Intel 的 vol 2 PDF 手册外 还有一个在线内在指南 https www intel com content www us en docs in
  • 嵌套接口:将 IDictionary> 转换为 IDictionary>?

    我认为投射一个相当简单IDictionary
  • 从Web API同步调用外部api

    我需要从我的 Web API 2 控制器调用外部 api 类似于此处的要求 使用 HttpClient 从 Web API 操作调用外部 HTTP 服务 https stackoverflow com questions 13222998
  • 用于登录 .NET 的堆栈跟踪

    我编写了一个 logger exceptionfactory 模块 它使用 System Diagnostics StackTrace 从调用方法及其声明类型中获取属性 但我注意到 如果我在 Visual Studio 之外以发布模式运行代
  • 堆栈溢出:堆栈空间中重复的临时分配?

    struct MemBlock char mem 1024 MemBlock operator const MemBlock b const return MemBlock global void foo int step 0 if ste
  • C# 中通过 Process.Kill() 终止的进程的退出代码

    如果在我的 C 应用程序中 我正在创建一个可以正常终止或开始行为异常的子进程 在这种情况下 我通过调用 Process Kill 来终止它 但是 我想知道该进程是否已退出通常情况下 我知道我可以获得终止进程的错误代码 但是正常的退出代码是什
  • 使用 WebClient 时出现 System.Net.WebException:无法创建 SSL/TLS 安全通道

    当我执行以下代码时 System Net ServicePointManager ServerCertificateValidationCallback sender certificate chain errors gt return t
  • C++ OpenSSL 导出私钥

    到目前为止 我成功地使用了 SSL 但遇到了令人困惑的障碍 我生成了 RSA 密钥对 之前使用 PEM write bio RSAPrivateKey 来导出它们 然而 手册页声称该格式已经过时 实际上它看起来与通常的 PEM 格式不同 相
  • 如何设计以 char* 指针作为类成员变量的类?

    首先我想介绍一下我的情况 我写了一些类 将 char 指针作为私有类成员 而且这个项目有 GUI 所以当单击按钮时 某些函数可能会执行多次 这些类是设计的单班在项目中 但是其中的某些函数可以执行多次 然后我发现我的项目存在内存泄漏 所以我想
  • iphone sqlite 静态链接?

    有人静态链接 sqlite 而不是使用动态链接 吗 我遇到的问题是 越狱手机的用户没有与普通 iPhone 所采用的 sqlite 版本相同的版本 因此导致崩溃 我假设在我的应用程序中静态链接已知版本的 sqlite 就是答案 我需要全文支
  • 什么时候虚拟继承是一个好的设计? [复制]

    这个问题在这里已经有答案了 EDIT3 请务必在回答之前清楚地了解我要问的内容 有 EDIT2 和很多评论 有 或曾经 有很多答案清楚地表明了对问题的误解 我知道这也是我的错 对此感到抱歉 嗨 我查看了有关虚拟继承的问题 class B p
  • Windows 窗体:如果文本太长,请添加新行到标签

    我正在使用 C 有时 从网络服务返回的文本 我在标签中显示 太长 并且会在表单边缘被截断 如果标签不适合表单 是否有一种简单的方法可以在标签中添加换行符 Thanks 如果您将标签设置为autosize 它会随着您输入的任何文本自动增长 为
  • WPF/C# 将自定义对象列表数据绑定到列表框?

    我在将自定义对象列表的数据绑定到ListBox in WPF 这是自定义对象 public class FileItem public string Name get set public string Path get set 这是列表
  • 通过指向其基址的指针删除 POD 对象是否安全?

    事实上 我正在考虑那些微不足道的可破坏物体 而不仅仅是POD http en wikipedia org wiki Plain old data structure 我不确定 POD 是否可以有基类 当我读到这个解释时is triviall
  • 将控制台重定向到 .NET 程序中的字符串

    如何重定向写入控制台的任何内容以写入字符串 对于您自己的流程 Console SetOut http msdn microsoft com en us library system console setout aspx并将其重定向到构建在
  • 基于 OpenCV 边缘的物体检测 C++

    我有一个应用程序 我必须检测场景中某些项目的存在 这些项目可以旋转并稍微缩放 更大或更小 我尝试过使用关键点检测器 但它们不够快且不够准确 因此 我决定首先使用 Canny 或更快的边缘检测算法 检测模板和搜索区域中的边缘 然后匹配边缘以查
  • Windows 和 Linux 上的线程

    我在互联网上看到过在 Windows 上使用 C 制作多线程应用程序的教程 以及在 Linux 上执行相同操作的其他教程 但不能同时用于两者 是否存在即使在 Linux 或 Windows 上编译也能工作的函数 您需要使用一个包含两者的实现

随机推荐