1. 程式人生 > >EF Core下 怎麼跑sql語句

EF Core下 怎麼跑sql語句

    興致來了,多寫一篇吧。 

    所有轉netcore的小夥伴們都發現了: ef core跟以前的ef差距非常大,view(檢視)無法通過dbfirst生成了,儲存過程也一樣(雖然我現在開始轉codefirst了)。  然而,如果真的想直接執行sql語句怎麼辦?  我們發現context下的Database屬性跟以前也不一樣了,只能做些事務操作,沒有執行sql了。可以執行sql的變成了每張具體的表(DbSet<T>)下面的FromSql方法了(需要顯式引用Microsoft.EntityFrameworkCore名稱空間)。 但是這個方法存在問題,只能返回該表型別的結果,無法返回任意型別。

    so,跟大家一樣,我去網上搜搜解決方案。查到了一個方案。

    然而,為了響應ef本身跨資料庫種類的設計要求,我覺得應該做一個可以自行根據資料庫型別判斷需要執行什麼語句的元件,所以我給我們親愛的dbcontext寫了一個擴充套件:

    首先請自覺nuget拉一下包 Microsoft.EntityFrameworkCore.Relational。

    整個擴充套件的大體思路是: 針對一次查詢,將各種資料庫的查詢語句封裝到一起,在執行時自動判斷當前連線的資料庫環境,執行相應的語句。這樣不破壞依賴注入的原則,需要的是程式設計人員根據可能連線的資料庫種類,寫對應庫的查詢語句。

    另外這裡還用了一個神奇的技術(我自己早就在使用了),以方便查詢引數的操作。就是使用匿名型別來提供查詢語句引數,如"select * from abc where id between @start and @end"語句,只需要提供引數 new {start = 1, end=100} 作為引數傳入即可。不用再去new 一個parameter,每次填一堆。

    廢話夠多了,直接上程式碼了:

using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using MySql.Data.MySqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.SqlClient;
using System.Text;

namespace SS.Data.EntityFramework.Core
{
    /// <summary>
    /// 資料庫查詢語句
    /// </summary>
    public class DbContextSqlQueryCommand
    {
        /// <summary>
        /// 使用不含引數的查詢語句
        /// </summary>
        /// <param name="query"></param>
        public DbContextSqlQueryCommand(string query)
        {
            Query = query;
        }
        /// <summary>
        /// 使用包含引數的查詢語句
        /// </summary>
        /// <param name="query"></param>
        public DbContextSqlQueryCommand(string query, object @params)
        {
            Query = query;
            Parameters = @params;
        }
        /// <summary>
        /// 查詢語句
        /// </summary>
        public string Query { get; set; }
        /// <summary>
        /// 引數
        /// </summary>
        public object Parameters { get; set; }
    }

    /// <summary>
    /// 資料庫查詢語句集合
    /// </summary>
    public class DbContextSqlQueryCommands
    {
        /// <summary>
        /// 資料庫為SqlServer時使用的查詢語句
        /// </summary>
        public DbContextSqlQueryCommand Sql { get; set; }
        /// <summary>
        /// 資料庫為MySql時使用的查詢語句
        /// </summary>
        public DbContextSqlQueryCommand MySql { get; set; }
        /// <summary>
        /// 資料庫為InMemory時使用的查詢語句
        /// </summary>
        public DbContextSqlQueryCommand InMemory { get; set; }
        /// <summary>
        /// 資料庫為Sqlite時使用的查詢語句
        /// </summary>
        public DbContextSqlQueryCommand Sqlite { get; set; }
    }

    /// <summary>
    /// 資料庫型別
    /// </summary>
    public enum DbContextType
    {
        InMemory = 0,
        SqlServer = 1,
        MySql = 2,
        Sqlite = 3,
    }

    /// <summary>
    /// EF上下文擴充套件
    /// </summary>
    public static class DbContextExtensions
    {
        //拼接引數
        private static void combineParams(DbContextType type, ref DbCommand command, object @params = null)
        {
            if (@params != null)
            {
                Type paramType;
                string prefix;
                switch (type)
                {
                    case DbContextType.InMemory:
                        throw new Exception("未實現的資料庫型別");
                    case DbContextType.SqlServer:
                        paramType = typeof(SqlParameter);
                        prefix = "@";
                        break;
                    case DbContextType.MySql:
                        paramType = typeof(MySqlParameter);
                        prefix = "@";
                        break;
                    case DbContextType.Sqlite:
                        paramType = typeof(SqliteParameter);
                        prefix = "@";
                        break;
                    default:
                        throw new Exception("未實現的資料庫型別");
                }
                foreach (var param in @params.GetType().GetProperties())
                {
                    var paramItem = Activator.CreateInstance(paramType, $"{prefix}{param.Name}", (object)param.GetValue(@params));
                    command.Parameters.Add(paramItem);
                }
            }
        }
        //建立命令(同時返回連線符)
        private static DbCommand createCommand(DbContext context, DbContextSqlQueryCommands commands, out DbConnection connection)
        {
            var conn = context.Database.GetDbConnection();
            connection = conn;
            conn.Open();
            var cmd = conn.CreateCommand();
            if (commands.Sqlite != null && context.Database.IsSqlite())
            {
                cmd.CommandText = commands.Sqlite.Query;
                combineParams(DbContextType.Sqlite, ref cmd, commands.Sqlite.Parameters);
            }
            else if(commands.MySql != null && context.Database.IsMySql())
            {
                cmd.CommandText = commands.MySql.Query;
                combineParams(DbContextType.MySql, ref cmd, commands.MySql.Parameters);
            }
            else if (commands.Sql != null && context.Database.IsSqlServer())
            {
                cmd.CommandText = commands.Sql.Query;
                combineParams(DbContextType.SqlServer, ref cmd, commands.Sql.Parameters);
            }
            else if (commands.InMemory != null)
            {
                throw new NotImplementedException();
            }
            return cmd;
        }

