我使用Visual Studio创建了一个空解决方案,并添加了一个DLL项目:SchoolSQLite。为了验证是否正常工作,我还添加了一个控制台应用程序,使用实体框架访问数据库。
为了完整起见,我添加了一些单元测试。这超出了本回答的范围。
在DLL项目中,我使用“引用-管理NUGET包”来搜索“System.Data.SQLite”。这是添加实体框架和SQLite所需代码的版本。如有需要,请更新到最新版本。
添加描述问题的类:Address、School、Teacher、Student、SchoolDbContext。
现在是我认为最困难的部分:控制台应用程序中App.Config文件中的连接字符串。
要使其工作,我需要在App.Config中加入以下部分:
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<configSections>
<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();
}
}
}
有几点值得一提:
- Table Address拥有额外的索引,所以使用PostalCode + House Number (+ extension)来搜索地址会更快。“你的邮政编码是什么?”“嗯,它是5473TB,6号房子”。索引将立即显示完整地址。
- 虽然SchoolDbcontext没有提及junction table 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);
}
对于熟悉实体框架的人来说,对这些表进行建模是相当简单的。
Address;一个简单表的例子
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();
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();
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();
students.HasRequired(student => student.School)
.WithMany(school => school.Students)
.HasForeignKey(student => student.SchoolId);
}
注意:默认情况下,如果删除一个学校,这将级联下去:所有它的老师和学生都将被删除。
只剩下一张表关系:连接表。如果我想的话,我也可以在这里定义学校与教师以及学校与学生之间的一对多关系。当我定义教师和学生时已经这样做了。所以它们在这里是不必要的。我留下了代码,作为示例,如果你想把它们放在这里。
private void OnModelCreatingTableRelations(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Teacher>()
.HasMany(teacher => teacher.Students)
.WithMany(student => student.Teachers)
.Map(manyToMany =>
{
manyToMany.ToTable("TeachersStudents");
manyToMany.MapLeftKey("TeacherId");
manyToMany.MapRightKey("StudentId");
});
}
这里解释了多对多映射。
现在我们几乎完成了。我们要做的只是确保数据库不会被删除和重新创建。通常是在以下情况下完成:
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) { }
}
有时我会看到人们在构造函数中设置初始化器:
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();
if (System.Diagnostics.Debugger.IsAttached)
{
Console.WriteLine();
Console.WriteLine("Fin");
Console.ReadKey();
}
}
Program()
{
SchoolDbContext.SetInitializeNoCreate();
}
现在就开始愉快的部分:添加一个学校,添加一个老师和一个学生,并让老师教授这个学生。
void Run()
{
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);
fetchedTeacher.Students.Add(fetchedStudent);
fetchedStudents.Teachers.Add(fetchedTeacher);
dbContext.SaveChanges();
}
我还尝试了从学校中删除老师,并发现这并没有伤害到学生。另外,如果一个学生离开学校,老师仍然会继续教学。最后:如果我删除一所学校,所有的学生和老师都将被删除。
现在我给你展示了:
- 像地址和学校这样的简单表格;
- 具有一对多关系的表格:教师和学生;
- 具有多对多关系的表格:学生教师。
还有一种关系我没有展示:自引用关系:指向同一张表中另一个对象的外键。我无法在学校数据库中想出一个好的例子。如果有人有好的想法,请编辑此答案并添加自引用表格。
希望这对你有用。
SaveChanges()
之后,*.db文件仍然没有被写入,但是db-*文件却被更新了。你如何在不退出进程的情况下更新主数据库文件呢? - Taylor Kendallthis.Database.ExecuteSqlRaw("PRAGMA journal_mode = 'delete';");
,它将会将数据写入数据库而不是临时文件。你可以在这里找到更多信息。尽管这种方法不适用于高性能应用程序,但在我的情况下正是我所需要的。 - Taylor Kendall