自從ADO.NET Entity Framework面世以來,受到大家的熱捧,它封裝了大量代碼生成的工具,用戶只需要建立好實體之間的關系,系統就是會為用戶自動成功了Add、Delete、CreateObject、Attach、ToList......等等方法,這些方法基本上已經包含獲取、刪除、插入等基本方法,使用起來非常方便。只是在實體的更新上,由於LINQ面向的是泛型對象T,所以每個對象的更新方法都要由用戶自動編輯。有見及此,下面在下利用反射方法,創建了一個更新工具,此工具可以更新ObjectContext裡面的任意一個實體或者多個關聯實體。
一、簡單介紹反射
反射是一個程序集發現及運行的過程,通過反射可以得到*.exe或*.dll等程序 集內部的信息。使用反射可以看到一個程序集內部的接口、類、方法、字段、屬性、特性等等信息。在System.Reflection命名空間內包含多個反 射常用的類,下面表格列出了常用的幾個類。(詳細的資料請參考“反射的奧妙”)
類型 作用 Assembly 通過此類可以加載操縱一個程序集,並獲取程序集內部信息 EventInfo 該類保存給定的事件信息 FieldInfo 該類保存給定的字段信息 MethodInfo 該類保存給定的方法信息 MemberInfo 該類是一個基類,它定義了EventInfo、FieldInfo、MethodInfo、PropertyInfo的多個公用行為 Module 該類可以使你能訪問多個程序集中的給定模塊 ParameterInfo 該類保存給定的參數信息 PropertyInfo 該類保存給定的屬性信息
二 、實體與上下文的關系
每個實體值都會包含在上下文中, 當您從客戶端回收到實體時, 可以比較與上下文中的該實體的版本更新的實體,並應用適當的更改。值得注意的是,上下文會必須KEY找到實體對像,然後對每一個屬性逐個賦值。如果想對實體對象直接賦值,那麼KEY那將會改變,系統將無法從上下文中找到該對象。
三、開發實例
在為一個項目建立關系圖時,都會為多個實體建立關系,在更新實體時往往需要把導航屬性一同更新,這使得更新方法更為繁瑣。比如在覺見的訂單管理項目中,在更新訂單Order的同時,必須把訂單對應的OrderItem對象實現同步更新。為了簡化代碼,在下利用反射原理建立一個了特定類UpdateHelp,利用這個類可以更新ObjectContext裡面的多個關系對象。
其原理在於系統使用GetIntrinsicObj(EntityObject)方法,根據輸入實體(obj)的KEY在上下文中獲取對應的實體對象(intrinsic),然後使用UpdateIntrinsticObj(Object)方法,利用PropertyInfo遍歷實體的每個屬性,把輸入的實體對象(obj)的每個屬性都賦值給上下文的實體對象(intrinsic)。最特別的地方在於當遇到導航屬性的時候,使用了遞歸算法,重復調用UpdateIntrinsticObj(object)方法為導航屬性賦值。當遇到一對多或者多對多關系的時候,導航屬性將會是是一個List<T>對象,方法中CloneNavigationProperty是為單個對象賦值,而CloneNavigationPropertyEntityCollection方法是為多個對象賦值。
public class UpdateHelp:IDisposable
{
//記錄已經復制過的實體類,避免重要加載
private IList<Type> listType = new List<Type>();
private BusinessContext _context;
public UpdateHelp(BusinessContext context)
{
_context = context;
}
public void Dispose()
{
_context.Dispose();
}
//更新普通屬性
private void CloneProperty(PropertyInfo propertyInfo,object intrinsicObj,object newObj)
{
var data = propertyInfo.GetValue(newObj, null);
propertyInfo.SetValue(intrinsicObj, data, null);
}
//更新普通導航屬性
private void CloneNavigationProperty(PropertyInfo propertyInfo,object intrinsicObj,object newObj)
{
var data = propertyInfo.GetValue(newObj, null);
object dataClone = UpdateIntrinsticObj(data);
propertyInfo.SetValue(intrinsicObj, dataClone, null);
}
//更新返回值為EntityCollection<TEntity>的導航屬性
private void CloneNavigationPropertyEntityCollection(PropertyInfo propertyInfo, object intrinsicObj, object newObj)
{
//獲取參數newObj中的對象集合
IEnumerable<object> newData = propertyInfo.GetValue(newObj, null) as IEnumerable<object>;
//獲取上下文中匹配的原對象集合
var intrinsicData = propertyInfo.GetValue(intrinsicObj, null);
//利用EntityCollection<TEntity>類的擴展方法Add在原集合中加入新對象
var addMethod = intrinsicData.GetType().GetMethod("Add");
foreach (object obj in newData)
{
Object objClone = UpdateIntrinsticObj(obj);
addMethod.Invoke(intrinsicData, new object[] { objClone });
}
}
//獲取上下文中待更新的原對象
private object GetIntrinsicObj(EntityObject entity)
{
Object intrinsicObj;
//根據輸入對象的EntityKey判斷該對象是已有值還是新建值
//若是已有值即從上下文中獲取對應值,若是新建值即反射生成一個新對象
if (entity.EntityKey.EntityKeyValues != null)
intrinsicObj = _context.GetObjectByKey(entity.EntityKey);
else
intrinsicObj = Activator.CreateInstance(entity.GetType());
return intrinsicObj;
}
//更新上下文中的原對象,返回值為更新後的原對象
public object UpdateIntrinsticObj(Object obj)
{
//記錄已經復制過的實體類,避免重要加載
listType.Add(obj.GetType());
//獲取上下文中的原對象
Object intrinsicObj=GetIntrinsicObj(obj as EntityObject);
//更新原對象的每個一個屬性
//把原對象intrinsicObj的每一個屬性設置為與obj對象相等
foreach (PropertyInfo propertyInfo in obj.GetType().GetProperties())
{
//若listType裡面包含些類型,證明此實體類已經更新過
if (!listType.Contains(propertyInfo.PropertyType) && propertyInfo.CanWrite
&& propertyInfo.Name != "EntityKey"&& propertyInfo.PropertyType.Name != "EntityReference`1")
{
//若為導航屬性則需要使用此方法更新
if (propertyInfo.GetCustomAttributes(typeof(EdmRelationshipNavigationPropertyAttribute), false).Count() != 0)
{
//若導航屬性返回值為集合則使用此方法
if (propertyInfo.PropertyType.Name == "EntityCollection`1")
CloneNavigationPropertyEntityCollection(propertyInfo, intrinsicObj, obj);
else//若導航屬性為普通對象則使用以下方法
CloneNavigationProperty(propertyInfo, intrinsicObj, obj);
}
else //若為普通屬性則使用以下方法
CloneProperty(propertyInfo, intrinsicObj, obj);
}
}
return intrinsicObj;
}
}
public class UpdateHelp:IDisposable
{
//記錄已經復制過的實體類,避免重要加載
private IList<Type> listType = new List<Type>();
private BusinessContext _context;
public UpdateHelp(BusinessContext context)
{
_context = context;
}
public void Dispose()
{
_context.Dispose();
}
//更新普通屬性
private void CloneProperty(PropertyInfo propertyInfo,object intrinsicObj,object newObj)
{
var data = propertyInfo.GetValue(newObj, null);
propertyInfo.SetValue(intrinsicObj, data, null);
}
//更新普通導航屬性
private void CloneNavigationProperty(PropertyInfo propertyInfo,object intrinsicObj,object newObj)
{
var data = propertyInfo.GetValue(newObj, null);
object dataClone = UpdateIntrinsticObj(data);
propertyInfo.SetValue(intrinsicObj, dataClone, null);
}
//更新返回值為EntityCollection<TEntity>的導航屬性
private void CloneNavigationPropertyEntityCollection(PropertyInfo propertyInfo, object intrinsicObj, object newObj)
{
//獲取參數newObj中的對象集合
IEnumerable<object> newData = propertyInfo.GetValue(newObj, null) as IEnumerable<object>;
//獲取上下文中匹配的原對象集合
var intrinsicData = propertyInfo.GetValue(intrinsicObj, null);
//利用EntityCollection<TEntity>類的擴展方法Add在原集合中加入新對象
var addMethod = intrinsicData.GetType().GetMethod("Add");
foreach (object obj in newData)
{
Object objClone = UpdateIntrinsticObj(obj);
addMethod.Invoke(intrinsicData, new object[] { objClone });
}
}
//獲取上下文中待更新的原對象
private object GetIntrinsicObj(EntityObject entity)
{
Object intrinsicObj;
//根據輸入對象的EntityKey判斷該對象是已有值還是新建值
//若是已有值即從上下文中獲取對應值,若是新建值即反射生成一個新對象
if (entity.EntityKey.EntityKeyValues != null)
intrinsicObj = _context.GetObjectByKey(entity.EntityKey);
else
intrinsicObj = Activator.CreateInstance(entity.GetType());
return intrinsicObj;
}
//更新上下文中的原對象,返回值為更新後的原對象
public object UpdateIntrinsticObj(Object obj)
{
//記錄已經復制過的實體類,避免重要加載
listType.Add(obj.GetType());
//獲取上下文中的原對象
Object intrinsicObj=GetIntrinsicObj(obj as EntityObject);
//更新原對象的每個一個屬性
//把原對象intrinsicObj的每一個屬性設置為與obj對象相等
foreach (PropertyInfo propertyInfo in obj.GetType().GetProperties())
{
//若listType裡面包含些類型,證明此實體類已經更新過
if (!listType.Contains(propertyInfo.PropertyType) && propertyInfo.CanWrite
&& propertyInfo.Name != "EntityKey"&& propertyInfo.PropertyType.Name != "EntityReference`1")
{
//若為導航屬性則需要使用此方法更新
if (propertyInfo.GetCustomAttributes(typeof(EdmRelationshipNavigationPropertyAttribute), false).Count() != 0)
{
//若導航屬性返回值為集合則使用此方法
if (propertyInfo.PropertyType.Name == "EntityCollection`1")
CloneNavigationPropertyEntityCollection(propertyInfo, intrinsicObj, obj);
else//若導航屬性為普通對象則使用以下方法
CloneNavigationProperty(propertyInfo, intrinsicObj, obj);
}
else //若為普通屬性則使用以下方法
CloneProperty(propertyInfo, intrinsicObj, obj);
}
}
return intrinsicObj;
}
}
在完成更新操作後,再加上LingHelp類,就可以利用它完成大部的數據處理問題,大家可以建立main測試一下。
public class LinqHelp:IDisposable
{
private BusinessContext _context;
private UpdateHelp _updateHelp;
public LinqHelp()
{
_context = new BusinessContext();
_updateHelp = new UpdateHelp(_context);
}
public LinqHelp(BusinessContext context)
{
_context = context;
_updateHelp = new UpdateHelp(context);
}
public void Dispose()
{
_context.Dispose();
}
public int Add<T>(T entity) where T : EntityObject
{
int n = -1;
Transaction transaction = Transaction.Current;
try
{
_context.AddObject(entity.GetType().Name, entity);
n = _context.SaveChanges();
}
catch (Exception ex)
{
Business.Common.ExceptionManager.DataException.DealWith(ex);
transaction.Rollback();
}
return n;
}
public int Update<T>(ref T entity) where T:EntityObject
{
int n = -1;
Transaction transaction = Transaction.Current;
try
{
EntityObject returnObj = this._updateHelp.UpdateIntrinsticObj(entity) as EntityObject;
n = _context.SaveChanges();
entity = _context.GetObjectByKey(entity.EntityKey) as T;
}
catch (Exception ex)
{
Business.Common.ExceptionManager.DataException.DealWith(ex);
transaction.Rollback();
}
return n;
}
public List<T> GetList<T>(string name) where T:EntityObject
{......}
........
}
public class OrderRepository
{
private LinqHelp _linqHelp;
public OrderRepository()
{
_linqHelp = new LinqHelp();
}
public int AddOrder(Order order)
{..........}
..............
public int UpdateOrder(Order order)
{
return _linqHelp.Update<Order>(ref order);
}
}
public class PersonRepository
{......}
class Program
{
static void Main(string[] args)
{
Test1();
Console.ReadKey();
}
public static void Test1()
{
using (BusinessContext context = new BusinessContext())
{
context.ContextOptions.LazyLoadingEnabled = true;
var order = context.Order.First();
order.Person.Address = "北京路1號";
OrderRepository orderRepository = new OrderRepository();
orderRepository.UpdateOrder(order);
}
}
public static void Test2()
{
using (BusinessContext context = new BusinessContext())
{
Person person = context.Person.First();
Order order = new Order();
order.OrderNumber = "2A34313344";
OrderItem orderItem = new OrderItem();
orderItem.Goods = "555";
orderItem.Count = 8;
orderItem.Price = 2.5;
order.OrderItem.Add(orderItem);
person.Order.Add(order);
PersonRepository personRepository = new PersonRepository();
personRepository.UpdatePerson(person);
Console.Write(person.Order.First().ID + person.Order.First().OrderItem.First().ID);
}
}
}
public class LinqHelp:IDisposable
{
private BusinessContext _context;
private UpdateHelp _updateHelp;
public LinqHelp()
{
_context = new BusinessContext();
_updateHelp = new UpdateHelp(_context);
}
public LinqHelp(BusinessContext context)
{
_context = context;
_updateHelp = new UpdateHelp(context);
}
public void Dispose()
{
_context.Dispose();
}
public int Add<T>(T entity) where T : EntityObject
{
int n = -1;
Transaction transaction = Transaction.Current;
try
{
_context.AddObject(entity.GetType().Name, entity);
n = _context.SaveChanges();
}
catch (Exception ex)
{
Business.Common.ExceptionManager.DataException.DealWith(ex);
transaction.Rollback();
}
return n;
}
public int Update<T>(ref T entity) where T:EntityObject
{
int n = -1;
Transaction transaction = Transaction.Current;
try
{
EntityObject returnObj = this._updateHelp.UpdateIntrinsticObj(entity) as EntityObject;
n = _context.SaveChanges();
entity = _context.GetObjectByKey(entity.EntityKey) as T;
}
catch (Exception ex)
{
Business.Common.ExceptionManager.DataException.DealWith(ex);
transaction.Rollback();
}
return n;
}
public List<T> GetList<T>(string name) where T:EntityObject
{......}
........
}
public class OrderRepository
{
private LinqHelp _linqHelp;
public OrderRepository()
{
_linqHelp = new LinqHelp();
}
public int AddOrder(Order order)
{..........}
..............
public int UpdateOrder(Order order)
{
return _linqHelp.Update<Order>(ref order);
}
}
public class PersonRepository
{......}
class Program
{
static void Main(string[] args)
{
Test1();
Console.ReadKey();
}
public static void Test1()
{
using (BusinessContext context = new BusinessContext())
{
context.ContextOptions.LazyLoadingEnabled = true;
var order = context.Order.First();
order.Person.Address = "北京路1號";
OrderRepository orderRepository = new OrderRepository();
orderRepository.UpdateOrder(order);
}
}
public static void Test2()
{
using (BusinessContext context = new BusinessContext())
{
Person person = context.Person.First();
Order order = new Order();
order.OrderNumber = "2A34313344";
OrderItem orderItem = new OrderItem();
orderItem.Goods = "555";
orderItem.Count = 8;
orderItem.Price = 2.5;
order.OrderItem.Add(orderItem);
person.Order.Add(order);
PersonRepository personRepository = new PersonRepository();
personRepository.UpdatePerson(person);
Console.Write(person.Order.First().ID + person.Order.First().OrderItem.First().ID);
}
}
}
四、性能問題
由於過度使用反射會使系統的性能下降,所以需要注意此更新方法的使用范圍。一般此反射更新只會使用在小型的項目當中,如果在大中型項目內使用,將會在性能上負出代價。(由於時間有限,而且沒有經過大量的測試,有不足之處請點評)