在实时项目中管理 Entity Framework Code First





5.00/5 (7投票s)
如何在实时项目中管理 Entity Framework Code First
引言
Entity framework 是一个非常棒的框架。而 Code First 的方法也越来越受欢迎。但如果我们不恰当地使用 Entity Framework Code First,它可能会变成一场灾难。所以在这篇文章中,我将尝试说明我在使用 Entity Framework 时遇到过哪些问题,以及如何以一种安全的方式使用这个框架。
背景
在开始之前,我假设我们对 Entity Framework Code First 有一些了解。至少,我们知道如何在 Entity Framework 中创建表、关系以及使用种子数据。
如果我们不了解,开始也不会太难
- http://msdn.microsoft.com/en-us/data/jj193542.aspx
- https://codeproject.org.cn/Articles/796540/Relationship-in-Entity-Framework-Using-Code-First
- https://codeproject.org.cn/Articles/396822/Basic-Handling-and-Tips-of-Database-Relationships
或者只是谷歌搜索一段时间,也就可以了。
那么,Entity Framework Code First 需要什么?
- 模型:将它们转换为表...
- 模型的配置:创建表之间的关系,以及格式化表列结构...
- 上下文:表示数据库、连接、调用模型配置、数据库初始化、数据库加载配置...
- 初始化程序:初始化数据库上下文,使用种子...
- 迁移:如果模型发生变化,如何处理旧数据或结构...
你提到的那些问题是什么?
问题开始于我们误用了以下部分:
- 数据库上下文的初始化
- 处理迁移
让我们来解释这些部分。
1. 数据库上下文的初始化
要初始化数据库上下文,我们可以使用 IDatabaseInitializers
类,例如:
CreateDatabaseIfNotExists
– 首先创建数据库,如果应用程序没有可用的数据库,由上下文和连接字符串指定(适用于实时项目)
但我的主机提供商不允许我这样做。它要求我手动创建数据库,然后再使用。我该怎么办?
更新的需求迫使你向表中添加一个新字段。你做到了,并提供了更新的上下文。现在如果我们运行应用程序,就会抛出一个异常,指向当前上下文和现有数据库不匹配。我该怎么办?
DropCreateDatabaseIfModelChanges
– 如果数据库与更新的上下文不匹配,则删除旧数据库,并创建一个新的数据库,因为上下文指向它(适用于开发)
更新的需求迫使你向表中添加一个新字段。你做到了,并提供了更新的上下文。现在如果我们运行应用程序,旧数据库将被删除。所有数据都会丢失。我该怎么办?
DropCreateDatabaseAlways
- 每次初始化上下文时,都会删除旧数据库并创建一个新数据库。(适用于测试,如果数据库容量较小。)
我们将无法存储足够的数据来工作。如果应用程序是实时操作的,我们绝不应该使用它。我该怎么办?
所以有人说,为什么不为实时项目使用 CreateDatabaseIfNotExists
,为开发使用 DropCreateDatabaseIfModelChanges
,并使用 SQL 脚本来创建数据库?当我们上线时,将 DropCreateDatabaseIfModelChanges
替换为 CreateDatabaseIfNotExists
,问题就解决了。如果我们某天提供更新,但忘记这样做怎么办?
2. 处理迁移
迁移确实是使用 Entity Framework Code First 的好方法。它使数据库能够保存信息,例如进行了多少次迁移以及何时进行的。此外,在迁移文件中,我们可以找到对迁移所做的更改,甚至可以在迁移之间来回跳转。在下一篇文章中,我们将讨论它。
但问题是,配置起来有点棘手。如果你没有多少时间进行研究,迁移可能会迫使你放弃使用 Entity Framework Code First 的选项。因为有时,应用程序会随着时间的推移而变得庞大,这迫使我们更改数据库数据和表关系等。
人们不喜欢为大型项目冒险。他们希望像以前使用 SQL 脚本那样处理事情。
解决方案是什么?
这是关键:Entity Framework 也可以用来将上下文映射到现有数据库。那么我们将做什么?
- 使用一个用于数据库的生成器上下文,它使用
DropCreateDatabaseIfModelChanges
。该上下文的访问仅限于特定项目,而不包括解决方案中的其他项目。或者可以被其他项目的代码继承(使用internal
/protected internal
作为访问修饰符)。这将用于重建数据库。 - 使用一个用于数据库的映射器上下文,它仅映射到数据库实体。这主要将由应用程序用来影响表。
- 从
Base
上下文继承通用内容,用于字段表和配置文件
映射器上下文
映射器上下文很简单,它继承了基础 Context
类。
我们将使用此上下文进行任何 CRUD 操作。
/*Context is only mapped to bd objects*/
public class UmsContext : Context
{
public UmsContext() : base("DbUms") //connection string name to base
{
/*Configuration for the context*/
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
}
}
生成器上下文
生成器上下文也继承自基础 Context
类,其初始化程序如下:
我们将仅使用此类来重新创建数据库
/*Context drops the old database and creates new one*/
internal class UmsBuildContext : Context
{
static UmsBuildContext()
{
//check connection string
string connectionString = ConfigurationManager.ConnectionStrings["DbUms"].ConnectionString;
Database.SetInitializer(new UmsBuildContextInitializer());
}
public UmsBuildContext()
: base("DbUms") //connection string name to base
{
}
}
初始化程序也可以像这样包含种子:
internal class UmsBuildContextInitializer
: DropCreateDatabaseIfModelChanges<UmsBuildContext>
{
protected override void Seed(UmsBuildContext context)
{
/*department seeds*/
List<Department> departments = new List<Department>()
{
new Department(){Name = "Math"},
new Department(){Name = "Physics"},
new Department(){Name = "Chemistry"},
};
context.Departments.AddRange(departments);
context.SaveChanges();
/*students seeds*/
List<Student> students = new List<Student>()
{
new Student(){ Name = "Nitol", DepartmentId = 1},
new Student(){ Name = "Tasnim", DepartmentId = 2},
new Student(){ Name = "Ripon", DepartmentId = 3}
};
context.Students.AddRange(students);
context.SaveChanges();
}
}
正如我们所说,我们想限制生成器上下文。这就是为什么我们使用 internal
作为 UmsBuildContext
的访问修饰符。但为了在测试项目中使用此上下文来重新创建数据库以进行测试/故意操作,也需要这样做。因此,为了访问其部分功能,我们在 public
类中使用此类,例如:
public class DbBuilder
{
public void Build()
{
var departments = new UmsBuildContext().Departments.ToList();
}
}
基类上下文
主要的基类上下文主要保存所有表配置和数据库实体引用,而上述上下文继承自它。
public class Context : DbContext
{
/*all tbls*/
public DbSet<Department> Departments { get; set; }
public DbSet<Student> Students { get; set; }
/*connection string provided by the sub class*/
protected Context(string connectionString)
: base(nameOrConnectionString: connectionString)
{
}
/*get tbl by tbl type*/
public DbSet<TSource> Table<TSource>() where TSource : class
{
return Set<TSource>();
}
/*all tbls configurations*/
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new DepartmentConfiguration());
modelBuilder.Configurations.Add(new StudentConfiguration());
}
}
Using the Code
因此,如果数据库结构发生任何更改,并且我们想重建整个数据库,我们只需调用:
/*Rebuild the db*/
//use only when we want to rebuild the database with seed data during production.
//avoid any use when the product is already been delivered to client.(best use in test code)
new DbBuilder().Build();
现在,让我们测试映射器上下文
/*see the datas*/
var db = new UmsContext();
var departments = db.Departments.ToList();
var departments2 = db.Table<Department>().ToList();
/*add new student with new department*/
var student = new Student()
{
Name = "Rintu",
Department = new Department() {Name = "CSE"}
};
db.Students.Add(student);
db.SaveChanges();
在这里,数据库中将创建新的 student
和 department
行,这意味着映射器工作正常。
更改数据库结构
为此,我们只需要:
- 修改模型类
- 在本地机器上重建数据库
- 将重新创建的数据库与旧数据库进行比较,以找到将旧数据库更新到当前版本的必需的 alter/update SQL 查询,并将这些查询应用于旧数据库。
- 用新创建的 DLL 替换旧的 DLL
限制
- 是的,可能会有一些我还没有遇到的错误,或者可能造成一些道德上的困扰。所以如果你发现任何问题,请告诉我。
- 我并非不鼓励任何人使用迁移。如果你有时间,请做一些研发。
- 重建系统可能看起来有点棘手,但我正在努力使其更合乎逻辑。
public class DbBuilder
{
public void Build()
{
var departments = new UmsBuildContext().Departments.ToList();
}
}
或者看看这个:http://stackoverflow.com/questions/4911329/ef-codefirst-ctp5-manually-drop-and-create-db
在附件中找到适用于框架 4 项目的 Visual Studio 2010 解决方案。如果你运行该项目,它将在本地机器上创建一个名为 "UMS
" 的数据库,其中包含 3 个表。你可以通过 app.config 进行更改。