【續】5年後,咱們爲何要從 Entity Framework 轉到 Dapper 工具?

前言

上一篇文章收穫了 140 多條評論,這是咱們始料未及的。html

向來有爭議的話題都是公說公的理,婆說婆的理,Entity Framework的愛好者對此能夠說是嗤之以鼻,不屑一顧,而Dapper愛好者則是舉雙手同意,閱之大快人心。sql

每一個人不一樣的閱歷,社會經驗,甚至對簡繁的偏見都會影響對此事的見解,凡事都有優劣,取其精華而棄之糟泊,方爲上策。數據庫

這篇文章則將目光聚焦到Dapper。數組

Dapper是如此的簡單,她只提供了 3 個幫助函數:瀏覽器

  1. 執行一個查詢,將結果映射到一個強類型列表
  2. 執行一個查詢,將結果映射到一個動態對象列表
  3. 執行一個命令,不返回結果

而在實際的項目中,咱們可能只會用到強類型列表,因此上面列出的 3 個幫助函數只會用到 2 個。app

有人說了,簡單其實就意味着複雜,的確如此。ide

過少的封裝意味着每次可能要書寫過多的重複代碼,所以每一個Dapper開發者可能都會自行擴展一些用着順手的方法,也就不足爲奇了,俗話說一千我的眼裏有一千個哈姆雷特。函數

下面我會分享在將 AppBoxPro 從 EntityFramework 遷移到 Dapper 中遇到的問題,以及解決方法,其中也包含個人小小封裝,但願你能喜歡。ui

下面是 AppBoxPro.Dapper 的項目開發截圖:spa

 

正文

模型的約定

咱們對模型有兩個約定:

1. IKeyID接口

2. NotMapped特性

來看一下 User 模型的聲明:

public class User : IKeyID
{
    [Key]
    public int ID { get; set; }

    [Required, StringLength(50)]
    public string Name { get; set; }

    [Required, StringLength(100)]
    public string Email { get; set; }

    public int? DeptID { get; set; }


    [NotMapped]
    public string UserDeptName { get; set; }
}

其中 IKeyID 是一個接口,定義了模型類必須包含名爲 ID 的屬性,這個接口是爲了計算 FineUIPro 控件中模擬樹的下拉列表和表格的數據源。

NotMapped特性代表這個屬性沒有數據庫映射,僅僅做爲一個內存中使用的屬性,通常有兩個用途:

1. 表關聯屬性,好比 User 模型中的 UserDeptName 屬性,在數據庫檢索時能夠經過 inner join 將 Dept 表的 Name 屬性映射於此。

2. 內存中計算的值,好比在 Dept 模型中的 TreeLevel, Enabled, IsTreeLeaf,用於在模擬樹的表格中肯定節點的層次結構和節點屬性。

一個請求一個數據庫鏈接

若是你查閱 Dapper 的文檔,你會發現一個常見的操做代碼段:

using (var conn = new MySqlConnection(connectionString))
{
    connection.Open();
    
    var users = conn.Query<User>("select * from users");

    // ...
}

雖然看起來簡單,可是若是每個地方都有加個 using 代碼段,勢必也會影響觀感和書寫體驗。

另外一方面,一個縮進的代碼段會建立一個變量做用域,有時咱們可能會但願在 using 外部獲取某個變量,這就變成了:

IEnumerable<User> users;

using (var conn = new MySqlConnection(connectionString))
{
    connection.Open();
    
    users = conn.Query<User>("select * from users");

    // ...
}

這樣寫起來就會感受磕磕絆絆,一點都不美好了。

爲了簡化代碼,咱們遵循以前的邏輯,一個請求一個數據庫鏈接,將 IDbConnection  保存到 HttpContext 上下文中:

public static IDbConnection DB
{
    get
    {
        if (!HttpContext.Current.Items.Contains("__AppBoxProContext"))
        {
            HttpContext.Current.Items["__AppBoxProContext"] = GetDbConnection();
        }
        return HttpContext.Current.Items["__AppBoxProContext"] as IDbConnection;
    }
}
public static IDbConnection GetDbConnection()
{
    IDbConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings["MySQL"].ToString());

    connection.Open();

    return connection;
}

而後在請求結束時銷燬這個鏈接,在 Global.asax 中:

protected virtual void Application_EndRequest()
{
    var context = HttpContext.Current.Items["__AppBoxProContext"] as IDbConnection;
    if (context != null)
    {
        context.Dispose();
    }
}

