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