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就是专门为多数据库语言而设的。

时间: 2024-10-11 01:30:07

ServiceStack.OrmLite中的一些"陷阱"(1)的相关文章

ServiceStack.OrmLite中的一些&quot;陷阱&quot;(2)

注:此系列不是说ServiceStack.OrmLite的多个陷阱,这仅仅个人认为是某一个陷阱(毕竟我踩坑了)而引发的思考. 前文说到了项目需要使用两种不同的数据库语言,虽说前文问题已基本解决了,但是我发现OrmLite在设计上有需要改进的地方.正如前面提到的OrmLite为了开发的便捷性,ORM所需要生成SQL语句DialectProvider设置为静态属性(尽管使用了线程安全),但是这样的话DialectProvider便与线程上下文产生耦合. 而一个更优的方法,则是使用代理.第一次对代理产

ServiceStack.OrmLite中的一些&quot;陷阱&quot;(3)

前文说到如果使用多数据库(不同SQL方言)时要如何开发?其实前文(第二篇)也有“透露”到.就是直接使用库提供的OrmLiteConnection 及OrmLiteConnectionFactory(IDbConnectionFactory) .我们先来看下代理类是怎么实现的: public class OrmLiteConnection : IDbConnection, IHasDbConnection, IHasDbTransaction { public readonly OrmLiteCo

ServiceStack.OrmLite简单扩展

ServiceStack.OrmLite框架将所有的方法都扩展到了一个IDbConnection对象上,使用起来不是 很方便,在此对该框架做一个简单的封装. OrmLiteExecFilterExt类 using System; using System.Collections.Generic; using System.Data; using System.Text; using System.Threading.Tasks; namespace ServiceStack.OrmLite.Ex

ServiceStack.OrmLite 入门(一)

软件环境: Win7 x64 SP1 SQL Server 2008r2 Visual Studio 2017 Professional 目标:取出示例数据库 ReportServer 的表 Roles 中的所有记录并显示. 步骤: 一.添加软件包 使用NuGet添加以下软件包: ServiceStack ServiceStack.OrmLite 二.定义表类 根据表Roles来定义对应的C#类: [Serializable] [Alias("Roles")] public class

ServiceStack.OrmLite 调用存储过程

最近在做关于ServiceStack.OrmLite调用存储过程时,有问题.发现ServiceStack.OrmLite不能调用存储过程,或者说不能实现我想要的需求.在做分页查询时,我需要传入参数传出参数. ServiceStack.OrmLite 调用存储过程代码: 存储过程:usp_GetCarComponentsList 传入参数:@page,@limit 传出参数:@pageCount ,@totalCount 问题描述:参数传入在数据库中不能接收,不知道是写法问题,还是其他原因.即便传

JavaScript中的this陷阱的最全收集--没有之一

原文:JavaScript中的this陷阱的最全收集--没有之一 当有人问起你JavaScript有什么特点的时候,你可能立马就想到了单线程.事件驱动.面向对象等一堆词语,但是如果真的让你解释一下这些概念,可能真解释不清楚.有句话这么说:如果你不能向一个6岁小孩解释清楚一个东西,那么你自己也不懂这个东西.这句话或许有点夸张,但是极其有道理.个人觉得,如果需要掌握一门语言,掌握它的API只是学了皮毛,理解这门语言的精髓才是重点.提及JavaScript的精髓,this.闭包.作用域链.函数是当之无

ServiceStack.OrmLite破解

在 ServiceStack.OrmLite下的 OrmLiteConfigExtensions 第199行把这句注释掉就可以了 //LicenseUtils.AssertValidUsage(LicenseFeature.OrmLite, QuotaType.Tables, typeModelDefinitionMap.Count);

JavaScript中的this陷阱的最全收集

当有人问起你JavaScript有什么特点的时候,你可能立马就想到了单线程.事件驱动.面向对象等一堆词语,但是如果真的让你解释一下这些概念,可能真解释不清楚.有句话这么说:如果你不能向一个6岁小孩解释清楚一个东西,那么你自己也不懂这个东西.这句话或许有点夸张,但是极其有道理.个人觉得,如果需要掌握一门语言,掌握它的API只是学了皮毛,理解这门语言的精髓才是重点.提及JavaScript的精髓,this.闭包.作用域链.函数是当之无愧的.这门语言正式因为这几个东西而变得魅力无穷. 博客的标题是<J

转:JavaScript中的this陷阱的最全收集

在其他地方看到的,觉得解释的狠详细,特此分享 当有人问起你JavaScript有什么特点的时候,你可能立马就想到了单线程.事件驱动.面向对象等一堆词语,但是如果真的让你解释一下这些概念,可能真解释不清楚.有句话这么说:如果你不能向一个6岁小孩解释清楚一个东西,那么你自己也不懂这个东西.这句话或许有点夸张,但是极其有道理.个人觉得,如果需要掌握一门语言,掌握它的API只是学了皮毛,理解这门语言的精髓才是重点.提及JavaScript的精髓,this.闭包.作用域链.函数是当之无愧的.这门语言正式因