通過這個簡單的封裝,上面的獲取用戶列表的代碼能夠直接寫了:

var users = conn.Query<User>("select * from users");

經過ID檢索對象

在項目中,咱們可能常常須要經過 ID 來檢索對象,在 Dapper 中實現很簡單:

User current = DB.QuerySingleOrDefault<User>("select * from users where ID = @UserID", new { UserID = id });

可是因爲這個操做常常用到,咱們可能須要屢次的拷貝粘貼,而僅僅修改其中的幾個字符串。

當事情變得再也不美好時,咱們就要重構了,此次的提取公共方法沒有任何難度:

protected T FindByID<T>(int paramID)
{
    return FindByID<T>(DB, paramID);
}

protected T FindByID<T>(IDbConnection conn, int paramID)
{
    // 約定:類型 User 對應的數據庫表名 users
    var tableName = typeof(T).Name.ToLower() + "s";

    return conn.QuerySingleOrDefault<T>("select * from "+ tableName +" where ID = @ParamID", new { ParamID = paramID });
}

能夠看到其中的註釋,一個模型類到數據庫表的約定:User 模型對應於數據庫表名 users,這個約定有助於咱們使用泛型,將參數強類型化(User)而無需傳遞字符串(users)。

通過此次的改造,經過ID檢索對象就簡單多了:

User current = FindByID<User>(id);

相關頁面展現(用戶編輯):

插入和更新

插入和更新是常見的數據庫操做,好比對菜單項的操做涉及對 menus 表的插入和更新:

Menu item = new Menu();
item.Name = tbxName.Text.Trim();
item.NavigateUrl = tbxUrl.Text.Trim();
item.SortIndex = Convert.ToInt32(tbxSortIndex.Text.Trim());
item.Remark = tbxRemark.Text.Trim();

DB.Execute("insert menus(Name, NavigateUrl, SortIndex, ImageUrl, Remark, ParentID, ViewPowerID) values (@Name, @NavigateUrl, @SortIndex, @ImageUrl, @Remark, @ParentID, @ViewPowerID);", item);

首先初始化一個 Menu 模型對象,而後從頁面上獲取屬性值並賦值到模型對象,最後經過 Dapper 提供的 Execute 方法執行插入操做。

相應的,更新操做須要首先經過菜單ID獲取菜單模型對象,而後更新數據庫:

Menu item = FindByID<Menu>(menuID);
item.Name = tbxName.Text.Trim();
item.NavigateUrl = tbxUrl.Text.Trim();
item.SortIndex = Convert.ToInt32(tbxSortIndex.Text.Trim());
item.ImageUrl = tbxIcon.Text;
item.Remark = tbxRemark.Text.Trim();

DB.Execute("update menus set Name = @Name, NavigateUrl = @NavigateUrl, SortIndex = @SortIndex, ImageUrl = @ImageUrl, Remark = @Remark, ParentID = @ParentID, ViewPowerID = @ViewPowerID where ID = @ID;", item);

上面的插入和更新操做存在兩個不方便的地方:

1. SQL語句中要包含多個要更新的屬性,容易遺漏和寫錯

2. 插入和更新的屬性列表相同時,寫法卻徹底不一樣,不方便拷貝粘貼

爲了克服上面兩個弱點,咱們對插入更新進行了簡單的封裝,爲了避免手工填寫屬性列表,咱們須要一個從模型類讀取屬性列表的方法:

private string[] GetReflectionProperties(object instance)
{
    var result = new List<string>();
    foreach (PropertyInfo property in instance.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public))
    {
        var propertyName = property.Name;
        // NotMapped特性
        var notMappedAttr = property.GetCustomAttribute<NotMappedAttribute>(false);
        if (notMappedAttr == null && propertyName != "ID")
        {
            result.Add(propertyName);
        }
    }
    return result.ToArray();
}

上面函數經過反射獲取實例對應模型類(instance.GetType())的屬性列表(GetProperties()),而後過濾掉 ID 屬性和擁有 NotMapped 標註的屬性,最後返回屬性數組。

對插入操做的封裝:

protected void ExecuteInsert<T>(object instance,  params string[] fields)
{
    return ExecuteInsert<T>(DB, instance, fields);
}

