Execute DAX queries through OLE DB and ADOMD.NET

If you want to execute a DAX query from your code to a Tabular model, you might think that an OLE DB connection should be the right way to get your data. It works, but if you your code is written in .NET a better solution is using the ADOMD.NET library. In this article you will see why ADOMD.NET is better in this scenario.

The OLE DB MSOLAP provider can be used to send MDX, DAX and DMX queries to an Analysis Services database. The result can be only a scalar or a table, so you cannot use an MDX query returning results on more than two axes. When you use a DAX query, the result will always fit well in a table, so you can iterate a DataReader to get the result. The following is a simple program you can write in C# to read the result of a DAX query (in this example the DAX query is sent to the Adventure Works DW Tabular model, you can replace the query with any valid DAX query for your model).

using System;
using System.Data.OleDb;

class Program {
    static void Main(string[] args) {
        string connectionString = 
@"Provider=MSOLAP;Data Source=localhost;Catalog=AdventureWorks Tabular Model SQL 2012";
        string queryString = @"
EVALUATE
CALCULATETABLE(
    VALUES('Product Subcategory'[Product Subcategory Name]),
    'Product Category'[Product Category Name] = ""Bikes"" )
";
        using (var connection = new OleDbConnection(connectionString)) {
            connection.Open();
            using (var command = new OleDbCommand(queryString, connection)) {
                using (var reader = command.ExecuteReader()) {
                    while (reader.Read()) {
                        Console.WriteLine(reader[0]);
                    }
                }
            }
        }

    }
}

The result returned in the DataReader is correctly typed (which is important if you have to manipulate the value of a number returned from a query without worrying about number formatting). The issue with OLE DB MSOLAP provider is related to the parameters. In fact, if you want to pass a parameter to the previous query, you will get an error. For example, consider the following example, in which the modified/added lines have been highlighted.

using System;
using System.Data.OleDb;

class Program {
    static void Main() {
        string connectionString = 
@"Provider=MSOLAP;Data Source=localhost;Catalog=AdventureWorks Tabular Model SQL 2012";
        string queryString = @"
EVALUATE
CALCULATETABLE(
    VALUES('Product Subcategory'[Product Subcategory Name]),
    'Product Category'[Product Category Name] = @Category )
";
        using (var connection = new OleDbConnection(connectionString)) {
            connection.Open();
            using (var command = new OleDbCommand(queryString, connection)) {
                command.Parameters.AddWithValue("@Category", "Bikes");
                using (var reader = command.ExecuteReader()) {
                    while (reader.Read()) {
                        Console.WriteLine(reader[0]);
                    }
                }
            }
        }
    }
}

As you can see, the constant “Bikes” have been replaced by the @Category parameter in the DAX query and the AddWithValue function called on command.Parameters add the corresponding parameter to the OleDbCommand object. However, if you execute this code you get an error as a result.

Unhandled Exception: System.Data.OleDb.OleDbException: The following system error occurred:  The parameter is incorrect.
Error Code = 0x80070057, External Code = 0x00000000:.
   at System.Data.OleDb.OleDbCommand.ProcessResults(OleDbHResult hr)
   at System.Data.OleDb.OleDbCommand.ApplyParameterBindings(ICommandWithParameters commandWithParameters, tagDBPARAMBINDINFO[] bindInfo)
   at System.Data.OleDb.OleDbCommand.CreateAccessor()
   at System.Data.OleDb.OleDbCommand.InitializeCommand(CommandBehavior behavior, Boolean throwifnotsupported)
   at System.Data.OleDb.OleDbCommand.ExecuteCommand(CommandBehavior behavior, Object& executeResult)
   at System.Data.OleDb.OleDbCommand.ExecuteReaderInternal(CommandBehavior behavior, String method)
   at System.Data.OleDb.OleDbCommand.ExecuteReader(CommandBehavior behavior)
   at System.Data.OleDb.OleDbCommand.ExecuteReader()
   at Program.Main() in C:DevsrcQueryTabularProgram.cs:line 17

The problem is related to a problem in System.Data.OleDb assembly in .NET, as described in a Connect bug that has never been fixed despite the “Fixed” state of the issue. The only possible workaround is using ADOMD.NET instead of OLE DB. It might seem a strange approach, because MD means MultiDimensional and a query in DAX is all but Multidimensional. However, because ADOMD.NET allows you to put parameters in an AdomdCommand and to read the result in a DataReader, you can easily translate the previous example in the following one, which uses ADOMD.NET (which is a separate download, look for it in the more recent SQL Server Feature Pack) instead of OLE DB.

using System;
using Microsoft.AnalysisServices.AdomdClient;

class Program {
    static void Main(string[] args) {
        string connectionString = 
@"Provider=MSOLAP;Data Source=localhost;Catalog=AdventureWorks Tabular Model SQL 2012";
        string queryString = @"
EVALUATE
CALCULATETABLE(
    VALUES('Product Subcategory'[Product Subcategory Name]),
    'Product Category'[Product Category Name] = @Category )
";
        AdomdConnection connection = new AdomdConnection();
        connection.ConnectionString = connectionString;
        connection.Open();
        AdomdCommand cmd = new AdomdCommand(queryString);
        cmd.Parameters.Add("Category", "Bikes");
        cmd.Connection = connection;
        using (var reader = cmd.ExecuteReader()) {
            while (reader.Read()) {
                Console.WriteLine(reader[0]);
            }
        }
    }
}

In conclusion, you should always consider using ADOMD.NET whenever you want to query an Analysis Services database from your own .NET code, regardless your query is written in DAX, MDX od DMX.