1. 程式人生 > >在EF中使用Expression自動生成p=>new Entity(){X="",Y="",..}格式的Lambda表示式靈活實現按需更新

在EF中使用Expression自動生成p=>new Entity(){X="",Y="",..}格式的Lambda表示式靈活實現按需更新

一、基本介紹                                                                                                                                                                                                                                                                        

回憶:最早接觸Expression是在學校接觸到EF的時候,發現where方法裡的引數是Expression<Func<T,bool>>這麼一個型別,當初也只是看到了,也沒有過多的去探究,只是知道傳入lambda表示式使用即可,對於Expression和裡面的Func<T,bool>到底是怎麼一種關係,都不清楚。目前也不是很瞭解,只知道一些簡單的使用,但是可以解決自己目前的一些問題就好了。畢竟作為一名18年的應屆畢業生,能力有限。

        今天,就簡單的以我遇到的問題介紹下我使用Expression的一種使用。

1、Expression和Func委託的區別。

        對於這兩者的區別,網上有大把的介紹。可自行去搜索看看。講的肯定比我這一知半解的好。我就不說了。

二、使用Expression和Func委託建立新的物件並初始化其一個或多個成員的運算,如 C# 中的 new Point { X = 1, Y = 2 }                                                                                              

       可能一些人不知道生成這樣的有什麼用?貌似廢話了,會來看,應都是需要用到的。但還是總會有些愛好學習的偶然翻到就看了看,所以我還是以自己遇到的問題來說說有什麼作用吧。以下觀點僅是我個人感受,不喜勿碰。也希望大佬給點建議。

       我剛開始使用EF進行開發操作資料庫的時候,並不習慣,感覺沒ADO.NET來得快,使用ADO.NET幾乎沒有不是拼接一個字串就能解決,如果有,那就兩個。但是自從習慣了EF後,就再也不想去使用ADO.NET了,感覺使用EF太舒服了。但是用著用著又遇到新的讓我難受的事情。大概是習慣瞭然後對EF的要求更高了,想讓我開發時更舒服些。

  比如,在使用EF做修改操作時,一般情況是要給實體所有的必要屬性賦值:

資料庫中原有的一條資料

現在對其進行修改操作

複製程式碼
        public static Users GetUser()
        {
            var user = new Users()
            {
                Id = 1,
                CreateTime = DateTime.Now,
                ModifiedTime = DateTime.Now,
                Age = 20,
                Sex = "男",
                Name = "那誰"
            };
            return user;
        }    
複製程式碼 複製程式碼
        static void Main(string[] args)
        {
            using (var ctx = new EfContext())
            {
                var user = GetUser();
                var dataBaseUser = ctx.Users.AsNoTracking().FirstOrDefault(u => u.Id == user.Id);
                if (dataBaseUser != null)
                {
                    ctx.Users.Attach(user);
                    ctx.Entry(user).State = EntityState.Modified;           

                    if (!ctx.ChangeTracker.HasChanges())
                    {
                        Console.WriteLine("更新成功!");
                    }
                    else if (ctx.SaveChanges() > 0)
                    {
                        Console.WriteLine("更新成功!");
                    }
                    else
                    {
                        Console.WriteLine("更新失敗!");
                    }
                }
                Console.ReadKey();
            }
            
        }        
複製程式碼

  更新成功。但是對於CreateTime和ModifiedTime而言,一旦資料建立後,CreateTime就不會改變,只會更新ModifiedTime。鑑於此,我改變一下獲取User的方法如下,最後依然在控制檯中提交上述更新成功的程式碼:

複製程式碼
        public static Users GetUser()
        {
            var user = new Users()
            {
                Id = 1,
                ModifiedTime = DateTime.Now,
                Age = 20,
                Sex = "男",
                Name = "那誰"
            };
            return user;
        }
