Why can I not cast IDbTransaction in ServiceStack OrmLite to DbTransaction?

烂漫一生 提交于 2019-12-11 22:22:47

问题


I am using ServiceStack.Ormlite v3.9.71 and have the following piece of code where I open an Ormlite SQLite Transaction, suppose I want to use this transaction in a command elsewhere in the code:

var connFactory = new OrmLiteConnectionFactory("ConnectionString", SqliteOrmLiteDialectProvider.Instance)
using (IDbConnection db = connFactory.Open()) // using var doesn't make a difference
using (IDbTransaction tran = db.OpenTransaction()) // using var or even db.BeginTransaction() doesn't make a difference
{
    // I do lots of boring stuff
    ...

    // Somewhere else in the code
    using (var cmd = db.CreateCommand())
    {
        cmd.Transaction = tran; // get an error
    }
}    

however when I do, I get the:

A first chance exception of type 'System.InvalidCastException' occurred in System.Data.dll

Additional information: Unable to cast object of type 'ServiceStack.OrmLite.OrmLiteTransaction' to type 'System.Data.Common.DbTransaction'. 

And the Stack Trace:

   at System.Data.Common.DbCommand.System.Data.IDbCommand.set_Transaction(IDbTransaction value)

I guess this is due to the OrmLiteTransaction wrapper, how can I get to the transaction?


回答1:


OrmLite provides ToDbTransaction extension method.

public static IDbTransaction ToDbTransaction(this IDbTransaction dbTrans)
    {
        var hasDbTrans = dbTrans as IHasDbTransaction;
        return hasDbTrans != null
            ? hasDbTrans.Transaction
            : dbTrans;
    }

You can use it in your code like this.

cmd.Transaction = tran.ToDbTransaction()

Hope it will solve your problem.




回答2:


UPDATE

OrmLiteConnection will return a SqliteCommand which means that it just isn't possible to set the command's Transaction property, because the command expects a SqliteTransaction object. Even if you try to set IDbCommand.Transaction, you'll call the DbCommand.Transaction setter which checks that the object passed is a DbTransaction object

The fix is to simply not set the Transaction property, as db.CreateCommand sets it itself if a transaction was already opened with db.OpenTransaction

Original

I think you should follow @Mangus' advice and use var instead of casting to interfaces.

Checking SqliteOrmLiteDialectProvider's implementation, it appears it's using Mono.Data.Sqlite. Mono.Data.Sqlite classes inherit from the abstract DbXXX classes but hide the base implementations with their own.

SQLiteCommand.Transaction and SQLiteConnection.CreateCommand have the following signatures:

public new SqliteCommand CreateCommand()
{
...
}

public new SqliteTransaction Transaction
{
...
}

By casting to the interfaces you call the base implementation in the DbConnection, DbCommand classes instead of the new implementations in Mono.Data.Sqlite. This means that OpenTransaction will return a DbTransaction-derived object instead of an OrmLiteTransaction object.

The following code should work:

var connFactory = new OrmLiteConnectionFactory("ConnectionString", SqliteOrmLiteDialectProvider.Instance)
using (var db = connFactory.Open())
using (var tran = db.OpenTransaction()) // or even db.BeginTransaction()
{
    // I do lots of boring stuff
    ...

    // Somewhere else in the code
    using (var cmd = db.CreateCommand())
    {
        cmd.Transaction = tran; 
    }
}    

ADO.NET providers since .NET 2.0 are expected to derive from the DbXXX abstract classes instead of implementing the .NET 1.1 interfaces like IDbConnection.

In your case, OrmLite breaks the expected behaviour resulting in a clash with Mono.Data.Sqlite's unexpected member hiding. Otherwise you wouldn't have noticed anything.



来源:https://stackoverflow.com/questions/26234765/why-can-i-not-cast-idbtransaction-in-servicestack-ormlite-to-dbtransaction

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!