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和最初的不一样。
怎么解决?很可能不需要解决,因为需要用到不同数据库语言的时候,根本不会再使用第一篇上的写法,这仅仅是我的代码洁癖,又或者担心有跟我一样不知道自己代码有多烂的程序员真的这样写而已。
那应该用怎样写法呢?请看下一篇。
ServiceStack.OrmLite中的一些"陷阱"(2)的更多相关文章
- ServiceStack.OrmLite中的一些"陷阱"(1)
使用过ServiceStack.Ormlite的人都应该知道,其作为一个轻量级的ORM,使用的便捷度非常高,用起来就一个字:爽!而支撑其便捷度的,是库内大量地使用了扩展方法及静态变量. 首先先从源头入 ...
- ServiceStack.OrmLite中的一些"陷阱"(3)
前文说到如果使用多数据库(不同SQL方言)时要如何开发?其实前文(第二篇)也有“透露”到.就是直接使用库提供的OrmLiteConnection 及OrmLiteConnectionFactory(I ...
- ServiceStack.OrmLite 调用存储过程
最近在做关于ServiceStack.OrmLite调用存储过程时,有问题.发现ServiceStack.OrmLite不能调用存储过程,或者说不能实现我想要的需求.在做分页查询时,我需要传入参数传出 ...
- ServiceStack.OrmLite
ServiceStack.OrmLite 谈谈我的入门级实体框架Loogn.OrmLite 每次看到有新的ORM的时候,我总会留意一下,因为自己也写过一个这样的框架,人总是有比较之心的.我可能会d ...
- ServiceStack.OrmLite T4模板使用记录
前言 最近研究了下ServiceStack.OrmLite,文档中也提到了使用T4模板对数据库中已经有了表进行实体的映射,这里也顺便记录下使用的步骤和情况. 开始使用 引用T4模板 首先我们创建一个工 ...
- ServiceStack.OrmLite 入门(一)
软件环境: Win7 x64 SP1 SQL Server 2008r2 Visual Studio 2017 Professional 目标:取出示例数据库 ReportServer 的表 Role ...
- JavaScript中的this陷阱的最全收集 没有之一
当有人问起你JavaScript有什么特点的时候,你可能立马就想到了单线程.事件驱动.面向对象等一堆词语,但是如果真的让你解释一下这些概 念,可能真解释不清楚.有句话这么说:如果你不能向一个6岁小孩解 ...
- 转:JavaScript中的this陷阱的最全收集
在其他地方看到的,觉得解释的狠详细,特此分享 当有人问起你JavaScript有什么特点的时候,你可能立马就想到了单线程.事件驱动.面向对象等一堆词语,但是如果真的让你解释一下这些概念,可能真解释不清 ...
- ServiceStack.OrmLite 学习笔记7-复杂点的使用1
复杂点的使用1 先看看这2个类 class Customer { public int Id { get; set; } ... } class CustomerAddress { public in ...
随机推荐
- IntelliJ IDEA 15.0.2远程debug tomcat
背景 在最近的java项目中使用了linux环境下编译的so文件,所以无法在window环境下debug,故此有了这篇文章 环境 jdk:jdk-8u101-linux-x64 os:CentOS r ...
- 【java基础学习一】int[]、Integer[]、String[] 排序( 正序、倒叙)、去重
调用: //重复项有9.5.1.2 int[] ints = new int[]{9,4,7,8,2,5,1,6,2,5,9,1}; arrayIntTest(ints); ///////////// ...
- java hashCode方法返回值
hashCode 是和内存地址相关的一个整数. HashCode只是在需要用到哈希算法的数据结构中才有用 用途是为了方便快速地查找对象: HashMap 是根据键对象的 HashCode 来进行快速查 ...
- 使用HTML5新支持的搭建WebRtc环境来作为视频通讯
发现如果再重新设计这块的话,又会有不同的思路.对于可定位能力,我们可以全息日志采集,将每个用户在整个系统的走向异步的抓取下来,再同步到专门的日志分析系统,在这个系统中可以根据用户号码.订单号进行过滤分 ...
- 详解收发不畅原因及U-Mail邮件中继解决之道
邮件在商务往来中扮演着信息交流的重要角色,假如传输受阻,必将造成沟通不畅:可能三五封邮件的投递你意识不到其重要性,但假如长期需和客户保持沟 通,则需要保证其一贯的稳定性,这就很考验相关软件平台的性能是 ...
- arm_linux_dt
http://www.deyisupport.com/question_answer/dsp_arm/c6000_multicore/f/53/t/44862.aspx
- 什么是SCADA Viewer
SCADA Viewer 什么是SCADA Viewer SCADA Viewer是一个基于Web的软件框架(基于Web的HMI/SCADA/M2M工业和楼宇自动化,支持Modbus,BACnet,O ...
- 安装oracle ebs 出现问题 atleast 55M of disk space
出现这个问题 可以进行如下尝试: 1.登陆用户是否具有管理员权限,可以右击exe用管理员登陆. 2.安装电脑是否有安装mcafee,可以禁用后运行. 3.看下C:\Documents and Sett ...
- thinkphp 数据访问
<?php namespace Admin\Controller; use Think\Controller; class MainController extends Controller { ...
- arduino编程语言Wiring参考手册API
对Arduino的编程是利用 Arduino编程语言 (基于 Wiring)和Arduino开发环境(based on Processing)来实现的. 通过编程,Arduino可以实现很多种功能. ...