複製程式碼

 

  上述丟擲的錯誤是因為CreateTime賦值為空引起的。解決這個異常的方法我們不去討論。丟擲這個異常說明即使你把不需要修改的屬性從賦值這個階段去掉,EF是預設這個屬性值為空或者是預設值(假如你設定了預設值的話)。

  下面我將獲取Main方法修改一下,用一般常用的方法去完成這樣的操作。

 

  更新成功。這樣是實現了按需更新。但是我認為這不夠靈活,而且如果一個表需要修改的欄位比較多的話,那麼這樣的賦值會讓程式碼看起來很多。而且有些系統可能會有不同的介面對同一張表進行不同欄位的修改,這樣你就需要又寫一個方法去進行這樣的操作。這樣我感覺是很難受的。而且EF的效能一直都被說不好。你看這樣的修改就知道了。第一步,查出來,第二步修改。用ADO.NET一步就可以解決得,EF卻要用兩步。網路環境好的情況這一步兩步的差別也不是很明顯,但是網路環境比較差就會讓人抓狂。

  鑑於此,我又學到了一些方法更好的去實現修改。比如使用EntityFramework.Extended這個外掛。只需在NuGet中搜索安裝就行。上面都是我在使用EF對資料庫進行更新操作時的一些經過,下面才正式開始進入和標題相符的內容。

  安裝好EntityFramework.Extended這個外掛後,引用EntityFramework.Extensions名稱空間。對資料庫進行修改只需要這樣:

複製程式碼
        static void Main(string[] args)
        {
            using (var ctx = new EfContext())
            {                
                int sum=ctx.Users.Where(w => w.Id == 1).Update(p => new Users() { ModifiedTime = DateTime.Now, Age = 21, Sex = "女" });
                if(sum>0)
                {
                    Console.WriteLine("更新成功!");
                }
                else
                {
                    Console.WriteLine("更新失敗!");
                }
                Console.ReadKey();
            }
            
        }
複製程式碼

  更新成功。而且是一步完成。整個程式碼更是很少,看著就舒服很多。但是這樣還是不夠靈活,因為對同一張表不同介面修改不同的欄位的時候,還是要重新寫個方法去對Update括號裡的User(){}進行賦於不同欄位值。(是不是發現了Update後需要的表示式和標題中的一樣?)然後我就去網上看了下,通過Expression拼接lambda表示式的有不少文章介紹。但是和這個Update需要的表示式是有區別。大概是我搜索方式有問題,一直都沒有看到有關的文章介紹。最後通過文件自己找到了這個方法。

  我們只需新增一個這樣的方法:

複製程式碼
        public static Expression<Func<Users,Users>> GetUpdatePredicate(Users model)
        {
            var list = new List<MemberBinding>();

            var p = Expression.Parameter(typeof(Users), "p");

            foreach (var item in model.GetType().GetProperties())
            {
                var value = item.GetValue(model);

                if(value!=null)
                {
                    list.Add(Expression.Bind(typeof(Users).GetMember(item.Name)[0], Expression.Constant(value)));
                }
            }
            Expression expr = Expression.MemberInit(Expression.New(typeof(Users)), list);

            var lambda = Expression.Lambda<Func<Users, Users>>(expr, p);
            return lambda;
        }    
複製程式碼

  如果前端傳入的是一個實體,在Main中就這樣呼叫。(因為我是控制檯應用程式,所以只能模擬一下前端傳入實體)。

複製程式碼
        static void Main(string[] args)
        {
            //模擬前端傳入的實體
            var user = new Users()
            {
                Id = 1,
                Name = "小米"
            };

            using (var ctx = new EfContext())
            {                
                int sum=ctx.Users.Where(w => w.Id == user.Id).Update(GetUpdatePredicate(user));
                if(sum>0)
                {
                    Console.WriteLine("更新成功!");
                }
                else
                {
                    Console.WriteLine("更新失敗!");
                }
                Console.ReadKey();
            }
            
        }
