所以我使用 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。
我没有展示一个关系:自引用:同一个表中另一个对象的外键。我无法在学校数据库中找到一个很好的例子。如果有人有好主意,请编辑此答案并添加自引用表。
希望这对你有用。