ServiceStack.OrmLite中的一些"陷阱"(1)


使用過ServiceStack.Ormlite的人都應該知道,其作為一個輕量級的ORM,使用的便捷度非常高,用起來就一個字:爽!
而支撐其便捷度的,是庫內大量地使用了擴展方法及靜態變量。

首先先從源頭入手分析(以下以Sqlite為例):

OrmLiteConfig.DialectProvider = SqliteOrmLiteDialectProvider.Instance;
using (IDbConnection db = "~/db.sqlite".MapAbsolutePath().OpenDbConnection())
{
    db.CreateTable<User>();
    db.Insert(new User("Hello", "Password"));
}

 

其中MapAbsolutePath()是轉換為絕對路徑,這里不詳細分析。而OPenDbConnection()則是OrmLiteConfig.cs上的擴展方法,其調用鏈如下:

public static IDbConnection OpenDbConnection(this string dbConnectionStringOrFilePath)
{
    var sqlConn = dbConnectionStringOrFilePath.ToDbConnection(DialectProvider);
    sqlConn.Open();
    return sqlConn;
}
public static IDbConnection ToDbConnection(this string dbConnectionStringOrFilePath)
{
    return dbConnectionStringOrFilePath.ToDbConnection(DialectProvider);
}

public static IDbConnection ToDbConnection(this string dbConnectionStringOrFilePath, IOrmLiteDialectProvider dialectProvider)
{
    var dbConn = dialectProvider.CreateConnection(dbConnectionStringOrFilePath, options: null);
    return dbConn;
}

 

從重載方法中可以看出如果不提供IOrmLiteDialectProvider 參數的話是會使用OrmLiteConfig.DialectProvider (type:IOrmLiteDialectProvider)這個靜態屬性作為默認值的。而IOrmLiteDialectProvider 的作用就是ORM基於不同數據庫語言的實現,ORM生成各自的SQL語句都靠它。

那么,既然有 ToDbConnection(string,IOrmLiteDialectProvider) 這樣的函數,難道就可以在同一個項目中混合使用多個不同數據庫語言IDbConnect 了嗎?(有可能項目有這樣的要求)
例如這樣:

var sqliteDb = "sqlitexxxx".ToDbConnection(SqliteOrmLiteDialectProvider.Instance);
var mysqlDb = "mysqlxxxx".ToDbConnection(MySqlDialectProvider.Instance);

我只能說:太!天!真!了!在OrmLite中大量地使用了OrmLiteConfig.DialectProvider 靜態屬性,以WriteConnectionExtensions.Delete<T>為例:

public static int Delete<T>(this IDbConnection dbConn, Expression<Func<T, bool>> where)
{
    return dbConn.Exec(dbCmd => dbCmd.Delete(where));
}

public static int Delete<T>(this IDbCommand dbCmd, Expression<Func<T, bool>> where)
{
    var ev = OrmLiteConfig.DialectProvider.SqlExpression<T>();
    ev.Where(where);
    return dbCmd.Delete(ev);
}

public static T Exec<T>(this IDbConnection dbConn, Func<IDbCommand, T> filter)
{
    var holdProvider = OrmLiteConfig.TSDialectProvider;
    try
    {
        var ormLiteDbConn = dbConn as OrmLiteConnection;
        if (ormLiteDbConn != null)
            OrmLiteConfig.TSDialectProvider = ormLiteDbConn.Factory.DialectProvider;

        using (var dbCmd = dbConn.CreateCommand())
        {
            dbCmd.Transaction = (ormLiteDbConn != null) ? ormLiteDbConn.Transaction : OrmLiteConfig.TSTransaction;
            dbCmd.CommandTimeout = OrmLiteConfig.CommandTimeout;
            var ret = filter(dbCmd);
            LastCommandText = dbCmd.CommandText;
            return ret;
        }
    }
    finally
    {
        OrmLiteConfig.TSDialectProvider = holdProvider;
    }
}

上述代碼中可以看出,IDbCommand的Delete擴展方法中就使用了OrmLiteConfig.DialectProvider 靜態屬性,所以我們使用前必須賦予初始值。

 

有細心的網友可能會發現,在Exec方法中用到的Provider是TSDialectProvider 而非 DialectProvider,過中原由得慢慢分析。

[ThreadStatic]
public static IOrmLiteDialectProvider TSDialectProvider;

private static IOrmLiteDialectProvider dialectProvider;
public static IOrmLiteDialectProvider DialectProvider
{
    get
    {
        if (dialectProvider == null)
        {
            throw new ArgumentNullException("DialectProvider",
                "You must set the singleton 'OrmLiteConfig.DialectProvider' to use the OrmLiteWriteExtensions");
        }
        return TSDialectProvider ?? dialectProvider;
    }
    set
    {
        dialectProvider = value;
    }
}

先看TSDialectProvider,這是線程安全的靜態變量。反而比較有意思的是DialectProvider,首先在get的時候dialectProvider 字段不能為null,否則會拋出異常。但是返回時卻優先返回TSDialectProvider,而不是dialectProvider。為什么呢?

我覺得這是出於便捷性和多線程方面的考慮。
首先是便捷性,如果不便捷而較為常規的做法是怎樣,我的實現是直接提供TSDialectProvider

[ThreadStatic]
private static IOrmLiteDialectProvider tsDialectProvider;

public static IOrmLiteDialectProvider DialectProvider
{
    get
    {
        return tsDialectProvider;
    }
    set
    {
        tsDialectProvider = value;
    }
}

多線程使用時必先在打開連接前先初始化DialectProvider

public class OrmLiteTest
{
    public static void Main(string[] args)
    {
        new Thread(SqliteTest).Start("sqlite");
        new Thread(MySqlTest).Start("mysql");
    }

    static void SqliteTest(string path)
    {
        OrmLiteConfig.DialectProvider = SqliteOrmLiteDialectProvider.Instance;
        var db = ((string)path).OpenDbConnection();
        //TODO sth.
    }
    static void MySqlTest(string path)
    {
        OrmLiteConfig.DialectProvider = MySqlDialectProvider.Instance;
var db = ((string)path).OpenDbConnection();
//TODO sth.
}
}

這里順道把多線程也解決了,通常只使用一種數據庫語言的時候,需要並發執行數據庫操作(多個線程,多個IDbConnection)時,每次都需提前設置OrmLiteConfig.DialectProvider 反而會顯得煩瑣。

return TSDialectProvider ?? dialectProvider;

因為一般情況下TSDialectProvider默認為null,除非需要用到多種數據庫才需要手動設置。可以說,TSDialectProvider就是專門為多數據庫語言而設的。

 


免責聲明!

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



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