複製程式碼

  然後除錯一下每一步生成的是什麼東西:

  一看不對啊。Age、CreateTime、ModifiedTime並沒有賦值也出現了啊。這是因為值型別的變數系統都會給他一個系統的預設值,因為我們判斷的是當item.GetValue(model)!=null時就將其加入到賦值裡去。像int型就的系統預設值是0,DateTime的系統預設值根據不同的應用程式有不一樣的預設值。所以才會加入到賦值裡面去。解決這個問題也簡單我們只需在為值型別的實體屬性的資料型別後加個“?”表示可空就行了。比如“int?”表示可空。但是有些屬性需要設定預設值怎麼辦?設定預設值的方法我經常用的就是[DefaultValue(1)],但是沒有找到獲取設定的預設值的方法,如果有知道的,麻煩留言告訴我一下,感激不盡。所以我使用的預設值是通過無參和有參建構函式實現的。就是新增資料和一些需要預設值的時候通過有參建構函式賦預設值,修改就是無參。這是我的解決辦法,如有更好的想法歡迎留言。處理好這個預設值問題後再看執行結果:

 

  發現生成的表示式對了,但是更新的時候丟擲了異常。上述異常是因為設定了主鍵自增引起的。主鍵也並不需要修改。所以在我們遍歷實體屬性的時候,可以跳過主鍵。跳過的方法目前只能笨一點。首先可以根據註解來跳過:item.GetCustomAttributesData()[0].AttributeType.Name.ToString().Contains("KeyAttribute"),這個獲取到的註解是[Key]:

其次根據EF的預設主鍵規則跳過,EF預設屬性名為“Id”或者“類名”+“Id”的屬性名為主鍵。根據這些跳過主鍵就行。你也可以用你自己的方法跳過主鍵,達到目的就行。做好這些後再來執行一下看看結果:

 

  更新成功。然後你可以將這個GetUpdatePredicate方法封裝成泛型,就可以讓所有實體類都可以使用這個方法。如果將更新的方法封裝成泛型,所有實體的基本更新呼叫這一個方法就可以了。只需傳入不同的實體類。

  可能認為給實體賦新值還是不夠靈活。上述已經提到過一種,前端傳入實體。用這種方式就就可以比較靈活的通過這種方式去更新資料庫。第二種是,當前端傳入多個引數的時候,只需將引數名修改成與對應的實體屬性名相同即可。然後通過遍歷傳入的引數,給對應的實體屬性賦值就可以比較靈活的用於不同頁面對於同一表格不同欄位的更新。這裡貼上我實現遍歷引數給對應實體賦值的程式碼:

複製程式碼
                var task = new TaskModel();

                foreach (var item in Request.QueryString)
                {
                    var taskmodel = task.GetType().GetProperty(item.ToString());
                    string value = Request.QueryString[item.ToString()];
                    var s = Nullable.GetUnderlyingType(taskmodel.PropertyType);
                    taskmodel.SetValue(task, Convert.ChangeType(value, s == null ? typeof(string) : s), null);
                }        
複製程式碼

可能你後臺使用Request.QueryString並不能接收到引數,就使用Request.Form。然後我還遇到過將所有資料通過Json傳入後臺的。這也好解決。解決方式個人能力有不同的解決方式。中心思想就是,可以靈活的將這些資料賦值給對應實體對應的屬性就好。實現了這個就OK了。

  文中有些地方會有所紕漏,就不要那麼計較了啦,畢竟是個剛畢業的菜鳥的第一篇部落格。喜歡的喜歡,拿去自己改改用。不喜歡的勿噴。歡迎

 GetUnderlyingType(Type nullableType)方法是用來返回一個可為空型別的基礎型別,如果 nullableType 引數不是一個封閉的Nullable<T>泛型,則反回null。 

Console.WriteLine(Nullable.GetUnderlyingType(typeof(Nullable<int>)));
 2     //輸出結果:System.Int32
 3 
 4     Console.WriteLine(Nullable.GetUnderlyingType(typeof(Nullable<>)) == null);
 5     //輸出結果:True
 6 
 7     Console.WriteLine(Nullable.GetUnderlyingType(typeof(int)) == null);
 8     //輸出結果:True
 9 
10     Console.WriteLine(Nullable.GetUnderlyingType(typeof(string)) == null);
11     //輸出結果:True

 

https://www.cnblogs.com/hgjmagic/p/9682196.html