EntityFramework基础框架搭建
近期学习了关于EntityFramework的基础概念知识,今天开始进行Sample设计及测试,从而深入了解关于EF的使用及知识总结。
首先进行Sample的环境配置,分别如下图所示:
以上两图即此TestSample所搭建的基础环境,右图为数据实体类部分,左图为数据属性约定、数据初始化及配置文件。此Sample主要为搭建环境而非系统内置方法测试,因此实体类的定义非常简单。
1 public class Role 2 { 3 public int ID { get; set; } 4 5 public string Name { get; set; } 6 7 public IList<User> Users { get; set; } 8 }
public class User { public int ID { get; set; } public string Name { get; set; } public IList<Role> Roles { get; set; } }
通过App.config文件中的配置,分别设定数据库连接字符串(从而设定数据库位置)、所使用的EF版本号。如下所示:
<?xml version="1.0" encoding="utf-8"?> <configuration> <configSections> //此处对引用的EF版本信息等进行配置 <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=6.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" /> </configSections> //此处设定连接字符串,即对数据库位置及名称进行配置 <connectionStrings> <add name="CodeOnly" connectionString="Data Source=.;Initial Catalog=PersonalInfoRecord;Integrated Security=True" providerName="System.Data.SqlClient" /> </connectionStrings> <entityFramework> //此处设定EntityFramework的默认连接工厂,若未进行连接字符串设定,则需要建立数据库时,将由连接工厂按默认设定进行数据库生成 <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework"> <parameters> <parameter value="v11.0" /> </parameters> </defaultConnectionFactory> //此处设定SqlServer服务提供者 <providers> <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /> </providers> </entityFramework> </configuration>
DbContextHelper类内是自定义的为DbContext类提供的扩展方法,本Sample中使用到的方法如下,功能是根据传入的连接字符串,根据EF的连接工厂生成数据连接:
internal static void InitializeConnection(this DbContext context, string connectionString) { if ((context == null) || (context.Database == null) || (context.Database.Connection == null)) throw new ArgumentException("数据库访问组件未正确初始化。", "context"); //获取程序使用的连接工厂 var factory = DbProviderServices.GetProviderFactory(context.Database.Connection); var srcBuilder = factory.CreateConnectionStringBuilder(); srcBuilder.ConnectionString = context.Database.Connection.ConnectionString; //引入连接字符串 var toBuilder = factory.CreateConnectionStringBuilder(); toBuilder.ConnectionString = connectionString; //使用连接工厂默认配置,进行数据库连接初始化 foreach (string key in srcBuilder.Keys) { if (srcBuilder.ShouldSerialize(key) && !toBuilder.ShouldSerialize(key)) { toBuilder.Add(key, srcBuilder[key]); } } context.Database.Connection.ConnectionString = toBuilder.ConnectionString; }
EntityContext类继承自DbContext类,在其中进行Context的初始化相关方法的定义:
public class EntityContext : DbContext { public const string ConnectionName = "CodeOnly"; static EntityContext() { Database.SetInitializer<EntityContext>(null); } public EntityContext() { this.InitializeConnection(GetConnectionStringFromConfiguration()); } //也可直接使用语句,但容错效果不好 //public EntityContext() : base("CodeOnly") { } public DbSet<User> User_Table { get; set; } public DbSet<Role> Role_Table { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); //移除级联删除约定 modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>(); //将自定义的PropertyConfigurationConvention类中的约定进行加载 modelBuilder.Conventions.Add<PropertyConfigurationConvention>(); //从程序集中检索所有的Configuration,并进行加载,在本Sample中,即检索到Configuration文件夹中的配置设定文件,并进行加载 modelBuilder.Configurations.AddFromAssembly(typeof(EntityContext).Assembly); } private string GetConnectionStringFromConfiguration() { //获取当前程序默认配置文件中的ConnectionStrings结点中的“CodeOnly”行 //若不为空则返回该行中的"ConnectionString"属性值 var css = ConfigurationManager.ConnectionStrings[ConnectionName]; return (css == null ? null : css.ConnectionString); } }
再来看Configuration文件夹中定义的对两个类的约定配置,以Role实体的约定为例,User实体的约定类似:
internal class RoleConfiguration : EntityTypeConfiguration<Role> { public RoleConfiguration() { //约定ID属性为自增长 this.Property(t => t.ID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); this.Property(t => t.Name); //约定ID属性作为此实体在数据表中的主键 this.HasKey(t => t.ID); } }
在UnitTest中,测试方法代码如下:
1 public void TestFirst() 2 3 { 4 5 using (var db = new EntityContext()) 6 7 { 8 9 //若数据库不存在,则根据EntityContext类中通过DbSet所声明的实体进行数据库创建 10 11 db.Database.CreateIfNotExists(); 12 13 14 15 //因为Role和User实体均将ID设为整型、主键、自增长,因此在进行Insert时无需进行设定 16 17 var role = new Role(); 18 19 role.Name = "Administrator"; 20 21 db.Role_Table.Add(role); 22 23 24 25 var user = new User(); 26 27 user.Name = "Apollo"; 28 29 user.Roles = new List<Role> { role }; 30 31 32 33 db.User_Table.Add(user); 34 35 36 37 //此Assert表示将所有对数据的操作进行对数据库的更新,并返回影响的行数,若大于0说明更新成功 38 39 Assert.IsTrue(db.SaveChanges() > 0); 40 41 } 42 43 }
在此案例中,得出以下几点注意事项:
1.仅在不存在此数据库时,CreateIfNotExists()方法才会发挥作用,创建新数据库,否则将会在执行SaveChanges方法时直接生成SQL操作,并在已有数据库基础上尝试执行,若实体结构与数据表结构不一致,则抛出异常。
2.EF默认生成的表名为Users,Roles,UserRoles,在UserRoles中默认将字段命名为User_ID,Role_ID,若希望使用自定义数据库中的字段名和表名,需要在Configuration文件中进行单独配置,否则若已有的数据表及字段命名与EF默认不一致,将在SaveChanges方法被调用时抛出异常。
3.SaveChanges方法是事务型方法,若其中出现异常,则整体回滚。