        /// <summary>
        /// 執行sql語句,返回受影響行數
        /// </summary>
        /// <param name="context">EF上下文</param>
        /// <param name="commands">資料庫查詢語句集合</param>
        /// <returns>受影響行數</returns>
        public static int Exec(this DbContext context, DbContextSqlQueryCommands commands)
        {
            var command = createCommand(context, commands, out var conn);
            var rsl = command.ExecuteNonQuery();
            conn.Close();
            return rsl;
        }

        /// <summary>
        /// 查詢資料庫
        /// </summary>
        /// <param name="context">EF上下文</param>
        /// <param name="commands">資料庫查詢語句集合</param>
        /// <returns>資料DataTable</returns>
        public static DataTable Query(this DbContext context, DbContextSqlQueryCommands commands)
        {
            var command = createCommand(context, commands, out var conn);
            var reader = command.ExecuteReader();
            DataTable dt = new DataTable();
            dt.Load(reader);
            reader.Close();
            conn.Close();
            return dt;
        }

        /// <summary>
        /// 查詢資料庫,返回多個查詢結果集
        /// </summary>
        /// <param name="context">EF上下文</param>
        /// <param name="commands">資料庫查詢語句集合</param>
        /// <returns>資料DataSet</returns>
        public static DataSet QuerySet(this DbContext context, DbContextSqlQueryCommands commands)
        {
            var dt = Query(context, commands);
            var ds = new DataSet();
            ds.Tables.Add(dt);
            return ds;
        }

        /// <summary>
        /// 查詢資料庫,返回IEnumerable的強型別資料
        /// </summary>
        /// <typeparam name="T">查詢結果型別</typeparam>
        /// <param name="context">EF上下文</param>
        /// <param name="commands">資料庫查詢語句集合</param>
        /// <returns>IEnumerable的強型別資料</returns>
        public static IEnumerable<T> Query<T>(this DbContext context, DbContextSqlQueryCommands commands)
        {
            var dt = Query(context, commands);
            return dt.ToEnumerable<T>();
        }

        /// <summary>
        /// 查詢資料庫,返回第一條資料
        /// </summary>
        /// <param name="context">EF上下文</param>
        /// <param name="commands">資料庫查詢語句集合</param>
        /// <returns>查詢到的第一條資料或null</returns>
        public static DataRow QueryOne(this DbContext context, DbContextSqlQueryCommands commands)
        {
            var dt = Query(context, commands);
            return dt.Rows.Count > 0 ? dt.Rows[0] : null;
        }

        /// <summary>
        /// 查詢資料庫,返回第一條強型別資料
        /// </summary>
        /// <typeparam name="T">查詢結果型別</typeparam>
        /// <param name="context">EF上下文</param>
        /// <param name="commands">資料庫查詢語句集合</param>
        /// <returns>查詢到的第一條強型別資料</returns>
        public static T QueryOne<T>(this DbContext context, DbContextSqlQueryCommands commands)
        {
            var dr = QueryOne(context, commands);
            return dr.ToObject<T>();
        }

        /// <summary>
        /// 查詢資料庫,返回唯一資料
        /// </summary>
        /// <param name="context">EF上下文</param>
        /// <param name="commands">資料庫查詢語句集合</param>
        /// <returns>查詢到的唯一資料</returns>
        public static object QueryObject(this DbContext context, DbContextSqlQueryCommands commands)
        {
            var command = createCommand(context, commands, out var conn);
            var rsl = command.ExecuteScalar();
            conn.Close();
            return rsl;
        }

        /// <summary>
        /// 查詢資料庫,返回唯一強型別資料
        /// </summary>
        /// <typeparam name="T">查詢結果型別</typeparam>
        /// <param name="context">EF上下文</param>
        /// <param name="commands">資料庫查詢語句集合</param>
        /// <returns>查詢到的唯一強型別資料</returns>
        public static T QueryObject<T>(this DbContext context, DbContextSqlQueryCommands commands)
        {
            return (T)QueryObject(context, commands);
        }
    }
}

    這裡並未提供ToObject和ToEnumerable等擴充套件方法的實現,這些方法只是簡單的將DataTable或者DataRow轉換成強型別,利用反射可以輕鬆做到,有興趣的朋友可以自己實現一下。 或者maybe我會在後面的文章裡貼一下。

    這個擴充套件是針對DbContext的擴充套件,暴露的方法大體上是:Exec 返回影響行數,  Query 返回查詢的列表,可以通過泛型過載Query<T>直接返回強型別的IEnumerable介面物件,QueryOne返回一行,同理QueryOne<T>返回強型別物件。QueryObject返回object,同理QueryObject<T>返回簡單型別值。

    具體用法示例:

var list = db.Query<xxxxxxxClass>(new DbContextSqlQueryCommands
        {
            //呼叫儲存過程
            Sql = new DbContextSqlQueryCommand(@"exec GetXXXProcedure @ids", new
            {
                ids = string.Join(",", cids),
            })
        });
    so, that's it.   好好工作,天天向太陽……

----------------------------------------華麗的分割線-------------------------------------

    補充一句,此擴充套件依賴Microsoft.EntityFrameWorkCore.xxx 的一堆,包括.SqlServer, .Sqlite,.InMemory等,其中Mysql微軟未提供官方庫,因而用了Pomelo.EntityFrameworkCore.MySql這個第三方庫。
    另外一點就是,上述程式碼其實未實現InMemory的庫的操作,主要是我還沒搞清InMemory的庫到底怎麼用