protected void ExecuteInsert<T>(IDbConnection conn, object instance, params string[] fields)
{
    // 約定:類型 User 對應的數據庫表名 users
    string tableName = typeof(T).Name.ToLower() + "s";

    if (fields.Length == 0)
    {
        fields = GetReflectionProperties(instance);
    }

    var fieldsSql1 = String.Join(",", fields);
    var fieldsSql2 = String.Join(",", fields.Select(field => "@" + field));

    var sql = String.Format("insert {0} ({1}) values ({2});", tableName, fieldsSql1, fieldsSql2);

    return conn.Execute(sql, instance);
}

ExecuteInsert 方法接受以下參數:

1. 類型 T:經過模型類名稱獲取數據庫表名,這是一個命名約定

2. instance:模型實例,須要插入到數據對應表中

3. fields:這是一個可變參數,若是未傳入 fields 參數,則經過前面定義的 GetReflectionProperties 函數獲取模型類的所有屬性列表

最後,通過簡單的字符串拼接,就能方便的生成須要的SQL語句,並執行 Dapper 的 Execute 來插入數據了。

使用 ExecuteInsert 方法,咱們能夠將上面的插入操做簡化爲:

ExecuteInsert<Menu>(item, "Name", "NavigateUrl", "SortIndex", "ImageUrl", "Remark", "ParentID", "ViewPowerID");

或者,直接這樣寫:

ExecuteInsert<Menu>(item);

是否是很方便。

 

一樣,對更新的操做也是相似的,只不過在封裝時拼接SQL字符串的邏輯稍微不一樣:

protected void ExecuteUpdate<T>(object instance,  params string[] fields)
{
    return ExecuteUpdate<T>(DB, instance, fields);
}

protected void ExecuteUpdate<T>(IDbConnection conn, object instance, params string[] fields)
{
    // 約定:類型 User 對應的數據庫表名 users
    string tableName = typeof(T).Name.ToLower() + "s";

    if (fields.Length == 0)
    {
        fields = GetReflectionProperties(instance);
    }

    var fieldsSql = String.Join(",", fields.Select(field => field + " = @" + field));

    var sql = String.Format("update {0} set {1} where ID = @ID", tableName, fieldsSql);

    return conn.Execute(sql, instance);
}

使用封裝後的 ExecuteUpdate 方法,上面的更新操做能夠簡化爲:

ExecuteUpdate<Menu>(item);

相關頁面展現(用戶角色頁面):

插入後返回自增ID

有時,插入新的數據以後,咱們須要當即獲取新插入數據的ID屬性,方便後續的數據庫操做,這就要對上面的 ExecuteInsert 進行改造,在 insert 語句以後加上以下SQL語句:

select last_insert_id();

上面的SQL語句僅適用於 MySQL 數據庫,固然對於其餘數據庫也不難支持,後面會講解。更新後的 ExecuteInsert 方法以下:

protected int ExecuteInsert<T>(object instance,  params string[] fields)
{
    return ExecuteInsert<T>(DB, instance, fields);
}

protected int ExecuteInsert<T>(IDbConnection conn, object instance, params string[] fields)
{
    // 約定:類型 User 對應的數據庫表名 users
    string tableName = typeof(T).Name.ToLower() + "s";

    if (fields.Length == 0)
    {
        fields = GetReflectionProperties(instance);
    }

    var fieldsSql1 = String.Join(",", fields);
    var fieldsSql2 = String.Join(",", fields.Select(field => "@" + field));

    var sql = String.Format("insert {0} ({1}) values ({2});", tableName, fieldsSql1, fieldsSql2);
    
    sql += "select last_insert_id();";

    return conn.QuerySingle<int>(sql, instance);
}

調用時,能夠直接拿到新增行的ID,而後執行其餘數據庫操做:

// 插入用戶
var userID = ExecuteInsert<User>(item);

// 更新用戶所屬角色
DB.Execute("insert roleusers (UserID, RoleID) values (@UserID, @RoleID)", new { UserID = userID, RoleID = 101 });

過濾,分頁和排序

分頁和排序是使用 Dapper 的一個攔路虎,由於不少初學者一看到 Dapper 竟然沒有內置的分頁功能就放棄了,至少對於 5 年前的我也遭遇了一樣的經歷。

這是徹底沒有必要的!

由於分頁和排序徹底是標準的SQL語句是事情,Dapper沒有義務爲此負責。

咱們能夠經過簡單的封裝化腐朽爲神奇,來看看過濾,分頁和排序也能如此簡單和優雅,這個過程通常能夠分解爲 3 個步驟:

