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

来源:https://www.cnblogs.com/hgjmagic/archive/2018/09/26/9682196.html
-Advertisement-
Play Games

一、基本介紹 回憶:最早接觸Expression是在學校接觸到EF的時候,發現where方法里的參數是Expression<Func<T,bool>>這麼一個類型,當初也只是看到了,也沒有過多的去探究,只是知道傳入lambda表達式使用即可,對於Expression和裡面的Func<T,bool>到 ...


一、基本介紹                                                                                                                                                                                                                                                                        

回憶:最早接觸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了。

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 使用反射(Reflection)使得程式在運行過程中可以動態的獲取對象或類型的類型信息,然後調用該類型的方法和構造函數,或訪問和修改該類型的欄位和屬性;可以通過晚期綁定技術動態的創建類型的實例;可以獲取程式集中的所有類型信息;可以在動態構建新類型;還可以檢索元素所添加的特性; ※反射相關的類基本都位 ...
  • 前言 配置 在我們開發過程中必不可少, ASP.NET 中的配置在 中。也可配置在如:JSON、XML、資料庫等(但 ASP.NET 並沒提供相應的模塊和方法)。 在 ASP.NET Core 中 Web.config 已經不存在了(但如果托管到 IIS 的時候可以使用 web.config 配置 ...
  • lodop是web列印控制項,引用安裝目錄下的ocx文件,可以在c/s架構中使用。 該文件所在路徑:C:\Program Files (x86)\MountTaiSoftware\Lodop 有32位和64位的,如下例子引用的是32位的:CAOSOFT_WEB_PRINT_lodop.ocx 使用的是 ...
  • 對於數據量較大的表格,需要計算一些特殊數值時,我們通過運用公式能有效提高我們數據處理的速度和效率,對於後期數據的增刪改查等的批量操作也很方便。此外,對於某些數值的信息來源,我們也可以通過讀取數據中包含的公式來獲取。下麵的示例中將分享通過C# 來創建、讀取Excel公式的方法。 工具使用 Spire. ...
  • 一、依賴倒置(Dependency Inversion Principle) 依賴倒置是面向對象設計領域的一種軟體設計原則。(其他的設計原則還有:單一職責原則、開放封閉原則、里式替換原則、介面分離原則,合稱SOLID) 話說設計原則有什麼用呢? 設計原則是無數編程前輩總結下來的經驗,好似編程界的金科 ...
  • 摘要:IIS配置成功後,系統一直提示無法載入應用程式集,iis已經進行過net frame重新註冊,bin文件夾也設置了everyone許可權,無論怎麼搗騰,都提示無法載入相應的程式,通過查閱網上N多資料,終於找到了相關設置方法,先記錄如下:錯誤信息: c_sharp_無法載入應用程式表現形式 處理方 ...
  • 1、右擊安裝編輯程式,在“視圖”菜單中,選擇“註冊表”。 2、創建“鍵”:HKCU\Software\Microsoft\Windows\CurrentVersion\Run 3、右鍵Run,新建“字元串值”。在屬性頁面設置name和value值([TARGETDIR]Form1.exe)。 ...
  • 一、添加引用 System.DirectoryServices System.DirectoryServices.AccountManagement 二、驗證代碼 聲明域 驗證代碼 三、開放埠 如果調用不通,需要防火牆開放389埠 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...