在前面幾篇介紹了Entity Framework 實體框架的形成過程,整體框架主要是基于Database First的方式構建,也就是利用EDMX文件的映射關系,構建表與表之間的關系,這種模式彈性好,也可以利用圖形化的設計器來設計表之間的關系,是開發項目較多采用的模式,不過問題還是這個XML太過復雜,因此有時候也想利用Code First模式構建整個框架。本文主要介紹利用Code First 來構建整個框架的過程以及碰到的問題探討。
1、基于SqlServer的Code First模式
為了快速了解Code First的工作模式,我們先以微軟自身的SQLServer數據庫進行開發測試,我們還是按照常規的模式先構建一個標準關系的數據庫,如下所示。
這個表包含了幾個經典的關系,一個是自引用關系的Role表,一個是User和Role表的多對多關系,一個是User和UserDetail之間的引用關系。
一般情況下,能處理好這幾種關系,基本上就能滿足大多數項目上的要求了。這幾個表的數據庫腳本如下所示。
create table dbo.Role (
ID nvarchar(50) not null,
Name nvarchar(50) null,
ParentID nvarchar(50) null,
constraint PK_ROLE primary key (ID)
)
go
create table dbo."User" (
ID nvarchar(50) not null,
Account nvarchar(50) null,
Password nvarchar(50) null,
constraint PK_USER primary key (ID)
)
go
create table dbo.UserDetail (
ID nvarchar(50) not null,
User_ID nvarchar(50) null,
Name nvarchar(50) null,
Sex int null,
Birthdate datetime null,
Height decimal null,
Note ntext null,
constraint PK_USERDETAIL primary key (ID)
)
go
create table dbo.UserRole (
User_ID nvarchar(50) not null,
Role_ID nvarchar(50) not null,
constraint PK_USERROLE primary key (User_ID, Role_ID)
)
go
alter table dbo.Role
add constraint FK_ROLE_REFERENCE_ROLE foreign key (ParentID)
references dbo.Role (ID)
go
alter table dbo.UserDetail
add constraint FK_USERDETA_REFERENCE_USER foreign key (User_ID)
references dbo."User" (ID)
go
alter table dbo.UserRole
add constraint FK_USERROLE_REFERENCE_ROLE foreign key (Role_ID)
references dbo.Role (ID)
go
alter table dbo.UserRole
add constraint FK_USERROLE_REFERENCE_USER foreign key (User_ID)
references dbo."User" (ID)
go
我們采用剛才介紹的Code Frist方式來構建實體框架,如下面幾個步驟所示。
1)選擇來自數據庫的Code First方式。
2)選擇指定的數據庫連接,并選擇對應的數據庫表,如下所示(包括中間表UserRole)。
生成項目后,項目工程會增加幾個類,包括Role實體類,User實體類,UserDetail實體類(沒有中間表UserRole的實體類),還有一個是包含這些實體類的數據庫上下文關系,它們的表之間的關系,是通過代碼指定的,沒有了EDMX文件了。
幾個類文件的代碼如下所示,其中實體類在類定義的頭部,增加了[Table("Role")]的說明,表明了這個實體類和數據庫表之間的關系。
[Table("Role")]
public partial class Role
{
public Role()
{
Children = new HashSet<Role>();
Users = new HashSet<User>();
}
[StringLength(50)]
public string ID { get; set; }
[StringLength(50)]
public string Name { get; set; }
[StringLength(50)]
public string ParentID { get; set; }
public virtual ICollection<Role> Children { get; set; }
public virtual Role Parent { get; set; }
public virtual ICollection<User> Users { get; set; }
}
其他類如下所示。
[Table("User")]
public partial class User
{
public User()
{
UserDetails = new HashSet<UserDetail>();
Roles = new HashSet<Role>();
}
[StringLength(50)]
public string ID { get; set; }
[StringLength(50)]
public string Account { get; set; }
[StringLength(50)]
public string Password { get; set; }
public virtual ICollection<UserDetail> UserDetails { get; set; }
public virtual ICollection<Role> Roles { get; set; }
}
[Table("UserDetail")]
public partial class UserDetail
{
[StringLength(50)]
public string ID { get; set; }
[StringLength(50)]
public string User_ID { get; set; }
[StringLength(50)]
public string Name { get; set; }
public int? Sex { get; set; }
public DateTime? Birthdate { get; set; }
public decimal? Height { get; set; }
[Column(TypeName = "ntext")]
public string Note { get; set; }
public virtual User User { get; set; }
}
還有一個就是生成的數據庫上下文的類。
public partial class DbEntities : DbContext
{
public DbEntities() : base("name=Model1")
{
}
public virtual DbSet<Role> Roles { get; set; }
public virtual DbSet<User> Users { get; set; }
public virtual DbSet<UserDetail> UserDetails { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Role>()
.HasMany(e => e.Children)
.WithOptional(e => e.Parent)
.HasForeignKey(e => e.ParentID);
modelBuilder.Entity<Role>()
.HasMany(e => e.Users)
.WithMany(e => e.Roles)
.Map(m => m.ToTable("UserRole"));
modelBuilder.Entity<User>()
.HasMany(e => e.UserDetails)
.WithOptional(e => e.User)
.HasForeignKey(e => e.User_ID);
modelBuilder.Entity<UserDetail>()
.Property(e => e.Height)
.HasPrecision(18, 0);
}
}
上面這個數據庫上下文的操作類,通過在OnModelCreating函數里面使用代碼方式指定了幾個表之間的關系,代替了EDMX文件的描述。
這樣好像看起來比EDMX文件簡單了很多,感覺很開心,一切就那么順利。
如果我們使用這個數據庫上下文進行數據庫的插入,也是很順利的執行,并包含了的多個表之間的關系處理,代碼如下所示。
private void NormalTest()
{
DbEntities db = new DbEntities();
Role role = new Role() { ID = Guid.NewGuid().ToString(), Name = "test33" };
User user = new User() { ID = Guid.NewGuid().ToString(), Account = "test33", Password = "test33" };
UserDetail detail = new UserDetail() { ID = Guid.NewGuid().ToString(), Name = "userName33", Sex = 1, Note = "測試內容33", Height = 175 };
user.UserDetails.Add(detail);
role.Users.Add(user);
db.Roles.Add(role);
db.SaveChanges();
List<Role> list = db.Roles.ToList();
}
我們發現,通過上面代碼的操作,幾個表都寫入了數據,已經包含了他們之間的引用關系了。
2、基于泛型的倉儲模式實體框架的提煉
為了更好對不同數據庫的封裝,我引入了前面介紹的基于泛型的倉儲模式實體框架的結構,希望后面能夠兼容多種數據庫的支持,最終構建代碼的分層結構如下所示。
使用這種框架的分層,相當于為各個數據庫訪問提供了統一標準的通用接口,為我們利用各種強大的基類快速實現各種功能提供了很好的保障。使用這種分層的框架代碼如下所示。
private void FrameworkTest()
{
Role role = new Role() { ID = Guid.NewGuid().ToString(), Name = "test33" };
User user = new User() { ID = Guid.NewGuid().ToString(), Account = "test33", Password = "test33" };
UserDetail detail = new UserDetail() { ID = Guid.NewGuid().ToString(), Name = "userName33", Sex = 1, Note = "測試內容33", Height = 175 };
user.UserDetails.Add(detail);
role.Users.Add(user);
IFactory.Instance<IRoleBLL>().Insert(role);
ICollection<Role> list = IFactory.Instance<IRoleBLL>().GetAll();
}
我們發現,這部分代碼執行的效果和純粹使用自動生成的數據庫上下文DbEntities 來操作數據庫一樣,能夠寫入各個表的數據,并添加了相關的應用關系。
滿以為這樣也可以很容易擴展到Oracle數據庫上,但使用SQLServer數據庫生成的實體類,在Oracle數據庫訪問的時候,發現它生成的實體類名稱全部是大寫,一旦修改為Camel駝峰格式的字段,就會出現找不到對應表字段的錯誤。
尋找了很多解決方案,依舊無法有效避免這個問題,因為Oracle本身的表或者字段名稱是大小寫敏感的,關于Oracle這個問題,先關注后續解決吧,不過對于如果不考慮支持多種數據庫的話,基于SQLServer數據庫的Code First構建框架真的還是比較方便,我們不用維護那個比較麻煩的EDMX文件,只需要在代碼函數里面動態添加幾個表之間的關系即可。