zoukankan      html  css  js  c++  java
  • 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就是专门为多数据库语言而设的。

  • 相关阅读:
    wifi 天线
    Ubuntu下通过SSH远程登录服务器的方法
    wifi 天线
    免费20G全能空间正在火爆开放中。。。。。要的抓紧时间申请了.
    php设计模式 Mediator (中介者模式)
    DEDE图片集上传图片时出错显示(FILEID)的解决办法
    SPL spl_autoload_register与__autoload方法使用示例浅谈
    mysql_fetch_array()和mysql_fetch_assoc()两个函数的区别
    nginx多站设置
    php中echo(),print(),print_r()的区别
  • 原文地址:https://www.cnblogs.com/godzza/p/3995067.html
Copyright © 2011-2022 走看看