C#綜合揭祕——利用泛型與反射更新實體(ADO.NET Entity Framework)
自從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那將會改變,系統將無法從上下文中找到該物件。三、開發例項
其原理在於系統使用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;
}
}
在完成更新操作後,再加上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);
}
}
}
四、效能問題
由於過度使用反射會使系統的效能下降,所以需要注意此更新方法的使用範圍。一般此反射更新只會使用在小型的專案當中,如果在大中型專案內使用,將會在效能上負出代價。(由於時間有限,而且沒有經過大量的測試,有不足之處請點評)C#綜合揭祕
反射的奧妙對JAVA與.NET開發有興趣的朋友歡迎加入QQ群:162338858