1. 添加過濾條件(好比匹配名稱中的關鍵詞,只列出啓用的行....)

2. 獲取總記錄行數(數據庫分頁須要在頁面顯示總記錄數,已經當前頁的記錄其實序號)

3. 獲取當前分頁的數據

下面是 AppBoxPro 中角色列表頁面的過濾,分頁和排序代碼,咱們能夠一目瞭然:

// 查詢條件
var builder = new WhereBuilder();

string searchText = ttbSearchMessage.Text.Trim();
if (!String.IsNullOrEmpty(searchText))
{
    builder.AddWhere("Name like @SearchText");
    builder.AddParameter("SearchText", "%" + searchText + "%");
}

// 獲取總記錄數(在添加條件以後,排序和分頁以前)
Grid1.RecordCount = Count<Role>(builder);

// 排列和數據庫分頁
Grid1.DataSource = SortAndPage<Role>(builder, Grid1);
Grid1.DataBind();

上面的涉及三個重要的自定義類和函數:

1. WhereBuilder:咱們封裝的一個簡單的類,主要目的是將查詢條件,條件參數以及SQL語句 3 則封裝在一塊兒。

2. Count:用來返回總記錄數。

3. SortAndPage:用來執行分頁和排序。

首先來看下WhereBuilder:

public class WhereBuilder
{
    private DynamicParameters _parameters = new DynamicParameters();

    public DynamicParameters Parameters
    {
        get { return _parameters; }
        set { _parameters = value; }
    }

    private List<string> _wheres = new List<string>();

    public List<string> Wheres
    {
        get { return _wheres; }
        set { _wheres = value; }
    }


    private string _fromSql = String.Empty;

    public string FromSql
    {
        get { return _fromSql; }
        set { _fromSql = value; }
    }

    public void AddWhere(string item)
    {
        _wheres.Add(item);
    }

    public void AddParameter(string name, object value)
    {
        _parameters.Add(name, value);
    }
}

其中:

1. _wheres: 對應於SQL的 where 子語句。

2. _parameters: 對應於 where 子語句用到的實際參數。

3. _fromSql: 若是省略此屬性,則從模型類名推導出須要操做的數據庫表名,對於須要進行表關聯的複雜查詢,則須要設置此參數,後面會進行詳細講解。

Count 的函數定義:

protected int Count<T>(WhereBuilder builder)
{
    return Count<T>(DB, builder);
}

protected int Count<T>(IDbConnection conn, WhereBuilder builder)
{
    var sql = builder.FromSql;
    if (String.IsNullOrEmpty(sql))
    {
        // 約定:類型 User 對應的數據庫表名 users
        sql = typeof(T).Name.ToLower() + "s";
    }

    sql = "select count(*) from " + sql;

    if (builder.Wheres.Count > 0)
    {
        sql += " where " + String.Join(" and ", builder.Wheres);
    }

    return conn.QuerySingleOrDefault<int>(sql, builder.Parameters);
}

SortAndPage的函數定義:

protected IEnumerable<T> SortAndPage<T>(WhereBuilder builder, FineUIPro.Grid grid)
{
    return SortAndPage<T>(DB, builder, grid);
}

protected IEnumerable<T> SortAndPage<T>(IDbConnection conn, WhereBuilder builder, FineUIPro.Grid grid)
{
    // sql: users
    // sql: select * from users
    // sql: select onlines.*, users.Name UserName from onlines inner join users on users.ID = onlines.UserID
    var sql = builder.FromSql;
    if (String.IsNullOrEmpty(sql))
    {
        // 約定:類型 User 對應的數據庫表名 users
        sql = typeof(T).Name.ToLower() + "s";
    }

    if (!sql.StartsWith("select"))
    {
        sql = "select * from " + sql;
    }

    if (builder.Wheres.Count > 0)
    {
        sql += " where " + String.Join(" and ", builder.Wheres);
    }

    sql += " order by " + grid.SortField + " " + grid.SortDirection;

    sql += " limit @PageStartIndex, @PageSize";

    builder.Parameters.Add("PageSize", grid.PageSize);
    builder.Parameters.Add("PageStartIndex", grid.PageSize * grid.PageIndex);

    return conn.Query<T>(sql, builder.Parameters);
}

上面的封裝很簡單,對分頁的處理只有這三行代碼:

sql += " limit @PageStartIndex, @PageSize";

