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

注:此系列不是说ServiceStack.OrmLite的多个陷阱,这仅仅个人认为是某一个陷阱(毕竟我踩坑了)而引发的思考。

前文说到了项目需要使用两种不同的数据库语言,虽说前文问题已基本解决了,但是我发现OrmLite在设计上有需要改进的地方。正如前面提到的OrmLite为了开发的便捷性,ORM所需要生成SQL语句DialectProvider设置为静态属性(尽管使用了线程安全),但是这样的话DialectProvider便与线程上下文产生耦合。

而一个更优的方法,则是使用代理。第一次对代理产生深刻印象的,便是Java的连接池。[故事准备开始]

[故事中断]说到连接池,试想OrmLite在这种与线程耦合的情况下能否实现?答案是:看实际情况(废话)。
可以的前提是不使用线程池线程池连接池区分清楚,虽然两者的目的都是一样的——实现对象的重用。如果使用了线程池,当线程被重用时,OrmLiteConfig.TSDialectProvider可能被之前的IDbConnection所“沾污”,除非IDbConnection关闭前自动清除该线程上下文的OrmLiteConfig.TSDialectProvider,但是没有代理的话,这明显是不可能的。[中断恢复]

[故事开始]那时候才刚学动态代理不久(Java 动态代理机制分析及扩展),在学到数据库开发连接池的概念时,起初没觉得有什么特别。突然一天猛然醒悟:Java的动态代理是1.6版本才支持,而当时我们使用的JDK版本乃是1.5!

在强烈的好奇心驱使下,我和同学反编译了连接池的JAR包,才发现原来是这样的!实现方法我们当时叫“静态代理”,就是实现IDbConnection接口(这里Java和C#混着讲,大概知道原理就行),然后手动写代码调用真实的IDbConnection对象,这得益于当初基于接口的设计。实现代码大概是这样的:

internal class ProxyConnection : IDbConnection
{
    public ProxyConnection(ProxyConnectionPool pool, IDbConnection real)
    {        Pool = pool;
        Real = real;
    }
    public ProxyConnectionPool Pool { get; set; }
    public IDbconnection Real { get; set; }
    public IDbTransaction BeginTransaction()
    {

        return Real.BeginTransaction();
    }
    // other implement...
    public void Dispose()
    {
        Close();
    }
    public void Close()
    {
        Pool.Recycle(this);
    }
}

依葫芦画瓢,基于OrmLite的实现大概是这样的:

internal class ProxyConnection : IDbConnection
{
    public ProxyConnection(IOrmLiteDialectProvider provider, IDbConnection real)
    {
        Provider = provider;
        Real = real;
    }
    public IOrmLiteDialectProvider Provider { get; set; }
    public IDbconnection Real { get; set; }
    public IDbTransaction BeginTransaction()
    {

        return Real.BeginTransaction();
    }
}

Delete代码的修改:

// ==============================
//   WriteConnectionExtensions
// ==============================

public static int Delete<T>(this IDbConnection dbConn, Expression<Func<T, bool>> where)
{
    var conn = dbConn as ProxyConnection;
    if( conn == null)
        throw new Exception("it‘s not a OrmLite DbConnection.");
    return dbConn.Exec(dbCmd => dbCmd.Delete(conn.Provider, where));
}

public static int Delete<T>(this IDbCommand dbCmd, IOrmLiteDialectProvider provider, Expression<Func<T, bool>> where)
{
    var ev = provider.SqlExpression<T>();
    ev.Where(where);
    return dbCmd.Delete(ev);
}
//ReadConnectionExtensions
public static T Exec<T>(this IDbConnection dbConn, Func<IDbCommand, T> filter)
{
    var conn = dbConn as ProxyConnection;
    if( conn == null)
        throw new Exception("it‘s not a OrmLite DbConnection.");

    using (var dbCmd = conn.CreateCommand())
    {
        dbCmd.Transaction = conn.Transaction;
        dbCmd.CommandTimeout = conn.CommandTimeout;
        var ret = filter(dbCmd);
        LastCommandText = dbCmd.CommandText;
        return ret;
    }
}

例: SqliteOrmLiteDialectProvider.cs的修改

//修改前
public class SqliteOrmLiteDialectProvider : SqliteOrmLiteDialectProviderBase
{
    public static SqliteOrmLiteDialectProvider Instance = new SqliteOrmLiteDialectProvider();
    protected override IDbConnection CreateConnection(string connectionString)
    {
        return new SqliteConnection(connectionString);
    }
}

//修改后
public class SqliteOrmLiteDialectProvider : SqliteOrmLiteDialectProviderBase
{
    public static SqliteOrmLiteDialectProvider Instance = new SqliteOrmLiteDialectProvider();
    protected override IDbConnection CreateConnection(string connectionString)
    {
        var sqliteConn = new SqliteConnection(connectionString);
        return new ProxyConnection(this, sqliteConn);
    }
}

如果修改成我的方案的话,其扩展方法接口不需修改,而且SQL生成再也不需要和线程耦合,转而与代理IDbConnection“耦合”,而这样的“耦合”也是理所当然的。我猜OrmLite之所以不这么做,其原因兼容“原始”的连接,在我的方案中,虽然扩展方法是面向IDbConnection,而实际上只面向ProxyConnection,如果非ProxyConnection的话会直接抛异常。
而实际上,OrmLite做了代理!(这是个整个思考的过程,所以没到最后,前面所下的“结论”不一定对。)

我们现在翻一下ReadConnectionExtensions.Exec<T> 方法:

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;   }
}

