0

I am using Microsoft.Enterprise Library with MySql database. I am trying to call a database method asynchronously.

My method is like below..

public static async Task<DataTable> ExecuteDataSetWithParameterAsSourceAsync(this Database database, CommandDetail cmdDetail, params Object[] parameterSource)
    {
        List<int> outParamIndex = new List<int>();

        AsyncCallback cb = new AsyncCallback(EndExecuteReaderCallBack);

        DataTable dt = new DataTable();
        DbAsyncState state = BeginExecuteReader(database, cmdDetail.CommandText, cb, parameterSource);
        IDataReader reader = (IDataReader)state.State;
        dt.Load(reader);
        reader.Close();
      ...
        return await Task.FromResult(dt);
    }

I am getting below Error

{"The database type "MySqlDatabase" does not support asynchronous operations."}

Below is the complete stack image of the error..

Stack Trace

My connection string is

 <add name="VirtualCloudDB" providerName="EntLibContrib.Data.MySql" connectionString="database=test;uid=xxx;password=xxx;Data Source=test-instance; maximumpoolsize=3"/>
29
  • just remove the async stuff Commented May 6, 2021 at 12:07
  • 1
    @Bezhas that's not the Microsoft Enterprise library, it's a clone of the decades-old EntLib code. Don't use it. The data layer functionality was incorporated in ADO.NET 2.0 15 years ago - you can use a database-agnostic provider factory already. The "async" code you used was replaced by Tasks in 2010 and became completely obsolete in 2012 when async/await was introduced Commented May 7, 2021 at 7:20
  • 1
    @Bezhas that's not what happened. I was actually there. EntLib was created to provide the missing functionality in .NET 1.0, by providing a database abstraction, a logging layer, exception handling etc. The Data part was completely incorporated into ADO.NET 2.0. Other parts were way over-engineered which is why they didn't catch on, or were replaced by other libraries like eg log4net. Microsoft was trying to promote its own CodePlex rival to Github back then, and never would have posted anything to GitHub. Obviously, Codeplex failed and was shut down. What you see is a clode of old code Commented May 7, 2021 at 7:38
  • 1
    @Bezhas oops, I forgot about the actual error -The database type "MySqlDatabase" does not support asynchronous operations.. That's exactly the error Oracle's provider raised in versions before 8.0. Use MySqlConnector or upgrade to the latest version of Connector/.NET Commented May 7, 2021 at 9:28
  • 1
    @Bezhas use DbCommand.ExecuteReaderAsync then. You're already working with DbCommand. In fact, if you check the Database.cs source you'll see it has ExecuteReader methods that work with ready-made commands, raw SQL or stored procedures, and all they do is ... create and populate a DbCommand. You can create an extension method on Database that does the same. Commented May 7, 2021 at 10:11

1 Answer 1

3

About the error

Oracle's Connector/.NET library didn't even allow asynchronous operations before v8.0. Even now, there are several quirks. It's better to use the independent, open-source MySqlConnector library.

If you absolutely must use Connector/.NET, upgrade to the latest version.

About the code (no history lesson)

Forget EntLib, especially DAAB. Even the docs say:

The Database class leverages the provider factory model from ADO.NET. A database instance holds a reference to a concrete DbProviderFactory object to which it forwards the creation of ADO.NET objects.

What you use isn't the real thing anyway, it's a community-supported clone of the official code that used to be stored in Codeplex. The only thing that is still in development is the Unity DI container.

Real async operations are available in ADO.NET and implemented by most providers. The database-agnostic, factory-based model of EntLib 1 was incorporated into ADO.NET 2 back in 2006. Entlib 2.0 DAAB is essentially a thin layer of convenience methods over ADO.NET 2.

ADO.NET 2 "Raw"

In ADO.NET 2.0 alone, the entire method can be replaced with :

async Task<DataTable> LoadProducts(string category)
{
    var sql="select * from Products where category=@category";
    using(var connection=new MySqlConnection(_connStrFromConfig))
    using(var cmd=new MySqlCommand(sql,connection))
    {
        cmd.Parameters.AddWithValue("@category",category);

        await connection.OpenAsync();

        using(var reader=await cmd.ExecuteReaderAsync())
        {
            DataTable dt = new DataTable();
            dt.Load(reader);
            return dt;
        }
    }
}

