关于c#:一个有趣的问题-你知道SqlDataAdapter中的Fill是怎么实现的吗

34次阅读

共计 4063 个字符,预计需要花费 11 分钟才能阅读完成。

一:背景

1. 讲故事

最近因为各方面起因换了一份工作,去了一家主营物联柜的公司,有意思的是物联柜上的终端是用 wpf 写的,代码也算是年久失修,感觉技术债还是蛮重的,前几天在调试一个 bug 的时候,看到了一段相似这样的代码:


    var dt = new DataTable();
    SqlDataAdapter adapter = new SqlDataAdapter(new SqlCommand());
    adapter.Fill(dt);

是不是很眼生哈,或者你也曾经多年不见了,犹记得那时候为了能从数据库获取数据,第一种办法就是采纳 SqlDataReader 一行一行从数据库读取,而且还要操心 Reader 的 close 问题,第二种办法为了防止麻烦, 就间接应用了本篇说到的 SqlDataAdapter,简略粗犷,啥也不必操心,对了,不晓得您是否和我一样对这个 Fill 办法很好奇呢?,它是如何将数据塞入到 DataTable 中的呢?也是用的 SqlDataReader 吗?而且 Fill 还有好几个扩大办法,哈哈,本篇就一一聊一聊,就当回顾经典啦!

二:对 Fill 办法的探索

1. 应用 dnspy 查看 Fill 源码

dnspy 小工具大家能够到 GitHub 下面去下载一下,这里就不具体说啦,接下来追一下 Fill 的最上层实现,如下代码:


        public int Fill(DataTable dataTable)
        {
            IntPtr intPtr;
            Bid.ScopeEnter(out intPtr, "<comm.DbDataAdapter.Fill|API> %d#, dataTable\n", base.ObjectID);
            int result;
            try
            {DataTable[] dataTables = new DataTable[]
                {dataTable};
                IDbCommand selectCommand = this._IDbDataAdapter.SelectCommand;
                CommandBehavior fillCommandBehavior = this.FillCommandBehavior;
                result = this.Fill(dataTables, 0, 0, selectCommand, fillCommandBehavior);
            }
            finally
            {Bid.ScopeLeave(ref intPtr);
            }
            return result;
        }

下面的代码比拟要害的一个中央就是 IDbCommand selectCommand = this._IDbDataAdapter.SelectCommand; 这里的 SelectCommand 来自于哪里呢?来自于你 new SqlDataAdapter 的时候塞入的构造函数 SqlCommand,如下代码:


        public SqlDataAdapter(SqlCommand selectCommand) : this()
        {this.SelectCommand = selectCommand;}

而后持续往下看 this.Fill 办法,代码简化后如下:


        protected virtual int Fill(DataTable[] dataTables, int startRecord, int maxRecords, IDbCommand command, CommandBehavior behavior)
        {result = this.FillInternal(null, dataTables, startRecord, maxRecords, null, command, behavior);
            
            return result;
        }

下面这段代码没啥好说的,持续往下追踪 this.FillInternal 办法,简化后如下:


        private int FillInternal(DataSet dataset, DataTable[] datatables, int startRecord, int maxRecords, string srcTable, IDbCommand command, CommandBehavior behavior)
        {
            int result = 0;
            try
            {IDbConnection connection = DbDataAdapter.GetConnection3(this, command, "Fill");
                try
                {
                    IDataReader dataReader = null;
                    try
                    {dataReader = command.ExecuteReader(behavior);
                        result = this.Fill(datatables, dataReader, startRecord, maxRecords);
                    }
                    finally
                    {if (dataReader != null)    dataReader.Dispose();}
                }
                finally
                {DbDataAdapter.QuietClose(connection, originalState);
                }
            }
            finally
            {if (flag)
                {
                    command.Transaction = null;
                    command.Connection = null;
                }
            }
            return result;
        }

大家能够认真研读一下下面的代码,挺有意思的,至多你能够获取以下两点信息:

  • 从各个 finally 中能够看到,当数据 fill 到 datatable 中之后,操作数据库的几大对象 Connection,Transaction,DataReader 都会进行敞开,你基本不须要操心。
  • this.Fill(datatables, dataReader, startRecord, maxRecords) 中能够看到,底层不出意外也是通过 dataReader.read() 一行一行读取而后塞到 DataTable中去的,不然它拿这个 dataReader 干嘛呢?不信的话能够持续往下追。

        protected virtual int Fill(DataTable[] dataTables, IDataReader dataReader, int startRecord, int maxRecords)
        {
            try
            {
                int num = 0;
                bool flag = false;
                DataSet dataSet = dataTables[0].DataSet;
                int num2 = 0;
                while (num2 < dataTables.Length && !dataReader.IsClosed)
                {DataReaderContainer dataReaderContainer = DataReaderContainer.Create(dataReader, this.ReturnProviderSpecificTypes);
                    if (num2 == 0)
                    {
                        bool flag2;
                        do
                        {flag2 = this.FillNextResult(dataReaderContainer);
                        }
                        while (flag2 && dataReaderContainer.FieldCount <= 0);    
                        }
                    }
                result = num;
            }
            return result;
        }

从下面代码能够看到,dataReader 被封装到了 DataReaderContainer 中,用 FillNextResult 判断是否还有批语句 sql,从而不便生成多个 datatable 对象,最初就是填充 DataTable,当然就是用 dataReader.Read()啦,不信你能够始终往里面追嘛,如下代码:


        private int FillLoadDataRow(SchemaMapping mapping)
        {
            int num = 0;
            DataReaderContainer dataReader = mapping.DataReader;
            
            while (dataReader.Read())
            {mapping.LoadDataRow();
                num++;
            }
            return num;
        }

到这里你应该意识到:DataReader 的性能必定比 Fill 到 DataTable 要高的太多,所以它和灵活性两者之间看您取舍了哈。

二:Fill 的其余重载办法

方才给大家介绍的是带有 DataTable 参数的重载,其实除了这个还有另外四种重载办法,如下图:


    public override int Fill(DataSet dataSet);
    public int Fill(DataSet dataSet, string srcTable);
    public int Fill(DataSet dataSet, int startRecord, int maxRecords, string srcTable);
    public int Fill(int startRecord, int maxRecords, params DataTable[] dataTables);

1. startRecord 和 maxRecords

从字面意思看就是想从指定的地位 (startRecord) 开始读,而后最多读取 maxRecords 条记录,很好了解,咱们晓得 reader() 是只读向前的,而后一起看一下源码底层是怎么实现的。

从上图中能够看出,还是很简略的哈,踢掉 startRecord 个 reader(), 而后再只读向前获取最多 maxRecords 条记录。

2. dataSet 和 srcTable

这里的 srcTable 是什么意思呢?从 vs 中看是这样的: The name of the source table to use for table mapping. 乍一看也不是特地分明,没关系,咱们间接看源码就好啦,反正我也没测试,嘿嘿。

从上图中你应该明确大略意思就是给你 dataset 中的 datatable 取名字,比方:name= 学生表 , 那么 database 中的的 tablename 顺次是: 学生表,学生表 1,学生表 2 ...,这样你就能够索引获取表的名字了哈,如下代码所示:

        DataSet dataSet = new DataSet();
        dataSet.Tables.Add(new DataTable("学生表"));
        var tb = dataSet.Tables["学生表"];

四:总结

本篇就聊这么多吧,算是解了多年之前我的一个好奇心,心愿本篇对您有帮忙。

正文完
 0