长期以来,使用实体框架 (EF) 一直是应用程序设计人员面临的挑战。这是因为大多数应用程序设计人员被实体框架的 ORM 性质分散了注意力,并且无法超越它。
Entity Framework 8.0 是一个经过深思熟虑的库,在过去十年中不断发展,具有许多有用的功能。工具箱中最新升级的功能是精心设计的数据库迁移。
在应用程序中使用实体框架之前,我们需要了解三个重要的策略:
1 — 延迟加载
2 — CRUD 操作
3 — 使用 TransactionScope
在生产就绪型应用程序中使用 Entity Framework 8.0 的第一步是使用_延迟加载_。
Entity Framework 中的延迟加载意味着我们只加载需要使用和操作的数据。
当然,Entity Framework 也提供了 Eager Loading,以一种快速而肮脏的方式对新应用程序_进行快速原型设计_,但它的使用确实会带来副作用。
事实是,Eager Loading 没有经过优化,无法用于高可用性生产就绪型应用程序。
在 Entity Framework 8.0 中,默认情况下启用延迟加载。
下面是我们如何在将 Entity Framework 8.0 的 DbContext 注入任何类构造函数之前对其进行初始化:
var myConnectionString = "/* some EF 8.0 connection string here */";
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(myConnectionString), ServiceLifetime.Scoped);
第二种策略是改进 CRUD 操作的使用。
以下是如何使用 Entity Framework 优化 CRUD 操作:
若要使用 Entity Framework 创建记录,请按照下列步骤操作:
1 — 创建实体对象。
2 — 将实体对象添加到 DbContext。
3 – 保存 DbContext 更改。
4 — 将实体对象与 DbContext 状态跟踪分离。
上面,前三个步骤非常标准。不过,这是值得回顾的第四步。
这里的想法是,一旦我们通过将实体对象添加到 DbContext 创建了新的数据库记录,我们必须禁用该对象的更改跟踪,否则实体框架将不会释放该对象并造成不利影响,例如无法在其他地方操作新记录。
以下是我们如何实现上述四个步骤来创建记录:
// Class: CreateUser
public string Username{ get; set; }
public int Execute(MyDbContext dataContext)
{
// Create Entity Object
var rec = new UserDb();
rec.Username = this.Username;
// Add Entity Object to DbContext
dataContext.Add(rec);
// Save Changes (This will create a new record in database)
dataContext.SaveChanges();
// Detach Entity Object to release/unload database record from DbContext
dataContext.Entry(rec).State = EntityState.Detached;
// return newly created record Id
return rec.UserId;
}
若要检索现有记录,请始终仅加载所需的记录。加载后,将它们映射到标准 POCO,并使用这些 POCO 来避免 DbContext 更改跟踪导致的记录锁定。这样,我们的 DbContext 将永远不会锁定数据库记录。
下面是如何使用 Entity Framework 检索记录并将其映射到 POCO:
// Class: GetUser
public int UserId { get; set; }
public UserData Execute(MyDbContext dataContext)
{
// Get Record
var data = (from a in dataContext.Users
where a.UserId == this.UserId
select a).FirstOrDefault();
// did we find the record?
if (data != null)
{
// Map Entity Object to POCO
var obj = this.Mapper.Map<UserData>(data);
// Return POCO to use
return obj;
}
return null;
}
在 Entity Framework 中更新记录应与创建和检索记录的方式相同。
以下是使用实体框架更新记录的步骤:
如前所述,实体框架跟踪对已修改的实体所做的更改。因此,一旦修改并保存了现有实体,我们就不应该重复使用它们。
下面是如何在 Entity Framework 8.0 中执行更新操作的:
// Class: SaveUser
public int UserId { get; set; }
public string Username{ get; set; }
public bool Execute(MyDbContext dataContext)
{
// Retrieve Record
var rec = (from a in dataContext.Users
where a.UserId == this.UserId
select a).FirstOrDefault();
if (rec != null)
{
// Update Record
rec.UserId = this.UserId;
rec.Username = this.Username;
// Save Record Changes
dataContext.SaveChanges();
return true;
}
return false;
}
如果您觉得需要使用更新的实体对象,则不要使用实体对象,而是将实体对象映射到 POCO,并在需要使用的位置引用该 POCO。
实体框架中的删除与更新非常相似。
以下是我们在 Entity Framework 中删除对象的方法:
// Class: DeleteUser
public int UserId { get; set; }
public bool Execute(MyDbContext dataContext)
{
// Retrieve Record
var rec = from a in dataContext.PhoneNumbers
where a.UserId == this.UserId
select a;
// Delete Record
dataContext.RemoveRange(rec);
// Save Changes
dataContext.SaveChanges();
return true;
}
第三个也是最重要的策略是将上述所有 CRUD 操作包装在 TransactionScope 中。
TransactionScope 是一个 .NET 类,一旦使用“_using”_块实例化,它就会将 using 块中包含的所有 CRUD 操作封装在一个事务下。它通过检测事务管理器的可用性来实现。
using (var transactionScope = new TransactionScope())
{
/*
Multiple SQL CRUD Operations where
*/
transactionScope.Complete();
}
在上面的示例中,创建一个 TransactionScope 实例足以将 using 区块中的所有 CRUD 操作包装在一个事务下。我们必须在 TransactionScope 上调用 Complete() 函数来提交更改,否则即使我们单独调用了每个 CRUD 操作的 DbContext.SaveChanges(),所有更改也会回滚。
对于实体框架,TransactionScope 必须与以下配置一起使用,否则我们可能会遇到过早中止的事务。
var transactionOptions =
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted,
Timeout = TimeSpan.FromSeconds(30)
};
var transactionFlow = TransactionScopeAsyncFlowOption.Enabled;
using(var scope = new TransactionScope(transactionOptions, transactionFlow))
{
/* Some CRUD Operations here */
}
上面是我们如何为 Entity Framework 8.0 配置 TransactionScope。
最后,我们可以总结上述知识,编写一个创建、检索、更新并在最后删除用户记录的 UnitOfWork:
var transactionOptions =
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted,
Timeout = TimeSpan.FromSeconds(30)
};
var transactionFlow = TransactionScopeAsyncFlowOption.Enabled;
using (scope = TransactionScope(transactionOptions, transactionFlow))
{
// Create a User
new CreateUser
{
Username = "testuser"
}.Execute(this.MyDbContext);
// Retrieve a User
var user = new GetUser
{
UserId = 1
}.Execute(this.MyDbContext);
// Update a User
new UpdateUser
{
UserId = 1,
UserName = "testuser-2"
}.Execute(this.MyDbContext);
// Delete a User
new DeleteUser
{
UserId = 1
}.Execute(this.MyDbContext);
// Commit Transaction
transactionScope.Complete();
}