Especially for MySQL, it's better to use the open-source MySqlConnector library than Oracle's official Connector/.NET.

ADO.NET 2 Factory model

ADO.NET 2 added abstract base classes and a factory model (based on DAAB 1, but easier) that allows using database-agnostic code as much as possible.

The previous code, without using the provider factory, can be rewritten as :


string _providerName="MySqlConnector"


DbCommand CreateConnection()
{
    DbProviderFactory _factory =DbProviderFactories.GetFactory(_providerName);
    connection = _factory.CreateConnection();
    connection.ConnectionString = connectionString;
    return connection;
}

async Task<DataTable> LoadProducts(string category)
{
    var sql="select * from Products where category=@category";
    using(DbConnection connection=CreateConnection())
    using(DbCommand cmd= connection.CreateCommand())
    {
        cmd.CommandText=sql;
        var param=cmd.CreateParameter();
        param.Name="@category";
        //The default is String, so we don't have to set it
        //param.DbType=DbType.String;
        param.Value=category;
        
        cmd.Parameters.Add("@category",category);

        await connection.OpenAsync();

        using(var reader=await cmd.ExecuteReaderAsync())
        {
            DataTable dt = new DataTable();
            dt.Load(reader);
            return dt;
        }
    }
}

All that's needed to target eg SQL Server or Oracle is registering and using a different provider name.

The code can be simplified. For example, DbParameterCollection.AddRange can be used to add multiple parameters at once. That's still too much code by modern standards though.

Entlib 2 DAAB - it's the same classes

Entlib 2 DAAB uses the same abstract classes. In fact, the Database class does little more than add convenience methods on top of the abstract classes, eg methods to create a DbCommand, or execute a query and return a reader or a Dataset.

If you didn't need parameters, you could write just :

DataTable LoadProducts(Database database)
{
    var sql="select * from Products";
    var set=database.ExecuteDataSet(CommandType.Text,sql);
    return set.Tables[0];
}

Unfortunately, there's no way to combine a raw query and parameters. Back when EntLib 1 was created it was thought that complex code should always be stored in a stored procedure. So while there's a ExecuteDataSet(string storedProcedureName,params object[] parameterValues), there's no equivalent for raw SQL.

And no Task-based async methods either. By 2010 EntLib was in support mode already.

Unfortunately again there's no way to directly create a DbCommand from Database. Again, the assumption was that people would either execute raw SQL or called a stored procedure. There's a GetSqlStringCommand that accepts no parameters. There's also Database.ProviderFactory that can be used to do everything manually, and end up with the same code as raw ADO.NET.

Another possible option is to cheat, and use Database.GetStoredProcCommand with positional parameters and change the CommandType

async Task<DataTable> LoadProducts(Database database,string category)
{
    var sql="select * from Products where category=@category";
    using(var cmd=database.GetStoredProcCommand(sql,category))
    {
        cmd.CommandType=CommandType.Text;
        using(var reader=await cmd.ExecuteReaderAsync())
        {
            DataTable dt = new DataTable();
            dt.Load(reader);
            return dt;
        }
    }

    return set.Tables[0];
}

Dapper

With microORM libraries like Dapper the code can be reduced to :

async Task<IEnumerable<Product>> LoadProducts(string category)
{
    var sql="select * from Products where category=@category";
    using(var connection=CreateConnection())
    {

        var products=await connection.Query<Product>(sql,new {category=category});

        return products;
    }
}

Dapper will open the connection if it's closed, execute the query asynchronously and map the results to the target object, in a single line of code. Parameters will be mapped by name from the parameter object.

When called without a type parameter, Query returns a list of dynamic objects

async Task<IEnumerable<dynamic>> LoadProducts(string category)
{
    var sql="select * from Products where category=@category";
    using(var connection=CreateConnection())
    {

        var products=await connection.Query(sql,new {category=category});

        return products;
    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.