在前面幾篇介紹了Entity Framework 實(shí)體框架的形成過程征峦,整體框架主要是基于Database First的方式構(gòu)建既绩,也就是利用EDMX文件的映射關(guān)系,構(gòu)建表與表之間的關(guān)系,這種模式彈性好,也可以利用圖形化的設(shè)計(jì)器來設(shè)計(jì)表之間的關(guān)系接奈,是開發(fā)項(xiàng)目較多采用的模式,不過問題還是這個(gè)XML太過復(fù)雜通孽,因此有時(shí)候也想利用Code First模式構(gòu)建整個(gè)框架序宦。本文主要介紹利用Code First 來構(gòu)建整個(gè)框架的過程以及碰到的問題探討。
1背苦、基于SqlServer的Code First模式
為了快速了解Code First的工作模式挨厚,我們先以微軟自身的SQLServer數(shù)據(jù)庫進(jìn)行開發(fā)測(cè)試,我們還是按照常規(guī)的模式先構(gòu)建一個(gè)標(biāo)準(zhǔn)關(guān)系的數(shù)據(jù)庫糠惫,如下所示疫剃。
這個(gè)表包含了幾個(gè)經(jīng)典的關(guān)系,一個(gè)是自引用關(guān)系的Role表硼讽,一個(gè)是User和Role表的多對(duì)多關(guān)系巢价,一個(gè)是User和UserDetail之間的引用關(guān)系。
一般情況下固阁,能處理好這幾種關(guān)系壤躲,基本上就能滿足大多數(shù)項(xiàng)目上的要求了。這幾個(gè)表的數(shù)據(jù)庫腳本如下所示备燃。
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方式來構(gòu)建實(shí)體框架碉克,如下面幾個(gè)步驟所示。
1)選擇來自數(shù)據(jù)庫的Code First方式并齐。
2)選擇指定的數(shù)據(jù)庫連接漏麦,并選擇對(duì)應(yīng)的數(shù)據(jù)庫表,如下所示(包括中間表UserRole)况褪。
生成項(xiàng)目后撕贞,項(xiàng)目工程會(huì)增加幾個(gè)類,包括Role實(shí)體類测垛,User實(shí)體類捏膨,UserDetail實(shí)體類(沒有中間表UserRole的實(shí)體類),還有一個(gè)是包含這些實(shí)體類的數(shù)據(jù)庫上下文關(guān)系食侮,它們的表之間的關(guān)系号涯,是通過代碼指定的,沒有了EDMX文件了锯七。
幾個(gè)類文件的代碼如下所示链快,其中實(shí)體類在類定義的頭部,增加了[Table("Role")]的說明起胰,表明了這個(gè)實(shí)體類和數(shù)據(jù)庫表之間的關(guān)系久又。
[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; }
}
還有一個(gè)就是生成的數(shù)據(jù)庫上下文的類效五。
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);
}
}
上面這個(gè)數(shù)據(jù)庫上下文的操作類地消,通過在OnModelCreating函數(shù)里面使用代碼方式指定了幾個(gè)表之間的關(guān)系,代替了EDMX文件的描述畏妖。
這樣好像看起來比EDMX文件簡(jiǎn)單了很多脉执,感覺很開心,一切就那么順利戒劫。
如果我們使用這個(gè)數(shù)據(jù)庫上下文進(jìn)行數(shù)據(jù)庫的插入半夷,也是很順利的執(zhí)行,并包含了的多個(gè)表之間的關(guān)系處理迅细,代碼如下所示巫橄。
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 = "測(cè)試內(nèi)容33", Height = 175 };
user.UserDetails.Add(detail);
role.Users.Add(user);
db.Roles.Add(role);
db.SaveChanges();
List<Role> list = db.Roles.ToList();
}
我們發(fā)現(xiàn),通過上面代碼的操作茵典,幾個(gè)表都寫入了數(shù)據(jù)湘换,已經(jīng)包含了他們之間的引用關(guān)系了。
2统阿、基于泛型的倉儲(chǔ)模式實(shí)體框架的提煉
為了更好對(duì)不同數(shù)據(jù)庫的封裝彩倚,我引入了前面介紹的基于泛型的倉儲(chǔ)模式實(shí)體框架的結(jié)構(gòu),希望后面能夠兼容多種數(shù)據(jù)庫的支持扶平,最終構(gòu)建代碼的分層結(jié)構(gòu)如下所示帆离。
使用這種框架的分層,相當(dāng)于為各個(gè)數(shù)據(jù)庫訪問提供了統(tǒng)一標(biāo)準(zhǔn)的通用接口结澄,為我們利用各種強(qiáng)大的基類快速實(shí)現(xiàn)各種功能提供了很好的保障哥谷。使用這種分層的框架代碼如下所示。
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 = "測(cè)試內(nèi)容33", Height = 175 };
user.UserDetails.Add(detail);
role.Users.Add(user);
IFactory.Instance<IRoleBLL>().Insert(role);
ICollection<Role> list = IFactory.Instance<IRoleBLL>().GetAll();
}
我們發(fā)現(xiàn)麻献,這部分代碼執(zhí)行的效果和純粹使用自動(dòng)生成的數(shù)據(jù)庫上下文DbEntities 來操作數(shù)據(jù)庫一樣呼巷,能夠?qū)懭敫鱾€(gè)表的數(shù)據(jù),并添加了相關(guān)的應(yīng)用關(guān)系赎瑰。
滿以為這樣也可以很容易擴(kuò)展到Oracle數(shù)據(jù)庫上王悍,但使用SQLServer數(shù)據(jù)庫生成的實(shí)體類,在Oracle數(shù)據(jù)庫訪問的時(shí)候餐曼,發(fā)現(xiàn)它生成的實(shí)體類名稱全部是大寫压储,一旦修改為Camel駝峰格式的字段,就會(huì)出現(xiàn)找不到對(duì)應(yīng)表字段的錯(cuò)誤源譬。
尋找了很多解決方案集惋,依舊無法有效避免這個(gè)問題,因?yàn)镺racle本身的表或者字段名稱是大小寫敏感的踩娘,關(guān)于Oracle這個(gè)問題刮刑,先關(guān)注后續(xù)解決吧喉祭,不過對(duì)于如果不考慮支持多種數(shù)據(jù)庫的話,基于SQLServer數(shù)據(jù)庫的Code First構(gòu)建框架真的還是比較方便雷绢,我們不用維護(hù)那個(gè)比較麻煩的EDMX文件泛烙,只需要在代碼函數(shù)里面動(dòng)態(tài)添加幾個(gè)表之間的關(guān)系即可。