注:此系列不是说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和最初的不一样。
怎么解决?很可能不需要解决,因为需要用到不同数据库语言的时候,根本不会再使用第一篇上的写法,这仅仅是我的代码洁癖,又或者担心有跟我一样不知道自己代码有多烂的程序员真的这样写而已。

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

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

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

    使用过ServiceStack.Ormlite的人都应该知道,其作为一个轻量级的ORM,使用的便捷度非常高,用起来就一个字:爽!而支撑其便捷度的,是库内大量地使用了扩展方法及静态变量. 首先先从源头入 ...

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

    前文说到如果使用多数据库(不同SQL方言)时要如何开发?其实前文(第二篇)也有“透露”到.就是直接使用库提供的OrmLiteConnection 及OrmLiteConnectionFactory(I ...

  3. ServiceStack.OrmLite 调用存储过程

    最近在做关于ServiceStack.OrmLite调用存储过程时,有问题.发现ServiceStack.OrmLite不能调用存储过程,或者说不能实现我想要的需求.在做分页查询时,我需要传入参数传出 ...

  4. ServiceStack.OrmLite

    ServiceStack.OrmLite 谈谈我的入门级实体框架Loogn.OrmLite   每次看到有新的ORM的时候,我总会留意一下,因为自己也写过一个这样的框架,人总是有比较之心的.我可能会d ...

  5. ServiceStack.OrmLite T4模板使用记录

    前言 最近研究了下ServiceStack.OrmLite,文档中也提到了使用T4模板对数据库中已经有了表进行实体的映射,这里也顺便记录下使用的步骤和情况. 开始使用 引用T4模板 首先我们创建一个工 ...

  6. ServiceStack.OrmLite 入门(一)

    软件环境: Win7 x64 SP1 SQL Server 2008r2 Visual Studio 2017 Professional 目标:取出示例数据库 ReportServer 的表 Role ...

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

    当有人问起你JavaScript有什么特点的时候,你可能立马就想到了单线程.事件驱动.面向对象等一堆词语,但是如果真的让你解释一下这些概 念,可能真解释不清楚.有句话这么说:如果你不能向一个6岁小孩解 ...

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

    在其他地方看到的,觉得解释的狠详细,特此分享 当有人问起你JavaScript有什么特点的时候,你可能立马就想到了单线程.事件驱动.面向对象等一堆词语,但是如果真的让你解释一下这些概念,可能真解释不清 ...

  9. ServiceStack.OrmLite 学习笔记7-复杂点的使用1

    复杂点的使用1 先看看这2个类 class Customer { public int Id { get; set; } ... } class CustomerAddress { public in ...

随机推荐

  1. mysql命令行导入sql脚本中文变问号问题

    之前一直用工具连接mysql虽然小问题不断也都无伤大雅,最近做金融云项目,只能通过服务器的内网访问数据库,也就是说只能在linux下通过命令行访问,在导入中文的时候发现都变成问号了,经过查询资料解决, ...

  2. node代码片段

    /** * Created by Administrator on 2016/8/22 0022. * chat */ var net=require('net'); var chatServer=n ...

  3. GitHub上最火的40个Android开源项目

    http://www.csdn.net/article/2013-05-03/2815127-Android-open-source-projects

  4. C++中类似C# region的功能

    使用#pragma region和#pragma endregion关键字,来定义可以展开和收缩的代码区域的开头和结尾, 可以把这些代码行收缩为一行,以后要查看其细节时,可以再次展开它. 例如: // ...

  5. jQuery(window) 和 jQuery(document)的区别

    jQuery(window).height()代表了当前可见区域的大小,而jQuery(document).height()则代表了整个文档的高度,可视具体情况使用

  6. SQL Server查询结果插入表

    a) 插入新表 select * into newtable from table b) 插入已经存在的表 insert into table select * from table2 where.. ...

  7. 关于Linux x64 Oracle JDK7u60 64-bit HotSpot VM 线程栈默认大小问题的整理

    JVM线程的栈默认大小,oracle官网有简单描述: In Java SE 6, the default on Sparc is 512k in the 32-bit VM, and 1024k in ...

  8. Python列表切成多个

    li = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18] #arr是被分割的list,n是每个chunk中含n元素. def chunks(arr, n) ...

  9. [题解]vijos & codevs 能量项链

    a { text-decoration: none; font-family: "comic sans ms" } .math { color: gray; font-family ...

  10. 如何在string.Format()方法中输出大括号

    在string.Format参数中,大括号{}是有特殊意义的符号,但是如果我们希望最终的结果中包含大括号({}),那么我们需要怎么做呢?是”\{”吗?很遗憾,运行时,会给你一个Exception的!正 ...