A. MVC4+EFcodefirst中如何處理資料庫歷史記錄的保存和查詢
二、查詢問題分析
(一) 數據查詢應該在哪做
在EF中,面向對象的數據查詢主要提供了兩種方式:
TEntity DbSet<TEntity>.Find(params object[] keyValues):針對主鍵設計的通過主鍵查找單個實體,會先在EF的本地數據集Local中進行查詢,如果沒有,再去資料庫中查詢。
IQueryable<T>、IEnumerable<T>類型的所有數據查詢的擴展方法(由於DbSet<T>繼承於IQueryable<T>與IEnumerable<T>),如SingleOrDefault,FirstOrDefault,Where等。其中IQueryable<T>的擴展方法會先收集需求,到最後一步再生成相應的SQL語句進行數據查詢;而IEnumerable<T>的擴展方法則是在查詢的第一步就生成相應的SQL語句獲取數據到內存中,後面的操作都是以內存中的數據為基礎進行操作的。
以上兩種方式為EF的數據查詢提供了極大的自由度,這個自由度是我們在封裝的時候需要保持的。但是,在閱讀不少人(其中不乏工作了幾年的)對EF的封裝,設計統一的數據操作介面Repository中關於數據查詢的操作中,通常會犯如下幾種失誤:
設計了很多GetByName,GetByXX,GetByXXX的操作,這些操作通常並不是所有實體都會用到,只是部分實體的部分業務用到,或者是「估計會用到」。
定義了按條件查詢的SingleOrDefault,FirstOrDefault,Count,GetByPredicate(predicate)等方法,但是對於條件predicate的類型是使用Expression<Func<TEntity, boo>>還是Func<TEntity, bool>很糾結,最後乾脆兩個都設計,相當於把IQueryable<T>,IEnumerable<T>的方法再過一遍。
定義了獲取全部數據的GetAll()方法,但卻使用了IEnumerable<TEntity>類型的返回值,明白的同學都知道,這相當於把整個表的數據都載入到內存中,問題很嚴重,設計者卻不知道。
諸如此類,各種奇葩的查詢操作層出不窮,這些操作或者破壞了EF數據查詢原有的靈活性,或者畫蛇添足。
其實,這么多失誤的原因只有一個,設計者忘記了EF是ORM,把EF當作ado.net來使用了。只要記著EF是ORM,以上這些功能已經實現了,就不要去重復實現了。那麼以上的問題就非常好解決了,只要:
在數據操作Repository介面中把EF的DbSet<TEntity>開放成一個只讀的IQueryable<TEntity>類型的屬性提供給業務層作為數據查詢的數據源
就可以了。這個數據源是只讀的,並且類型是IQueryable<T>,就保證了它只能作為數據查詢的數據源,而不像開放了DbSet<T>類型那樣可以在業務層中調用EF的內部方法進行增、刪、改等操作。另外IQueryable<T>類型保持了EF原有的查詢自由性與靈活性,簡單明了。這個數據集還可以傳遞到業務層的各個層次,以實現在哪需要數據就在哪查的靈活性。
(二) 循環中的查詢陷阱
EF的導航屬性是延遲載入的,延遲載入的優點就是不用到不載入,一次只載入必要的數據,這減少了每次載入的數據量,但缺點也不言自明:極大的增加了資料庫連接的次數,比如如下這么個簡單的需求:
輸出每個用戶擁有的角色數量
根據這個需求,很容易就寫出了如下的代碼:
遍歷所有用戶信息,輸出每個用戶信息中角色(導航屬性)的數量。
上面這段代碼邏輯很清晰,看似沒有什麼問題。我們來分析一下代碼的執行過程:
132行,從IOC容器中獲取用戶倉儲介面的實例,這沒什麼問題。
133行,取出所有用戶信息(memberRepository.Entities),執行SQL如下:
SELECT [Extent1].[Id] AS [Id], [Extent1].[UserName] AS [UserName], [Extent1].[Password] AS [Password], [Extent1].[NickName] AS [NickName], [Extent1].[Email] AS [Email], [Extent1].[IsDeleted] AS [IsDeleted], [Extent1].[AddDate] AS [AddDate], [Extent1].[Timestamp] AS [Timestamp], [Extent2].[Id] AS [Id1] FROM [dbo].[Members] AS [Extent1] LEFT OUTER JOIN [dbo].[MemberExtends] AS [Extent2] ON [Extent1].[Id] = [Extent2].[Member_Id]
雖然EF生成的SQL有些復雜,但還是沒什麼問題
3. 136行,就開始有問題了,每次循環都會連接一次資料庫,執行一次如下查詢(最後一個1是用戶編號):
exec sp_executesql N'SELECT [Extent2].[Id] AS [Id], [Extent2].[Name] AS [Name], [Extent2].[Description] AS [Description], [Extent2].[RoleTypeNum] AS [RoleTypeNum], [Extent2].[IsDeleted] AS [IsDeleted], [Extent2].[AddDate] AS [AddDate], [Extent2].[Timestamp] AS [Timestamp] FROM [dbo].[RoleMembers] AS [Extent1] INNER JOIN [dbo].[Roles] AS [Extent2] ON [Extent1].[Role_Id] = [Extent2].[Id] WHERE [Extent1].[Member_Id] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1
試想,如果有100個用戶,就要連接100次資料庫,這么一個簡單的需求,連接了101次資料庫,還不得讓資料庫瘋掉了。
當然,有同學可以要說,這里用了延遲載入才會多了很多連接資料庫的次數,你可以立即載入啊,把Role角色一次性載入進來。好吧,我們來看看立即載入:
143行,在取所有用戶信息的時候使用Include方法把與用戶關聯的所有角色信息也一並查詢出來了,這樣在循環遍歷的時候就不會再連接資料庫去查詢角色信息了。但是如果看到執行的SQL語句,估計你想死的心情都有了。執行的查詢如下:
SELECT [Project1].[Id] AS [Id], [Project1].[UserName] AS [UserName], [Project1].[Password] AS [Password], [Project1].[NickName] AS [NickName], [Project1].[Email] AS [Email], [Project1].[IsDeleted] AS [IsDeleted], [Project1].[AddDate] AS [AddDate], [Project1].[Timestamp] AS [Timestamp], [Project1].[Id1] AS [Id1], [Project1].[C1] AS [C1], [Project1].[Id2] AS [Id2], [Project1].[Name] AS [Name], [Project1].[Description] AS [Description], [Project1].[RoleTypeNum] AS [RoleTypeNum], [Project1].[IsDeleted1] AS [IsDeleted1], [Project1].[AddDate1] AS [AddDate1], [Project1].[Timestamp1] AS [Timestamp1] FROM ( SELECT [Extent1].[Id] AS [Id], [Extent1].[UserName] AS [UserName], [Extent1].[Password] AS [Password], [Extent1].[NickName] AS [NickName], [Extent1].[Email] AS [Email], [Extent1].[IsDeleted] AS [IsDeleted], [Extent1].[AddDate] AS [AddDate], [Extent1].[Timestamp] AS [Timestamp], [Extent2].[Id] AS [Id1], [Join2].[Id] AS [Id2], [Join2].[Name] AS [Name], [Join2].[Description] AS [Description], [Join2].[RoleTypeNum] AS [RoleTypeNum], [Join2].[IsDeleted] AS [IsDeleted1], [Join2].[AddDate] AS [AddDate1], [Join2].[Timestamp] AS [Timestamp1], CASE WHEN ([Join2].[Member_Id] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1] FROM [dbo].[Members] AS [Extent1] LEFT OUTER JOIN [dbo].[MemberExtends] AS [Extent2] ON [Extent1].[Id] = [Extent2].[Member_Id] LEFT OUTER JOIN (SELECT [Extent3].[Member_Id] AS [Member_Id], [Extent4].[Id] AS [Id], [Extent4].[Name] AS [Name], [Extent4].[Description] AS [Description], [Extent4].[RoleTypeNum] AS [RoleTypeNum], [Extent4].[IsDeleted] AS [IsDeleted], [Extent4].[AddDate] AS [AddDate], [Extent4].[Timestamp] AS [Timestamp] FROM [dbo].[RoleMembers] AS [Extent3] INNER JOIN [dbo].[Roles] AS [Extent4] ON [Extent4].[Id] = [Extent3].[Role_Id] ) AS [Join2] ON [Extent1].[Id] = [Join2].[Member_Id] ) AS [Project1] ORDER BY [Project1].[Id] ASC, [Project1].[Id1] ASC, [Project1].[C1] ASC
(三) 導航屬性的查詢陷阱
我們再來回顧一下導航屬性的長相(以用戶信息中的角色信息為例):
可以看到,集合類的導航屬性是一個ICollection<T>類型的集合,其實現類可以是通常使用List<T>或者HashSet<T>。用了ICollection<T>,就限定了集合類的導航屬性是一個內存集合,只要用到這個導航屬性,就必須把集合中的所有數據都載入到內存中,才能進行後續操作。比如上面的例子中,我們的需求只是想知道用戶擁有角色的數量,原意只是要執行一下SQL的Count語句即可,卻想不到EF是把這個集合載入到內存中(上面的語句,是把當前用戶的所有角色信息查詢出來),再在內存中進行計數,這無形中是一個很大的資源浪費。比如在一個商城系統中,我們想了解一種商品的銷量(proct.Orders.Count),那就可能把幾萬條訂單信息都載入到內存中,再進行計數,這將是災難性的資源消耗。
B. asp.net mvc4怎麼把資料庫裡面的圖片顯示到頁面上
不有html標簽? 圖片標簽 <img src="圖片路徑"/ >
C. ASP.NET MVC4 如何同時保存圖片和存儲文字到資料庫
uploaderfy只是負責上傳吧...
如果你想用這種 , 最好還是分開 , 現在一般的也都是分開操作....
填寫信息 , 到上傳文件這里 , 選擇文件 , 同時上傳 , 上傳完畢後 , 通過回調 , 吧上傳成功返回的文件名 寫到一個hidden欄位中 (也可以把圖片顯示出來 , 這看起來更友好) , 然後 提交表單, 這時候就能同時把上傳的那個文件名給保存了...
缺點是可以上傳文件後不保存信息....造成呢個很多冗餘圖片
D. .net mvc4 怎麼把圖片的相對路徑存儲在資料庫
建議存儲文件名,或者是一個路徑 而不要\這種 讀取的時候拼接下路徑就可以了
E. MVC4自動創建資料庫文件在哪
先看web.config里的connection string的定義。看看是聯什麼資料庫。如肆檔果是SQL Express或畢雹返者SQL CE的資料庫,默認會放到app_data下,如果是oracle,一般是在「安裝目錄:\oracle\proct\10.2.0\oradata\手飢伺服器名\***.ORA」
F. MVC4自動創建資料庫文件在哪
先看web.config里的connection string的定義。看看是聯什麼資料庫。如果是SQL Express或者SQL CE的資料庫,默認會放到app_data下,如果是oracle,一般是在「安裝目錄:\oracle\proct\10.2.0\oradata\伺服器名\***.ORA」