builder.Parameters.Add("PageSize", grid.PageSize);
builder.Parameters.Add("PageStartIndex", grid.PageSize * grid.PageIndex);

固然這裏的 limit 子句只適用於 MySQL,其餘數據庫的用法後面會有介紹。

對於 builder.FromSql 屬性,若是留空,則檢索當前數據表的所有數據。而對於表關聯查詢,能夠設置完整的 select 子句,下面會進行介紹。

表關聯

在線用戶列表頁面,對於某個用戶,咱們不只要列出用戶的登陸時間,最後操做時間,IP地址,還要列出用戶名和用戶中文名稱。

這裏就須要用到表關聯,由於 onlines  只記錄用戶ID,而用戶名稱須要從 users 表獲取,下面就是此頁面的過濾,分頁和排序邏輯:

// 查詢條件
var builder = new WhereBuilder();

string searchText = ttbSearchMessage.Text.Trim();
if (!String.IsNullOrEmpty(searchText))
{
    builder.AddWhere("users.Name like @SearchText");
    builder.AddParameter("SearchText", "%" + searchText + "%");
}

DateTime twoHoursBefore = DateTime.Now.AddHours(-2);
builder.AddWhere("onlines.UpdateTime > @TwoHoursBefore");
builder.AddParameter("TwoHoursBefore", twoHoursBefore);


// 獲取總記錄數(在添加條件以後,排序和分頁以前)
Grid1.RecordCount = Count<Online>(builder);

// 排列和數據庫分頁
builder.FromSql = "select onlines.*, users.Name UserName, users.ChineseName UserChineseName from onlines inner join users on users.ID = onlines.UserID";

Grid1.DataSource = SortAndPage<Online>(builder, Grid1);
Grid1.DataBind();

相關頁面展現(用戶列表):

事務(Transaction)

Dapper對事務有兩種支持,一種是直接在 Query 或者 Execute 中傳遞 transaction 對象,而另一種則更加簡單。

在更新用戶信息時,首先是更新 users 表,而後還要操做用戶角色表和用戶部門表,對於多個數據表的屢次操做,能夠放到一個事務中:

using (var transactionScope = new TransactionScope())
{
    // 更新用戶
    ExecuteUpdate<User>(DB, item);

    // 更新用戶所屬角色
    int[] roleIDs = StringUtil.GetIntArrayFromString(hfSelectedRole.Text);
    DB.Execute("delete from roleusers where UserID = @UserID", new { UserID = userID });
    DB.Execute("insert roleusers (UserID, RoleID) values (@UserID, @RoleID)", roleIDs.Select(u => new { UserID = userID, RoleID = u }).ToList());

    // 更新用戶所屬職務
    int[] titleIDs = StringUtil.GetIntArrayFromString(hfSelectedTitle.Text);
    DB.Execute("delete from titleusers where UserID = @UserID", new { UserID = userID });
    DB.Execute("insert titleusers (UserID, TitleID) values (@UserID, @TitleID)", titleIDs.Select(u => new { UserID = userID, TitleID = u }).ToList());


    transactionScope.Complete();
}

相關頁面展現(角色權限):

匿名參數(對象和數組)

Dapper支持方便的傳入匿名參數,前面已經屢次看到,好比下面這個更新用戶角色的代碼:

DB.Execute("insert roleusers (UserID, RoleID) values (@UserID, @RoleID)", new { UserID = userID, RoleID = 101 });

不只如此,Dapper還支持屢次執行一個命令,只須要傳入一個匿名數組便可。

在 AppBoxPro 中,有多處應用場景,好比前面的更新用戶角色的代碼:

DB.Execute("insert roleusers (UserID, RoleID) values (@UserID, @RoleID)", roleIDs.Select(u => new { UserID = userID, RoleID = u }).ToList());

這裏經過 Select 表達式獲取一個動態對象數組。

在 ConfigHelper 中,咱們還有手工建立匿名數組的場景,用來更新 configs 表中的多個行數據:

DB.Execute("update configs set ConfigValue = @ConfigValue where ConfigKey = @ConfigKey",
new[] { 
    new { ConfigKey = "Title", ConfigValue = Title }, 
    new { ConfigKey = "PageSize", ConfigValue = PageSize.ToString() }, 
    new { ConfigKey = "Theme", ConfigValue = Theme }, 
    new { ConfigKey = "HelpList", ConfigValue = HelpList },
    new { ConfigKey = "MenuType", ConfigValue = MenuType }
});

