.Net Core 系列:2、ADO.Net 基礎


  目錄:

      1、環境搭建

      2、ADO.Net 基礎

      3、ASP.Net Core 基礎

      4、Entity Framework Core 基礎

      5、實現登錄注冊功能

      6、實現目錄管理功能

      7、實現文章發布、編輯、閱覽和刪除功能

      8、實現文章回復功能

      9、實現文章點贊功能

      10、正式發布文章系統


1.前言

         因為本系列是.NET Core 系列,本文中所有敘述的是基於.NET Core 1.1版本的數據訪問層接口。為什么需要強調是.Net Core 1.1呢?由於在2017年Q3發布的.NET Core 2.0中通過官網的Apis文檔可以看出,在.NET Core 2.0時代已經將.NET Framework的ADO.NET整套體系完整覆蓋。同時也讓我十分糾結,到底要不要寫這篇文章。從而造成我對整個系列的編寫產生了動搖。回頭想想,還是寫下來吧!就當是自己的一次學習筆記的記錄。

2. ADO.NET的前世今生

         ADO.NET的名稱起源於ADO(ActiveX Data Objects),是一個COM組件庫,用於在以往的Microsoft技術中訪問數據。之所以使用ADO.NET名稱,是因為Microsoft希望表明,這是在NET編程環境中優先使用的數據訪問接口。

         ADO.NET可讓開發人員以一致的方式存取資料來源(例如 SQL Server 與 XML),以及透過 OLE DB 和 ODBC 所公開的資料來源。資料共用的消費者應用程序可使用ADO.NET 來連接至這些資料來源,並且擷取、處理及更新其中所含的資料。

         ADO.NETt可將資料管理的資料存取分成不連續的元件,這些元件可分開使用,也可串聯使用ADO.NET也包含 .NET Framework 資料提供者,以用於連接資料庫、執行命令和擷取結果。這些結果會直接處理、放入ADO.NET DataSet 物件中以便利用機器操作 (Ad Hoc)的方式公開給使用者、與多個來源的資料結合,或在各層之間進行傳遞。DataSet 物件也可以與.NET Framework 資料提供者分開使用,以便管理應用程序本機的資料或來自 XML 的資料。

         ADO.NET類別 (Class) 位於 System.Data.dll 中,而且會與 System.Xml.dll 中的XML 類別整合。

         ADO.NET可為撰寫 Managed 程式碼的開發人員提供類似於ActiveX Data Objects (ADO)提供給原生元件物件模型 (Component Object Model,COM)開發人員的功能。建議使用ADO.NET而非ADO來存取.NET 應用程序中的資料。

         ADO .NET會提供最直接的方法,讓開發人員在 .NET Framework 中進行資料存取。

 

------- 摘自百度百科

 

       我什么要摘出這一段呢?因為在.NET Core 1.1里根本不存在DataSet這個類只實現了DataTable,也不存在DataAdapter。在2002-2009年這7年間,大量的應用均是使用這類適配存取的方式去編寫應用。簡單、高效、門檻低。2009年后各種ORM、MVC、MVP、MVVM、IoC等各種架構和技術開始流行。DataAdapter+DataSet+ASP.NET WEBFORM的套路開始走下神壇。

 

       而.NET Core 1.1可以說是微軟一個重要的雲計算計划的其中一個棋子。並且微軟本質的目標也並非鼓勵將已有應用向.NET Core遷移。所以在.NET Core 1.1版本只實現了一個基礎閉環。.NET Standard + .NET Core MVC + EntityFramework Core。畢竟步子邁得太大容易扯着蛋。

 

3、用基礎的劍法打出一套增、刪、改、查

     讓我們看看.NET Core 為我們提供了什么基礎劍法。

        增、刪、改、查均是SQL語句的命令,所以只要存在能向數據庫發送SQL腳本的接口則可以實現,Command,要發送腳本總要知道腳本往哪里發找到了Connection,執行完腳本數據庫向我們回發結果總要有一個承載 Reader、 Record。如果執行的是多條腳本,並且需要保證腳本一次成功,我們找到了Transaction

 

