SocialClub:一个使用 C#.NET、Entity Framework 5 和 LocalDb 的示例应用程序
使用 Entity Framework 代码优先和 LocalDB 进行基本 CRUD 操作的简介。
目录
引言
本文档介绍如何使用 Entity Framework (EF) Code-First 开发方法和 LocalDB (SQL Express) 执行基本的 CRUD 操作。本文提供的 SocialClub 示例应用程序基于我之前的文章[^]。演示层 (UI) 保持不变,但数据层 (数据访问和业务逻辑) 已更改,以演示 Entity Framework Code-First。
软件环境
- 开发环境:Visual Studio.NET 2012
- 框架:.NET Framework 4.5
- 用户界面:WinForms
- 编程语言:C#.NET
- 数据访问:Entity Framework 5
- 数据库:LocalDB (Sql Server)
先决条件
运行此示例应用程序需要 Visual Studio.NET 2012,并且入门需要具备 .NET Framework、C#.NET 和 Sql Server 的基础知识。
应用程序结构
- 演示层:演示层包含与应用程序用户交互所需的组件。
- 数据访问层:数据访问层提供了一个简单的 API 来访问和操作数据。该组件通常提供用于对特定业务实体执行创建、读取、更新和删除 (CRUD) 操作的方法。数据访问类组件的逻辑分离如下图所示(中间层),将在后续部分进行讨论。
- 数据库:数据库层包含具有表结构和数据的数据库。
LocalDB
LocalDB 是 SQL Express 的一个新版本,随 .NET Framework 4.5 一起提供,专为开发人员设计。如果您已安装 Visual Studio 2012,那么您已经拥有 LocalDB。在 Visual Studio 2012 中,转到“视图”菜单 -> 选择“SQL Server 对象资源管理器”。右键单击“SQL Server”并选择“添加 SQL Server”,这将提示输入 SQL Server 名称。将服务器名称提供为 (localdb)\v11.0 并单击“连接”。您将连接到 LocalDb。它提供了一个类似于 SQL Express 的运行 T-SQL 的编程界面。您可以在这个MSDN 博客[^] 中找到有关 LocalDb 的更多信息。
下面显示了 Visual Studio 2012 中 LocalDb 的示例屏幕截图。
数据访问层
Code First
Code-First 是一种数据建模方法。此方法的第一步是创建表示域模型的 POCO(Plain-Old-CLR-Object)类(业务实体),然后从这些 POCO 类创建数据库。
业务实体 (POCO)
Social Club 是一个管理俱乐部成员详细信息的小型应用程序。我们只需要一个具有必要属性的 POCO 类来表示会员详细信息,这将允许创建和存储“俱乐部会员”信息。
public class ClubMember
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime DateOfBirth { get; set; }
public int Occupation { get; set; }
public Nullable<decimal> Salary { get; set; }
public int MaritalStatus { get; set; }
public int HealthStatus { get; set; }
public Nullable<int> NumberOfChildren { get; set; }
}
DbContext
第二步是定义一个派生自 DbContext 类的类。这个 Context 类暴露了实体集合。DbContext 和 DbSet 是 Entity Framework 库中用于与数据交互的两个主要类型。DbContext 类管理与数据库的连接。它会根据需要打开和关闭连接。
public class SocialClubDbContext : DbContext
{
public SocialClubDbContext()
: base("SocialClub.DbConnection")
{
Database.SetInitializer<socialclubdbcontext>(new SocialClubInitializer());
Configuration.ProxyCreationEnabled = false;
}
public DbSet<clubmember> ClubMembers { get; set; }
}
数据库初始化
在上面的代码中,您可以看到在构造函数中调用了一个数据库初始化器来初始化数据库。在使用 Code-First 方法时,Entity Framework 提供了不同的数据库初始化选项。以下是可用的选项:
- CreateDatabaseIfNotExists:如果数据库不存在,此初始化器将创建数据库。
- DropCreateDatabaseIfModelChanges:如果模型类已更改,此初始化器将删除/删除现有数据库并创建新数据库。
- DropCreateDatabaseAlways:无论模型类是否已更改,此初始化器都会在每次运行应用程序时删除/删除现有数据库。
- 自定义 DB 初始化器:我们可以创建自己的初始化器类,该类派生自上述选项之一,并重写 seed 方法以执行自定义初始化。
下面显示的代码是示例应用程序的自定义 DB 初始化器类,它派生自 CreateDatabaseIfNotExists,并且该类重写了 seed 方法来初始化 DB。
public class SocialClubInitializer : CreateDatabaseIfNotExists<socialclubdbcontext>
{
protected override void Seed(SocialClubDbContext context)
{
var clubMembers = new List<clubmember>{
new ClubMember {
Name = "Pete Darson",
Occupation = (int)Occupation.Doctor,
HealthStatus = (int)HealthStatus.Good,
MaritalStatus = (int)MaritalStatus.Married,
NumberOfChildren = 2,
DateOfBirth = new DateTime(1982,07,12),
Salary = 5500
},
new ClubMember {
Name = "Mat Pearson",
Occupation = (int)Occupation.Engineer,
HealthStatus = (int)HealthStatus.Excellent,
MaritalStatus = (int)MaritalStatus.Single,
NumberOfChildren = 0,
DateOfBirth = new DateTime(1980,05,21),
Salary = 3500
}
};
clubMembers.ForEach(category => context.ClubMembers.Add(category));
}
}
连接字符串
项目已添加 App.config 文件,该文件包含数据库连接字符串。DbContext 类在与数据库交互时会尝试从配置文件中查找连接字符串。从 DbContext 派生上下文类时,我们可以将连接名称作为参数传递给基构造函数,也可以使用无参数构造函数。当使用无参数构造函数时,连接字符串的名称应与 DbContext 默认使用的上下文类的名称匹配。如果名称不同,则需要在构造函数中传递连接名称,以告知 DbContext 使用给定的连接。
下面提供的连接字符串已添加到 app.config 文件中,该文件指定 LocalDb 作为数据源。如果您希望使用 SQL Server,则可以根据需要修改 app.config 文件中的连接字符串。
<connectionStrings>
<add name="SocialClub.DbConnection"
providerName="System.Data.SqlClient"
connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=SocialClub;Integrated Security=true" />
</connectionStrings>
参考上面派生的 DbContext 类,您会注意到默认构造函数使用“name={connectionstringname}”语法调用基构造函数。这表明具有指定名称的连接字符串应从配置文件加载,以用于此上下文。
扩展方法
已向项目添加了一个扩展方法,用于将“IList”集合转换为 DataTable,将记录转换为 DataRow。这是为了弥合数据层和演示层之间的差距,因为我们正在使用上一篇文章中提供的相同示例应用程序。UI 绑定到 DataTable 和 DataRow,因此下面的扩展方法有助于将数据转换为所需类型。
public static DataTable ToDataTable<t>(this IList<t> data)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
foreach (PropertyDescriptor prop in properties)
{
table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType)
?? prop.PropertyType);
}
foreach (T item in data)
{
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in properties)
{
row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
}
table.Rows.Add(row);
}
return table;
}
public static DataRow ToDataRow<t>(this T data)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
DataTable table = new DataTable();
foreach (PropertyDescriptor prop in properties)
{
table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType)
?? prop.PropertyType);
}
DataRow row = table.NewRow();
foreach (PropertyDescriptor prop in properties)
{
row[prop.Name] = prop.GetValue(data) ?? DBNull.Value;
}
table.Rows.Add(row);
return table.Rows[0];
}
使用 LINQ 访问和操作数据
数据层中的服务类实现了接口,用于查询 Dbcontext 以访问和更新数据。准备好上下文类后,我们就可以查询 Dbcontext 来创建、检索、更新和删除记录。要访问数据,我们需要访问上下文对象的 DbSet 属性。访问上下文对象上的 DbSet 属性将返回指定类型的所有实体。让我们为俱乐部成员服务创建一个接口,并开始实现其中的一些接口,看看如何使用 LINQ 访问和操作数据。
public interface IClubMemberService
{
DataRow GetById(int Id);
DataTable GetAll();
DataTable Search(int occupation, int maritalStatus, string operand);
bool Create(ClubMember clubMember);
bool Update(ClubMember clubMember);
bool Delete(int id);
}
创建新记录
下面的代码显示了如何创建新的 ClubMember 并将其添加到 socialClub 数据库。创建一个新的 ClubMember 模型并设置其属性,然后将其添加到 SocialClubDbContext 上下文对象的 ClubMembers 属性中,然后调用 SaveChanges 方法将更改保存到数据库。
public bool Create(ClubMember clubMember)
{
using (var context = new SocialClubDbContext())
{
context.ClubMembers.Add(clubMember);
return context.SaveChanges() > 0;
}
}
检索记录
要检索单个记录,我们需要使用带有“where”子句的 ClubMember DbSet 属性进行查询,该子句根据条件过滤值的序列。在下面的示例中,“where”子句将只返回一个元素,因此我们调用 SingleOrDefault() 方法来获取序列的唯一元素或在序列为空时获取默认值。
public DataRow GetById(int id)
{
using (var context = new SocialClubDbContext())
{
ClubMember membership = context.ClubMembers
.Where(i => i.Id == id)
.SingleOrDefault();
return membership.ToDataRow<clubmember>();
}
}
更新记录
要更新记录,请将更新后的记录附加到相应的 DbSet 属性,并将实体状态设置为 EntityState.Modified,最后,调用 SaveChanges() 方法将更改保存到数据库。
public bool Update(ClubMember clubMember)
{
using (var context = new SocialClubDbContext())
{
context.ClubMembers.Attach(clubMember);
context.Entry(clubMember).State = EntityState.Modified;
return context.SaveChanges() > 0;
}
}
删除记录
有多种方法可以删除记录。例如,您可以附加记录并将实体状态设置为 EnityState.Deleted,然后将更改保存到数据库,这与我们在上面的“更新记录”中所做的类似。另一种方法(请参阅下面的示例代码)是查找记录,然后将记录传递给 DbSet 属性的“Remove”方法,并调用 SaveChanges()。
public bool Delete(int id)
{
using (var context = new SocialClubDbContext())
{
var clubMember = context.ClubMembers.Find(id);
context.ClubMembers.Remove(clubMember);
return context.SaveChanges() > 0;
}
}