多數據庫支持

多數據庫支持真的不難,在咱們支持的 MySQL 和 SQLServer 兩個數據庫中,只有少數幾處須要特殊處理。

1. 數據庫鏈接,咱們能夠根據 ProviderName 來生成不一樣的 IDbConnection 實例。

首先來看下 Web.config 中數據庫相關的配置節:

<appSettings>
    <!-- 須要鏈接的數據庫,對應於 connectionStrings 節的 name 屬性 -->
    <add key="Database" value="MySQL" />
</appSettings>
<connectionStrings>
    <clear />
    <add name="SQLServer" connectionString="Password=pass;Persist Security Info=True;User ID=sa;Initial Catalog=appbox;Data Source=." providerName="System.Data.SqlClient" />
    <add name="MySQL" connectionString="Server=localhost;Database=appbox;Uid=root;Pwd=pass;Charset=utf8" providerName="MySql.Data.MySqlClient" />
</connectionStrings>

而後是對 GetDbConnection 的擴展:

public static IDbConnection GetDbConnection()
{
    var database = ConfigurationManager.AppSettings["Database"];

    var connectionStringSection = ConfigurationManager.ConnectionStrings[database];
    var connectionString = connectionStringSection.ToString();

    IDbConnection connection;
    if (connectionStringSection.ProviderName.StartsWith("MySql"))
    {
        connection = new MySqlConnection(connectionString);
    }
    else
    {
        connection = new SqlConnection(connectionString);
    }

    // 打開數據庫鏈接
    connection.Open();

    return connection;
}

2. 插入後獲取新增的行ID

protected int ExecuteInsert<T>(IDbConnection conn, object instance, params string[] fields)
{
    // 約定:類型 User 對應的數據庫表名 users
    string tableName = typeof(T).Name.ToLower() + "s";

    if (fields.Length == 0)
    {
        fields = GetReflectionProperties(instance);
    }

    var fieldsSql1 = String.Join(",", fields);
    var fieldsSql2 = String.Join(",", fields.Select(field => "@" + field));

    var sql = String.Format("insert {0} ({1}) values ({2});", tableName, fieldsSql1, fieldsSql2);

    if (conn is MySqlConnection)
    {
        sql += "select last_insert_id();";
    }
    else
    {
        sql += "SELECT @@IDENTITY;";
    }

    return conn.QuerySingle<int>(sql, instance);
}

3. 數據庫分頁處理,更新後的 SortAndPage 函數:

protected IEnumerable<T> SortAndPage<T>(IDbConnection conn, WhereBuilder builder, FineUIPro.Grid grid)
{
    var sql = builder.FromSql;
    if (String.IsNullOrEmpty(sql))
    {
        // 約定:類型 User 對應的數據庫表名 users
        sql = typeof(T).Name.ToLower() + "s";
    }

    if (!sql.StartsWith("select"))
    {
        sql = "select * from " + sql;
    }

    if (builder.Wheres.Count > 0)
    {
        sql += " where " + String.Join(" and ", builder.Wheres);
    }

    sql += " order by " + grid.SortField + " " + grid.SortDirection;

    // 分頁
    if (conn is MySqlConnection)
    {
        sql += " limit @PageStartIndex, @PageSize";
    }
    else
    {
        sql += " OFFSET @PageStartIndex ROWS FETCH NEXT @PageSize ROWS ONLY";
    }

    builder.Parameters.Add("PageSize", grid.PageSize);
    builder.Parameters.Add("PageStartIndex", grid.PageSize * grid.PageIndex);


    return conn.Query<T>(sql, builder.Parameters);
}

好了,上面就是所有的多數據庫處理代碼了。相比 jQuery 對不一樣瀏覽器的封裝,這裏的多數據庫支持真是的小巫見大巫了。

小結

這篇文章主要描述了從 Entity Framework 遷移到 Dapper 時遇到的問題,以及咱們給出的簡單封裝,但願你能喜歡。 

後記 

注:AppBox非免費軟件,若是你但願獲取以下版本和後續更新,請加入【三石和他的朋友們】付費知識星球下載源代碼:http://fineui.com/fans/

  1. AppBoxPro(Entity Framework版)
  2. AppBoxPro(Dapper版)
  3. AppBoxMvc(Entity Framework版)
  4. AppBoxMvc(Dapper版) 
相關文章
相關標籤/搜索