1. 程式人生 > >C#綜合揭祕——利用泛型與反射更新實體(ADO.NET Entity Framework)

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那將會改變,系統將無法從上下文中找到該物件。

三、開發例項

在為一個專案建立關係圖時,都會為多個實體建立關係,在更新實體時往往需要把導航屬性一同更新,這使得更新方法更為繁瑣。比如在覺見的訂單管理專案中,在更新訂單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;
        }
    }

在完成更新操作後,再加上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 點選這裡加入此群