[原創]修正SubSonic v2.2.1的一處BUG,以及如何使用SubSonic進行多表查詢、子查詢以及數據庫分頁


相信很多同學都用過SubSonic,在07 - 10年ORM興起的時代,SubSonic可以說是DotNet開發人員的救星。雖說現在 EntityFramework大有一統江湖的趨勢,不過在DotNet2.0框架下,SubSonic依然是為數不多的選擇。

 

最近在維護基於 ExtAspNet 的通用權限管理項目 AppBox ,在使用SubSonic進行多表查詢和數據庫分頁時遇到了點問題,下面我會詳細分享這一經過,以及如何通過修改SubSonic的源代碼來修正這一問題。

 

我要實現如下的功能

我要實現的功能非常簡單:用戶管理,角色管理,角色用戶管理(一個用戶可以屬於多個角色)。相信很多同學閉着眼睛就能把數據庫給構造出來,不是嗎?

 

1. 用戶表

image

 

2. 角色表

image

 

3. 角色用戶表

image

 

其中用戶管理和角色管理都很簡單,我要實現的角色用戶管理界面如下所示:

1. 查看角色下的所有用戶

image

 

2. 向角色添加現有用戶

image

 

 

 

數據庫查詢時遇到問題

在查看角色下的所有用戶頁面,需要進行表關聯,相關的SubSonic代碼如下所示:

   1:  // 查詢 X_User 表
   2:  SqlQuery q = new Select().From<XUser>();
   3:  q.Where("1").IsEqualTo("1");
   4:   
   5:  // 在用戶名稱中搜索
   6:  string searchText = ttbSearchUser.Text.Trim();
   7:  if (!String.IsNullOrEmpty(searchText))
   8:  {
   9:      q.And(XUser.NameColumn).ContainsString(searchText);
  10:  }
  11:   
  12:  // 過濾選中角色下的所有用戶
  13:  object[] values = Grid1.DataKeys[Grid1.SelectedRowIndexArray[0]];
  14:  int roleId = Convert.ToInt32(values[0]);
  15:  SqlQuery subQ = new Select(XRoleUser.UserIdColumn).From<XRoleUser>().Where(XRoleUser.RoleIdColumn).IsEqualTo(roleId);
  16:   
  17:  q.And(XUser.IdColumn).In(subQ);
  18:   
  19:   
  20:  // 在查詢添加之后,排序和分頁之前獲取總記錄數
  21:  // Grid1總共有多少條記錄
  22:  Grid2.RecordCount = q.GetRecordCount();
  23:   
  24:  // 排列
  25:  q.OrderBys.Add(GetSortExpression(Grid2, XUser.Schema));
  26:   
  27:  // 數據庫分頁
  28:  q.Paged(Grid2.PageIndex + 1, Grid2.PageSize);
  29:  items = q.ExecuteAsCollection<XUserCollection>();

令人不解的時,居然報如下錯誤:

image

 

很明顯,SubSonic生成的SQL腳本不對,經過調試發現生成的腳本如下所示:

   1:  DECLARE @Page int
   2:  DECLARE @PageSize int
   3:   
   4:  SET @Page = 1
   5:  SET @PageSize = 20
   6:   
   7:  SET NOCOUNT ON
   8:   
   9:  -- create a temp table to hold order ids
  10:  DECLARE @TempTable TABLE (IndexId int identity, _keyID Int)
  11:   
  12:  -- insert the table ids and row numbers into the memory table
  13:  INSERT INTO @TempTable
  14:  (
  15:      _keyID
  16:  )
  17:  SELECT [dbo].[X_User].[Id]
  18:  FROM [dbo].[X_User]
  19:  WHERE 1 = @10
  20:  AND [dbo].[X_User].[Id] IN (SELECT [dbo].[X_RoleUser].[UserId]
  21:      FROM [dbo].[X_RoleUser]
  22:      WHERE [dbo].[X_RoleUser].[RoleId] = @RoleId0
  23:      )
  24:   
  25:  AND 1 = @10
  26:  AND [dbo].[X_User].[Id] IN (SELECT [dbo].[X_RoleUser].[UserId]
  27:      FROM [dbo].[X_RoleUser]
  28:      AND [dbo].[X_RoleUser].[RoleId] = @RoleId0
  29:      )
  30:   
  31:   ORDER BY Name DESC
  32:   
  33:  -- select only those rows belonging to the proper page
  34:  SELECT [dbo].[X_User].[Id], [dbo].[X_User].[Name], [dbo].[X_User].[Password], [dbo].[X_User].[Enabled], [dbo].[X_User].[Email], [dbo].[X_User].[Gender], [dbo].[X_User].[RealName], [dbo].[X_User].[QQ], [dbo].[X_User].[MSN], [dbo].[X_User].[CellPhone], [dbo].[X_User].[OfficePhone], [dbo].[X_User].[HomePhone], [dbo].[X_User].[Remark], [dbo].[X_User].[DeptId], [dbo].[X_User].[RoleId], [dbo].[X_User].[CreateTime]
  35:   
  36:  FROM [dbo].[X_User]
  37:  INNER JOIN [dbo].[X_RoleUser] ON [dbo].[X_User].[Id] = [dbo].[X_RoleUser].[UserId]
  38:   
  39:  INNER JOIN @TempTable t ON [dbo].[X_User].[Id] = t._keyID
  40:  WHERE t.IndexId BETWEEN ((@Page - 1) * @PageSize + 1) AND (@Page * @PageSize)

