.net core可以執行SQL語句,但是只能生成強類型的返回結果。例如var blogs = context.Blogs.FromSql("SELECT * FROM dbo.Blogs").ToList()。而不允許返回DataSet、DataTable等弱類型。可能由於這個原因沒有實現在.net core中DataTable,然而DataTable還是可能會用到的。我們這里就有一個數據倉庫的需求,允許用戶自行編寫類似SQL語句,然后執行,以表格展示。因為語句是千變萬化的,因此我也不知道用戶的語句輸出的是啥,更無法以類型來定義,因此只能采用DataTable方式。
之前.net framework下,可以通過dataadpater很方便的填充datatable,然后將datatable的數據推送到客戶端展示。但是.net core下,已經沒有DataTable和DataSet,我們只能自行實現MicroDataTable。
這里我們也按照DataTable的方式,MicroDataTable的列定義為MicroDataColumn,行定義為MicroDataRow。代碼如下:
1 public class MicroDataTable
2 {
3 /// <summary>
4 /// 整個查詢語句結果的總條數,而非本DataTable的條數
5 /// </summary>
6 public int TotalCount { get; set; }
7
8 public List<MicroDataColumn> Columns { get; set; } = new List<MicroDataColumn>();
9
10 public List<MicroDataRow> Rows { get; set; } = new List<MicroDataRow>();
11
12 public MicroDataColumn[] PrimaryKey { get; set; }
13
14 public MicroDataRow NewRow()
15 {
16 return new MicroDataRow(this.Columns, new object[Columns.Count]);
17 }
18 }
19
20 public class MicroDataColumn
21 {
22 public string ColumnName { get; set; }
23 public Type ColumnType { get; set; }
24 }
25
26 public class MicroDataRow
27 {
28 private object[] _ItemArray;
29 public List<MicroDataColumn> Columns { get; private set; }
30
31 public MicroDataRow(List<MicroDataColumn> columns, object[] itemArray)
32 {
33 this.Columns = columns;
34 this._ItemArray = itemArray;
35 }
36
37 public object this[int index]
38 {
39 get { return _ItemArray[index]; }
40 set { _ItemArray[index] = value; }
41 }
42 public object this[string columnName]
43 {
44 get
45 {
46 int i = 0;
47 foreach (MicroDataColumn column in Columns)
48 {
49 if (column.ColumnName == columnName)
50 break;
51 i++;
52 }
53 return _ItemArray[i];
54 }
55 set
56 {
57 int i = 0;
58 foreach (MicroDataColumn column in Columns)
59 {
60 if (column.ColumnName == columnName)
61 break;
62 i++;
63 }
64 _ItemArray[i] = value;
65 }
66 }
67 }
需要注意的是TotalCount屬性,在分頁情況下,是指查詢語句在數據庫中查詢出的所有記錄條數,而MicroDataTable的數據是當前頁面的記錄。
對於從數據庫中獲取DataTable的做法,采用類似SqlHelper的方式編寫DbContext的ExecuteDataTable擴展方法,傳入SQL語句和SQL語句的參數,生成MicroDataTable:
1 public static MicroDataTable ExecuteDataTable(this DbContext context, string sql, params object[] parameters)
2 {
3 var concurrencyDetector = context.Database.GetService<IConcurrencyDetector>();
4
5 using (concurrencyDetector.EnterCriticalSection())
6 {
7 var rawSqlCommand = context.Database.GetService<IRawSqlCommandBuilder>().Build(sql, parameters);
8
9 RelationalDataReader query = rawSqlCommand.RelationalCommand.ExecuteReader(context.Database.GetService<IRelationalConnection>(), parameterValues: rawSqlCommand.ParameterValues);
10
11 return MicroDataTableHelper.FillDataTable(query.DbDataReader, 0, int.MaxValue);
12 }
13 }
14
15 public static MicroDataTable ExecuteDataTable(this DbContext context, string sql, int pageIndex, int pageSize, params object[] parameters)
16 {
17 var concurrencyDetector = context.Database.GetService<IConcurrencyDetector>();
18
19 using (concurrencyDetector.EnterCriticalSection())
20 {
21 var rawSqlCommand = context.Database.GetService<IRawSqlCommandBuilder>().Build(sql, parameters);
22
23 RelationalDataReader query = rawSqlCommand.RelationalCommand.ExecuteReader(context.Database.GetService<IRelationalConnection>(), parameterValues: rawSqlCommand.ParameterValues);
24
25 return MicroDataTableHelper.FillDataTable(query.DbDataReader, 0, int.MaxValue);
26 }
27 }
這個方法還是需要部分.net framework core的技巧的,流程是根據SQL和參數創建原生的SQLCommand,執行ExecuteReader方法返回DataReader,再把DataReader填充到MicroDataTable中。注意的是,IConcurrencyDetector在.net core的描述是這樣的:This API supports the Entity Framework Core infrastructure and is not intended to be used directly from your code. This API may change or be removed in future releases。我們只能先這樣實現,以后看是否ef.core能否改變或者給出更好的方式。
上面程序中,最后有一句話MicroDataTableHelper.FillDataTable,這個方法的主要功能是從DataReader填充到MicroDataTable的。
1 public static MicroDataTable FillDataTable(DbDataReader reader, int pageIndex, int pageSize)
2 {
3 bool defined = false;
4
5 MicroDataTable table = new MicroDataTable();
6
7 int index = 0;
8 int beginIndex = pageSize * pageIndex;
9 int endIndex = pageSize * (pageIndex + 1) - 1;
10
11 while (reader.Read())
12 {
13 object[] values = new object[reader.FieldCount];
14
15 if (!defined)
16 {
17 for (int i = 0; i < reader.FieldCount; i++)
18 {
19 MicroDataColumn column = new MicroDataColumn()
20 {
21 ColumnName = reader.GetName(i),
22 ColumnType = reader.GetFieldType(i)
23 };
24
25 table.Columns.Add(column);
26 }
27
28 defined = true;
29 }
30
31 if (index >= beginIndex && index <= endIndex)
32 {
33 reader.GetValues(values);
34
35 table.Rows.Add(new MicroDataRow(table.Columns, values));
36 }
37
38 index++;
39 }
40
41 table.TotalCount = index;
42
43 return table;
44 }
上面這個程序,是按部就班的寫法,效率應該不太高。最近時間緊,沒有分析原先的Datatable裝載方式,以后有時間優化吧。下面給出一個當時用.net framework從datareader獲取分頁數據到datatable的程序,僅作參考。當時這段程序使用了table.beginloaddata/endloaddata方式,效率明顯有提升。
1 using (IDataReader reader = cmd.ExecuteReader(CommandBehavior.CloseConnection))
2 {
3 int fieldCount = reader.FieldCount;
4 for (int i = 0; i < fieldCount; i++)
5 {
6 table.Columns.Add(reader.GetName(i), reader.GetFieldType(i));
7 }
8
9 object[] values = new object[fieldCount];
10 int currentIndex = 0;
11 int startIndex = pageSize * pageIndex;
12 try
13 {
14 table.BeginLoadData();
15 while (reader.Read())
16 {
17 if (startIndex > currentIndex++)
18 continue;
19
20 if (pageSize > 0 && (currentIndex - startIndex) > pageSize)
21 break;
22
23 reader.GetValues(values);
24 table.LoadDataRow(values, true);
25 }
26 }
27 finally
28 {
29 table.EndLoadData();
30 try //lgy:由於連接阿里雲ADS數據庫cmd.Cancel()會報錯,所以把錯誤忽略了。
31 {
32 cmd.Cancel();
33 }
34 catch
35 {
36 }
37 reader.Close();
38 }
39 }