其中加粗红色部分就是代理类OrmLiteConnection,其作用是使用自带的DialectProvider代替线程上下文中的TSDialectProvider 。虽然是线程安全,但我个人不建议这种写法,而推荐其作为filter参数传入,大概如下:

public static T Exec<T>(this IDbConnection dbConn, Func<IDbCommand, IOrmLiteDialectProvider, T> filter)
{
    using (var dbCmd = dbConn.CreateCommand())
    {
        var ormLiteDbConn = dbConn as OrmLiteConnection;
        var holdProvider = (ormLiteDbConn != null) ? ormLiteDbConn.Factory.DialectProvider : OrmLiteConfig.TSDialectProvider;
        dbCmd.Transaction = (ormLiteDbConn != null) ? ormLiteDbConn.Transaction : OrmLiteConfig.TSTransaction;
      dbCmd.CommandTimeout = OrmLiteConfig.CommandTimeout;
        var ret = filter(dbCmd, holdProvider);
        LastCommandText = dbCmd.CommandText;
        return ret;
    }
}

虽然filter调用时要麻烦点,但给人(我自认为)的感觉更安全,只有兼容到原始的IDbConnection时才需要和线程上下文相关,而原始的做法确实将原本独立的DialectProvider交给了上下文,我们再看看Exec的Action参数版本:

public static void Exec(this IDbConnection dbConn, Action<IDbCommand> filter)
{
    var dialectProvider = OrmLiteConfig.DialectProvider; // (1)
    try
    {
        var ormLiteDbConn = dbConn as OrmLiteConnection;
        if (ormLiteDbConn != null)
            OrmLiteConfig.DialectProvider = ormLiteDbConn.Factory.DialectProvider; // (2)

        using (var dbCmd = dbConn.CreateCommand())
        {
            dbCmd.Transaction = (ormLiteDbConn != null) ? ormLiteDbConn.Transaction : OrmLiteConfig.TSTransaction;
            dbCmd.CommandTimeout = OrmLiteConfig.CommandTimeout;

            filter(dbCmd);
            LastCommandText = dbCmd.CommandText;
        }
    }
    finally
    {
        OrmLiteConfig.DialectProvider = dialectProvider; // (3)
    }
}

说实话,我不知道是代码BUG,还是我能力低没能理解到作者的意思。我们可以从第一篇回顾下OrmLiteConfig.DialectProvider的代码(两篇同时看)。
首先,我个人认为两个Exec方法中的dialectProvider 临时变量都是为了 简便filter 的内部实现的而暂时替代全局的OrmLiteConfig.(TS)DialectProvider(两个)变量。
在后一个实现方法中(Action参数)假设有两种情况:
1.当前线程用户没有自行设置TSDialectProvider,即TSDialectProvider = null。
2.TSDialectProvider 不为空。

情况1:

//(1) OrmLiteConfig.DialectProvider(get) = 默认,dialectProvider = 默认
//(2) OrmLiteConfig.DialectProvider(get) = 代理,dialectProvider = 默认
//(3) OrmLiteConfig.DialectProvider(get) = 默认,dialectProvider = 默认

情况2:
//(1) OrmLiteConfig.DialectProvider(get) = TSDialectProvider,dialectProvider = TSDialectProvider
//(2) OrmLiteConfig.DialectProvider(get) = TSDialectProvider,dialectProvider = TSDialectProvider,OrmLiteConfig.DialectProvider(set.dialectProvider) = 代理
//(3) OrmLiteConfig.DialectProvider(get) = TSDialectProvider,dialectProvider = TSDialectProvider,OrmLiteConfig.DialectProvider(set.dialectProvider) = TSDialectProvider

情况2中正因为OrmLiteConfig.DialectProvider优先返回TSDialectProvider才导致“数据现场无法恢复”。
但情况1就好了吗?如果项目中包含了3种不同数据库语言的存在,那么并发的时候也可能因乱序原因导致OrmLiteConfig.DialectProvider和最初的不一样。
怎么解决?很可能不需要解决,因为需要用到不同数据库语言的时候,根本不会再使用第一篇上的写法,这仅仅是我的代码洁癖,又或者担心有跟我一样不知道自己代码有多烂的程序员真的这样写而已。

那应该用怎样写法呢?请看下一篇。

时间: 2024-08-06 17:56:03

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

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

使用过ServiceStack.Ormlite的人都应该知道,其作为一个轻量级的ORM,使用的便捷度非常高,用起来就一个字:爽!而支撑其便捷度的,是库内大量地使用了扩展方法及静态变量. 首先先从源头入手分析(以下以Sqlite为例): OrmLiteConfig.DialectProvider = SqliteOrmLiteDialectProvider.Instance; using (IDbConnection db = "~/db.sqlite".MapAbsolutePath(

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.闭包.作用域链.函数是当之无愧的.这门语言正式因