3.1  IDbConnection

       和數據庫交互,必須連接它,連接幫助指明數據庫服務器,數據庫名字,用戶名,密碼和其他需要的參數。Connection會被Command對象使用,這樣就能夠知道是在哪個數據源上面執行命令。其實我就是我們訪問數據庫時最初的那個連接數據庫字符串。

          現在很多學習編程的新同學,都是依賴百度/谷歌去直接搜索解決方法。但是學習的本質都是從官方例子開始理解的。

using System;
using System.Data;

namespace IDbConnectionSample {
   class Program {
      static void Main(string[] args) {
         IDbConnection connection;

         // First use a SqlClient connection
         connection = new System.Data.SqlClient.SqlConnection(@"Server=(localdb)\V11.0");
         Console.WriteLine("SqlClient\r\n{0}", GetServerVersion(connection));
         connection = new System.Data.SqlClient.SqlConnection(@"Server=(local);Integrated Security=true");
         Console.WriteLine("SqlClient\r\n{0}", GetServerVersion(connection));

         // Call the same method using ODBC
         // NOTE: LocalDB requires the SQL Server 2012 Native Client ODBC driver
         connection = new System.Data.Odbc.OdbcConnection(@"Driver={SQL Server Native Client 11.0};Server=(localdb)\v11.0");
         Console.WriteLine("ODBC\r\n{0}", GetServerVersion(connection));
         connection = new System.Data.Odbc.OdbcConnection(@"Driver={SQL Server Native Client 11.0};Server=(local);Trusted_Connection=yes");
         Console.WriteLine("ODBC\r\n{0}", GetServerVersion(connection));

         // Call the same method using OLE DB
         connection = new System.Data.OleDb.OleDbConnection(@"Provider=SQLNCLI11;Server=(localdb)\v11.0;Trusted_Connection=yes;");
         Console.WriteLine("OLE DB\r\n{0}", GetServerVersion(connection));
         connection = new System.Data.OleDb.OleDbConnection(@"Provider=SQLNCLI11;Server=(local);Trusted_Connection=yes;");
         Console.WriteLine("OLE DB\r\n{0}", GetServerVersion(connection));
         }

      public static string GetServerVersion(IDbConnection connection) {
         // Ensure that the connection is opened (otherwise executing the command will fail)
         ConnectionState originalState = connection.State;
         if (originalState != ConnectionState.Open)
            connection.Open();
         try {
            // Create a command to get the server version
            // NOTE: The query's syntax is SQL Server specific
            IDbCommand command = connection.CreateCommand();
            command.CommandText = "SELECT @@version";
            return (string)command.ExecuteScalar();
         }
         finally {
            // Close the connection if that's how we got it
            if (originalState == ConnectionState.Closed)
               connection.Close();
         }
      }
   }
}

  這個例子主要是演示了三種鏈接驅動的使用方式分別是:SqlClient、ODBC、OLE DB 但是很遺憾NET Core 1.1只提供了SqlClient,但是在官方的Apis2.0文檔中是可以找ODBC、OLE DB、OracleClient。除了官方的驅動開源的驅動提供NET Core 1.1的還有Npgsql(postgresql數據庫),Mysql的官方驅動尚未支持.NET Core,在nuget上可以找到Mysql.Data(8.0.8)預覽版的驅動可以支持.NET Core 1.1

 

下面我們以Npgsql來演示一次上述例子。

using System;
using System.Data;
using Npgsql;

namespace IDbConnectionSample
{
    class Program
    {
        static void Main(string[] args)
        {
            IDbConnection connection;

            connection = new NpgsqlConnection("Server=192.168.1.41;Port=5432;User Id=postgres;Password=postgres;");
            Console.WriteLine("Npgsql:\r\n{0}", GetServerVersion(connection));
            Console.ReadKey();
        }

        public static string GetServerVersion(IDbConnection connection)
        {
            ConnectionState originalState = connection.State;
            if (originalState != ConnectionState.Open)
                connection.Open();
            try
            {
                IDbCommand command = connection.CreateCommand();
                command.CommandText = "SELECT version()";//pgsql獲取版本的函數是Version()
                return (string)command.ExecuteScalar();
            }
            finally
            {
                if (originalState == ConnectionState.Closed)
                    connection.Close();
            }
        }
    }
}

 