這里面有兩處錯誤:

1. 首先,25 – 29 行的Where子句重復了,相信這個問題一直存在於SubSonic2.2中,只不過大家都沒發現而已

2. 其次,重復的子查詢中Where被替換成了AND,導致這個子查詢沒有Where子句,從而報錯!

 

如果撇去分頁的SQL腳本不管,正確的SQL腳本應該是這樣的:

   1:  SELECT [dbo].[X_User].[Id]
   2:  FROM [dbo].[X_User]
   3:  WHERE 1 = @10
   4:  AND [dbo].[X_User].[Id] IN (SELECT [dbo].[X_RoleUser].[UserId]
   5:      FROM [dbo].[X_RoleUser]
   6:      WHERE [dbo].[X_RoleUser].[RoleId] = @RoleId0
   7:      )
   8:   
   9:   ORDER BYName DESC

很明顯,SubSonic在生成帶子查詢的分頁SQL腳本時除了問題。

 

修改SubSonic的源代碼

從Github下載SubSonic2.0的源代碼:https://nodeload.github.com/subsonic/SubSonic-2.0/zip/master

其實下載下來的是SubSonic2.2.1,找到其中的 SqlQuery\SqlGenerators\ANSISqlGenerator.cs 文件:

   1:   
   2:  public virtual string BuildPagedSelectStatement()
   3:  {
   4:      // 省略的代碼...
   5:      
   6:      string wheres = GenerateWhere();
   7:   
   8:      //have to doctor the wheres, since we're using a WHERE in the paging
   9:      //bits. So change all "WHERE" to "AND"
  10:      string tweakedWheres = wheres.Replace("WHERE", "AND");
  11:      
  12:      // 省略的代碼...
  13:   
  14:      string sql = string.Format(PAGING_SQL, idColumn, String.Concat(fromLine, joins, wheres), String.Concat(tweakedWheres, orderby, havings),
  15:          String.Concat(select, fromLine, joins), query.CurrentPage, query.PageSize, sqlType);
  16:      return sql;
  17:  }

其中 tweakedWheres 是關鍵,作者還特別指出要把其中 WHERE 替換成 AND,殊不知這樣做對子查詢是破壞性操作,而且下面連接SQL腳本時重復添加了WHERE子句。

修改后的代碼:

   1:   
   2:  public virtual string BuildPagedSelectStatement()
   3:  {
   4:      // 省略的代碼...
   5:      
   6:      string wheres = GenerateWhere();
   7:   
   8:      //have to doctor the wheres, since we're using a WHERE in the paging
   9:      //bits. So change all "WHERE" to "AND"
  10:      //string tweakedWheres = wheres.Replace("WHERE", "AND");
  11:      
  12:      // 省略的代碼...
  13:   
  14:      string sql = string.Format(PAGING_SQL, idColumn, String.Concat(fromLine, joins, wheres), String.Concat(orderby, havings),
  15:          String.Concat(select, fromLine, joins), query.CurrentPage, query.PageSize, sqlType);
  16:      return sql;
  17:  }

 

搞定!

 

子查詢與表關聯查詢(查看角色下所有用戶)

在上面的例子中,我們使用的是子查詢,對於“查看角色下所有用戶”這個案例,我們還有如下另一種解決辦法(效果完全一樣):

 

   1:  // 查詢 X_User 表
   2:  SqlQuery q = new Select().From<XUser>().InnerJoin(XRoleUser.UserIdColumn, XUser.IdColumn);
   3:  q.Where("1").IsEqualTo("1");
   4:   
   5:  // 在用戶名稱中搜索
   6:  string searchText = ttbSearchUser.Text.Trim();
   7:  if (!String.IsNullOrEmpty(searchText))
   8:  {
   9:      q.And(XUser.NameColumn).ContainsString(searchText);
  10:  }
  11:   
  12:  // 過濾選中角色下的所有用戶
  13:  object[] values = Grid1.DataKeys[Grid1.SelectedRowIndexArray[0]];
  14:  int roleId = Convert.ToInt32(values[0]);
  15:  q.And(XRoleUser.RoleIdColumn).IsEqualTo(roleId);
  16:   
  17:   
  18:  // 在查詢添加之后,排序和分頁之前獲取總記錄數
  19:  // Grid1總共有多少條記錄
  20:  Grid2.RecordCount = q.GetRecordCount();
  21:   
  22:  // 排列
  23:  q.OrderBys.Add(GetSortExpression(Grid2, XUser.Schema));
  24:   
  25:  // 數據庫分頁
  26:  q.Paged(Grid2.PageIndex + 1, Grid2.PageSize);
  27:  items = q.ExecuteAsCollection<XUserCollection>();

 

