通過一段時間對Ado.Net Entity Framework的使用,感受到它的便利同時,也受到了一些困擾。其中最大的困擾,是源自AEF的設計理念,並不完全符合Web開發,以及有並發訪問要求的系統。
最明顯的一點體現在緩存上。使用緩存是提高系統數據交互性能最簡捷有效的途徑,但如果想緩存Ado.Net Entity的話,並不是想像中那么簡單,比如:
var db =
new
DBEntities();
HttpContext.Current.Cache[
"product"
] = db.Products.ToList();
|
如果你這樣寫了,那恭喜你,如果你試圖在另一個上下文環境中修改它,你會發現緩存成了禁區。系統會告訴你"一個實體對象不能由多個 IEntityChangeTracker 實例引用"。
var db =
new
DBEntites();
var product = = (Cache[
"product"
]
as
List<Product>).Single(p=>p.PId==id);
db.Attach(product);
//試圖修改
|
但是原來的那個IEntityChangeTracker哪去了呢,顯然它現在不可能再被移除了,除非你保留填充緩存時ObjectContext的引用。一開始,對這個問題,我通過新建一個Entity進行更新,然后把緩存對應項替換掉。這帶來了大量的令人不爽累贅代碼,因為不同類型實體類創建是不同的。如果業務邏輯稍復雜些,更新數據庫再對緩存同步的過程會更痛苦。而且隨着修改次數增多,內存中會充斥大量未釋放的ObjectContext對象。
在AEF中,實體雖然有狀態,卻不能管理自己的狀態,必須依附於一個對象上下文中,對象上下文提供一個狀態管理器監視實體狀態。這和NHibernate等ORM框架大相徑庭,引起了國內外的論壇上許多抱怨。也許AEF團隊這樣設計有他們的考慮,可至少在Web開發中,確實導致了不便。
於是我開始考慮如何干凈地移除那個麻煩的IEntityChangeTracker 實例引用,一種方式是用反射,這是個不得已的辦法。好在有更好的解決方案—在實體加入緩存前將它們Detach。在這里不得不再批評一下AEF API設計的不人性化,實在是想不明白,ObjectContext的Attach方法為什么不能直接判斷類型來添加實體,還需要指定實體集名稱呢?
首先修改緩存過期的更新方法:
var db =
new
DBEntities();
var products = db.Products.ToList();
foreach
(var p
in
products) db.Detach(p);
HttpContext.Current.Cache[
"product"
] = products;
|
然后創建一個自己的ObjectContext,繼承自動生成的實體上下文類。
public
class
CustomDBEntites : DBEntities
{
List<
object
> AttachedEntities =
new
List<
object
>();
static
List<Type> CachedTypes;
//系統緩存的實體類型
static
Dictionary<Type,
string
> EntitySets;
//實體類型對應的實體集名稱
static
CustomDBEntites ()
{
CachedTypes =
new
List<Type> {
typeof
(Section) };
EntitySets =
new
Dictionary<Type,
string
>();
EntitySets.Add(
typeof
(Product),
"Products"
);
//.... ....
}
/// <summary>
/// 使用指定的SaveOptions 將所有更新保存到數據源,並清除所有被更新實體的上下文引用。
/// </summary>
/// <param name="options">一個確定操作的行為的 System.Data.Objects.SaveOptions 值。</param>
public
override
int
SaveChanges(System.Data.Objects.SaveOptions options)
{
try
{
return
base
.SaveChanges(options);
}
finally
{
foreach
(var item
in
AttachedEntities)
this
.Detach(item);
}
}
/// <summary>
/// 將對象附加到實體集上下文
/// </summary>
/// <param name="entity">對象實體</param>
public
void
Attach(IEntityWithKey entity, EntityState state)
{
var type = entity.GetType();
var cached = CachedTypes.Contains(type);
this
.AttachTo(EntitySets[type], entity);
this
.ObjectStateManager.ChangeObjectState(entity, state);
//保存修改過的實體(除刪除)
if
(cached && state != EntityState.Deleted)
this
.AttachedEntities.Add(entity);
}
/// <summary>
/// 將對象附加到實體集上下文,一般用於修改。
/// </summary>
/// <param name="entity">對象實體</param>
public
new
void
Attach(IEntityWithKey entity)
{
Attach(entity, EntityState.Unchanged);
}
/// <summary>
/// 將對象附加到實體集上下文,並設置狀態為新加。
/// </summary>
/// <param name="entity">對象實體</param>
public
void
AddObject(
object
entity)
{
Attach(entity
as
IEntityWithKey, EntityState.Added);
}
/// <summary>
/// 將對象附加到實體集上下文,並設置狀態為刪除。
/// </summary>
/// <param name="entity">對象實體</param>
public
new
void
DeleteObject(
object
entity)
{
Attach(entity
as
IEntityWithKey, EntityState.Deleted);
}
}
|
這樣,我們就可以直接放心的進行Add/Attach或Delete了。Attach方法需要IEntityWithKey參數,DeleteObject和AddObject參數類型則是object,這應該也是AEF自己的混亂。在此,也無法覆蓋各個實體集諸如db.Products.AddObject這樣的方法,不過At Least, it works,and looks nice。作為應用開發程序員,這就足夠了。
搞定了自己的程序,然后靜候AEF 5.0的到來。或許,也該試用下NHibernate等第三方框架了,我對AEF已經有點敬畏了。