3.2  IDbCommand

        成功與數據建立連接后,就可以用Command對象來執行查詢、修改、插入、刪除等命令;Command對象常用的方法有ExecuteReader()方法、ExecuteScalar()方法和ExecuteNonQuery()方法;插入數據可用ExecuteNonQuery()方法來執行插入命令。

 

看例子:

using System;
using System.Data;
using Npgsql;
using System.Collections.Generic;

namespace IDbConnectionSample
{
    class Program
    {
        static void Main(string[] args)
        {
            IDbConnection connection;

            connection = new NpgsqlConnection("Server=192.168.1.41;Port=5432;User Id=postgres;Password=postgres;Database=test");
            Console.WriteLine("Npgsql:\r\n{0}", GetServerVersion(connection));

            Console.WriteLine("InsertUser:\r\n{0}", InsertUser(connection, "InsertUser" + DateTime.Now.Ticks));
            
            foreach (var item in AllUserName(connection))
            {
                Console.WriteLine("AllUserName:\r\n{0}", item);
            }
            Console.ReadKey();
        }

        /// <summary>
        /// 查單值
        /// </summary>
        /// <param name="connection"></param>
        /// <returns></returns>
        public static string GetServerVersion(IDbConnection connection)
        {
            ConnectionState originalState = connection.State;
            if (originalState != ConnectionState.Open)
                connection.Open();
            try
            {
                IDbCommand command = connection.CreateCommand();
                command.CommandText = "SELECT version()";
                return (string)command.ExecuteScalar();
            }
            finally
            {
                if (originalState == ConnectionState.Closed)
                    connection.Close();
            }
        }

        /// <summary>
        /// 增加一個用戶
        /// </summary>
        /// <param name="connection"></param>
        /// <returns></returns>
        public static bool InsertUser(IDbConnection connection, string userName)
        {
            ConnectionState originalState = connection.State;
            if (originalState != ConnectionState.Open)
                connection.Open();
            try
            {
                IDbCommand command = connection.CreateCommand();
                //實際項目不要使用這種方式。僅演示使用,為什么?自己探索。
                //自定義的user表需要加上架構限定名
                command.CommandText = "INSERT INTO public.user(name) VALUES ('" + userName + "')";
                return command.ExecuteNonQuery() > 0;
            }
            finally
            {
                if (originalState == ConnectionState.Closed)
                    connection.Close();
            }
        }

        /// <summary>
        /// 查找所有用戶的名稱
        /// </summary>
        /// <param name="connection"></param>
        /// <returns></returns>
        public static IList<string> AllUserName(IDbConnection connection)
        {
            IList<string> allUserName = new List<string>();
            ConnectionState originalState = connection.State;
            if (originalState != ConnectionState.Open)
                connection.Open();
            try
            {
                IDbCommand command = connection.CreateCommand();
                //自定義的user表需要加上架構限定名
                command.CommandText = "select name from public.user";
                var reader = command.ExecuteReader();
                while (reader.Read())
                {
                    //根據select的語句中第0位是name
                    allUserName.Add(reader.GetString(0));
                }

            }
            finally
            {
                if (originalState == ConnectionState.Closed)
                    connection.Close();
            }

            return allUserName;
        }
    }
}

執行結果:

 

3.3  IDataReader

        許多數據操作要求開發人員只是讀取一串數據。DataReader對象允許開發人員獲得從Command對象的SELECT語句得到的結果。考慮性能的因素,從DataReader返回的數據都是快速的且只是“向前”的數據流。這意味着開發人員只能按照一定的順序從數據流中取出數據。這對於速度來說是有好處的,但是如果開發人員需要操作數據,並且這些數據含有復雜邏輯,最好還是先將數據轉化為DataTable(1.1沒有DataSet)、結構體或簡單失血模型。因為DataReader和數據庫間產生着持續的鏈接,知道度完后才會關閉連接。如果在read的過程中進行復雜的邏輯操作,那么對於並發來說那將是一個災難。

 

從上圖中沒有找到任何讀取數據列的操作,主要是因為IDataReader是繼承IDataRecord,主要的讀取操作均實現於IDataRecord

 

 

4、實現一個簡單的SqlHelp類

 我們定義一個簡單的ISqlHelper接口,為什么要定義接口,主要是出於考慮到不同數據庫可以擴展出不同的實現。

 

using System;
using System.Collections.Generic;
using System.Data;

namespace IDbConnectionSample
{
    /// <summary>
    /// Sql操作助手
    /// </summary>
    public interface ISqlHelper
    {
        /// <summary>
        /// 當前連接字符串
        /// </summary>
        IDbConnection Connection { get; set; }

        /// <summary>
        /// 查詢
        /// </summary>
        /// <typeparam name="T">實體類</typeparam>
        /// <param name="sqlText">Sql文本</param>
        /// <param name="commandType">命令類型</param>
        /// <param name="parms">參數</param>
        /// <returns>返回泛型的實體對象</returns>
        IEnumerable<T> Query<T>(string sqlText, CommandType commandType = CommandType.Text, params IDbDataParameter[] parms) where T :class;

        /// <summary>
        /// 查詢
        /// </summary>
        /// <typeparam name="T">實體類</typeparam>
        /// <param name="sqlText">Sql文本</param>
        /// <param name="func">返回IDbDataParameter[]的委托</param>
        /// <param name="commandType">命令類型</param>
        /// <returns>返回泛型的實體對象</returns>
        IEnumerable<T> Query<T>(string sqlText, Func<IDbDataParameter[]> func, CommandType commandType = CommandType.Text) where T : class;

        /// <summary>
        /// 查詢首行首列
        /// </summary>
        /// <param name="sqlText">Sql文本</param>
        /// <param name="commandType">命令類型</param>
        /// <param name="parms">參數</param>
        /// <returns>首行首列的值</returns>
        object Scalar(string sqlText, CommandType commandType = CommandType.Text, params IDbDataParameter[] parms);

        /// <summary>
        /// 查詢首行首列
        /// </summary>
        /// <param name="sqlText">Sql文本</param>
        /// <param name="func">返回IDbDataParameter[]的委托</param>
        /// <param name="commandType">命令類型</param>
        /// <returns>首行首列的值</returns>
        object Scalar(string sqlText, Func<IDbDataParameter[]> func, CommandType commandType = CommandType.Text);

        /// <summary>
        /// 查詢首行首列
        /// </summary>
        /// <typeparam name="T">僅支持基礎類型</typeparam>
        /// <param name="sqlText">Sql文本</param>
        /// <param name="commandType">命令類型</param>
        /// <param name="parms">參數</param>
        /// <returns></returns>
        T Scalar<T>(string sqlText, CommandType commandType = CommandType.Text, params IDbDataParameter[] parms) where T : struct;

        /// <summary>
        /// 查詢首行首列
        /// </summary>
        /// <typeparam name="T">僅支持基礎類型</typeparam>
        /// <param name="sqlText">Sql文本</param>
        /// <param name="func">返回IDbDataParameter[]的委托</param>
        /// <param name="commandType">命令類型</param>
        /// <returns>首行首列的值</returns>
        T Scalar<T>(string sqlText, Func<IDbDataParameter[]> func, CommandType commandType = CommandType.Text) where T : struct;


        /// <summary>
        /// 執行腳本
        /// </summary>
        /// <param name="sqlText">Sql文本</param>
        /// <param name="commandType">命令類型</param>
        /// <param name="parms">參數</param>
        /// <returns>返回影響行數</returns>
        int Execute(string sqlText, CommandType commandType = CommandType.Text, params IDbDataParameter[] parms);


        /// <summary>
        /// 執行腳本
        /// </summary>
        /// <param name="sqlText">Sql文本</param>
        /// <param name="func">返回IDbDataParameter[]的委托</param>
        /// <param name="commandType">命令類型</param>
        /// <returns></returns>
        int Execute(string sqlText, Func<IDbDataParameter[]> func, CommandType commandType = CommandType.Text);
    }
}

 

//SqlHelper.cs
using System;
using System.Collections.Generic;
using System.Data;

namespace IDbConnectionSample
{
    public class SqlHelper : ISqlHelper, IDisposable
    {
        public SqlHelper(IDbConnection connection)
        {
            Connection = connection;
        }

        public IDbConnection Connection { get; set; }

        public void Dispose()
        {
            Connection.Dispose();
        }


        private IDbCommand buildCommand(string sqlText, CommandType commandType, params IDbDataParameter[] parms)
        {
            if (Connection == null)
                throw new Exception("IDbConnection is NULL");

            IDbCommand command = Connection.CreateCommand();
            command.CommandText = sqlText;
            command.CommandType = commandType;
            if (parms != null && parms.Length > 0)
            {
                foreach (var item in parms)
                {
                    command.Parameters.Add(item);
                }
            }

            return command;
        }

        public int Execute(string sqlText, CommandType commandType = CommandType.Text, params IDbDataParameter[] parms)
        {
            if (Connection == null)
                throw new Exception("IDbConnection is NULL");

            ConnectionState originalState = Connection.State;

            if (originalState != ConnectionState.Open)
                Connection.Open();
            try
            {
                return buildCommand(sqlText, commandType, parms).ExecuteNonQuery();
            }
            finally
            {
                if (originalState == ConnectionState.Closed)
                    Connection.Close();
            }
        }



        public int Execute(string sqlText, Func<IDbDataParameter[]> func, CommandType commandType = CommandType.Text)
        {
            return Execute(sqlText, commandType, func());
        }

        public IEnumerable<T> Query<T>(string sqlText, CommandType commandType = CommandType.Text, params IDbDataParameter[] parms) where T : class
        {
            if (Connection == null)
                throw new Exception("IDbConnection is NULL");

            ConnectionState originalState = Connection.State;

            if (originalState != ConnectionState.Open)
                Connection.Open();
            try
            {
                var reader = buildCommand(sqlText, commandType, parms).ExecuteReader();
                while (reader.Read())
                    yield return EntityBuilder<T>.GenerateByDataRecord(reader);
            }
            finally
            {
                if (originalState == ConnectionState.Closed)
                    Connection.Close();
            }
        }

        public IEnumerable<T> Query<T>(string sqlText, Func<IDbDataParameter[]> func, CommandType commandType = CommandType.Text) where T : class
        {
            return Query<T>(sqlText, commandType, func());
        }

        public object Scalar(string sqlText, CommandType commandType = CommandType.Text, params IDbDataParameter[] parms)
        {
            if (Connection == null)
                throw new Exception("IDbConnection is NULL");

            ConnectionState originalState = Connection.State;

            if (originalState != ConnectionState.Open)
                Connection.Open();
            try
            {
                return buildCommand(sqlText, commandType, parms).ExecuteScalar();
            }
            finally
            {
                if (originalState == ConnectionState.Closed)
                    Connection.Close();
            }
        }

        public object Scalar(string sqlText, Func<IDbDataParameter[]> func, CommandType commandType = CommandType.Text)
        {
            return Scalar(sqlText, commandType, func());
        }

        public T Scalar<T>(string sqlText, CommandType commandType = CommandType.Text, params IDbDataParameter[] parms) where T : struct
        {
            return (T)Scalar(sqlText, commandType, parms);
        }

        public T Scalar<T>(string sqlText, Func<IDbDataParameter[]> func, CommandType commandType = CommandType.Text) where T : struct
        {
            return Scalar<T>(sqlText, commandType, func());
        }
    }
}

 

//PGSqlHelper.cs
using Npgsql;

namespace IDbConnectionSample
{
    public class PGSqlHelper : SqlHelper
    {
        public PGSqlHelper(string connectionString) : base(new NpgsqlConnection(connectionString)) { }
    }
}
//MSSqlHelper.cs
namespace IDbConnectionSample
{
   public class MSSqlHelper: SqlHelper
    {
        public MSSqlHelper(string connectionString) : base(new System.Data.SqlClient.SqlConnection(connectionString)) { }
    }
}

通過接口,基類、子類,則實現了基礎的SqlHelper,在ORM大行其道的時候是否簡單的SqlHelper就沒有用武之地呢?其實不是,在日常的維護中我們通常需要編寫大量的小工具去跨越權限修復各種因為測試遺漏的數據錯誤。這種情況下我們需要的是快狠准,通過簡單的子類重載則可以完成不同數據庫的Helper。SqlHelper的威力就體現出來了。

5、總結

 無論技術怎么發展其實都是從最基礎的技能出發,掌握基礎技能很重要。不忘初心,方得始終


免責聲明!

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



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