再來看下這段代碼生成的SQL腳本(修正SubSonic2.2.1中的BUG后):

   1:  DECLARE @Page int
   2:  DECLARE @PageSize int
   3:   
   4:  SET @Page = 1
   5:  SET @PageSize = 20
   6:   
   7:  SET NOCOUNT ON
   8:   
   9:  -- create a temp table to hold order ids
  10:  DECLARE @TempTable TABLE (IndexId int identity, _keyID Int)
  11:   
  12:  -- insert the table ids and row numbers into the memory table
  13:  INSERT INTO @TempTable
  14:  (
  15:      _keyID
  16:  )
  17:  SELECT [dbo].[X_User].[Id]
  18:  FROM [dbo].[X_User]
  19:  INNER JOIN [dbo].[X_RoleUser] ON [dbo].[X_User].[Id] = [dbo].[X_RoleUser].[UserId]
  20:      WHERE 1 = @10
  21:      AND [dbo].[X_RoleUser].[RoleId] = @RoleId1
  22:   
  23:  ORDER BY Name DESC
  24:   
  25:  -- select only those rows belonging to the proper page
  26:  SELECT [dbo].[X_User].[Id], [dbo].[X_User].[Name], [dbo].[X_User].[Password], [dbo].[X_User].[Enabled], [dbo].[X_User].[Email], [dbo].[X_User].[Gender], [dbo].[X_User].[RealName], [dbo].[X_User].[QQ], [dbo].[X_User].[MSN], [dbo].[X_User].[CellPhone], [dbo].[X_User].[OfficePhone], [dbo].[X_User].[HomePhone], [dbo].[X_User].[Remark], [dbo].[X_User].[DeptId], [dbo].[X_User].[RoleId], [dbo].[X_User].[CreateTime]
  27:   
  28:  FROM [dbo].[X_User]
  29:  INNER JOIN [dbo].[X_RoleUser] ON [dbo].[X_User].[Id] = [dbo].[X_RoleUser].[UserId]
  30:   
  31:  INNER JOIN @TempTable t ON [dbo].[X_User].[Id] = t._keyID
  32:  WHERE t.IndexId BETWEEN ((@Page - 1) * @PageSize + 1) AND (@Page * @PageSize)

 

向當前角色添加現有用戶

對於這個情況,我們要注意一點,就是供選擇的現有用戶不應當包括哪些已經屬於當前角色的用戶,可以用子查詢來實現:

   1:  SqlQuery q = new Select().From<XUser>(); //.LeftOuterJoin(XRoleUser.UserIdColumn, XUser.IdColumn);
   2:  q.Where("1").IsEqualTo("1");
   3:   
   4:  // 在職務名稱中搜索
   5:  string searchText = ttbSearchMessage.Text.Trim();
   6:  if (!String.IsNullOrEmpty(searchText))
   7:  {
   8:      q.And(XUser.NameColumn).ContainsString(searchText);
   9:  }
  10:   
  11:  // 排除已經屬於本角色的用戶
  12:  int currentRoleId = GetQueryIntValue("id");
  13:  SqlQuery subQ = new Select(XRoleUser.UserIdColumn).From<XRoleUser>().Where(XRoleUser.RoleIdColumn).IsEqualTo(currentRoleId);
  14:   
  15:  q.And(XUser.IdColumn).NotIn(subQ);
  16:  //q.And(XUser.IdColumn).IsNotEqualTo(1);
  17:              
  18:  // 只列出不在當前角色中的用戶
  19:  //q.AndExpression(XUser.RoleIdColumn.ColumnName).IsNotEqualTo(GetQueryIntValue("id")).Or(XUser.RoleIdColumn).IsNull().CloseExpression();
  20:   
  21:  // 在查詢添加之后,排序和分頁之前獲取總記錄數
  22:  // Grid1總共有多少條記錄
  23:  Grid1.RecordCount = q.GetRecordCount();
  24:   
  25:  // 排列
  26:  q.OrderBys.Add(GetSortExpression(Grid1, XUser.Schema));
  27:   
  28:  // 數據庫分頁
  29:  q.Paged(Grid1.PageIndex + 1, Grid1.PageSize);
  30:  XUserCollection items = q.ExecuteAsCollection<XUserCollection>();

 

小結

雖然SubSonic2.2的代碼已經不更新了,但是在實際應用中,我們可以恰當的修正其源代碼來滿足需求,這也歸功於開源的力量。同時也希望大家能多關注同樣是完全開源的ExtAspNet(基於ExtJS的專業ASP.NET2.0控件庫)。

 

注:AppBox是捐贈軟件,也就是說你可以通過捐贈作者來獲得AppBox源代碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM