ADO.NET Provider - MarkMpn/Sql4Cds GitHub Wiki
The MarkMpn.Sql4Cds.Engine NuGet package allows you to include SQL 4 CDS
functionality in your own .NET applications. This is exposed using an ADO.NET provider which you can access using the
Sql4CdsConnection
class.
You can create a connection using a standard XRM connection string:
using (var con = new Sql4CdsConnection("AuthType=ClientSecret;url=https://contosotest.crm.dynamics.com;ClientId={AppId};ClientSecret={ClientSecret}"))
using (var cmd = con.CreateCommand())
{
cmd.CommandText = "SELECT name FROM account";
using (var reader = cmd.ExecuteReader())
{
// ...
}
}
If you already have an IOrganizationService
instance you can use that instead:
using (var con = new Sql4CdsConnection(svc))
// ...
When you restore the package using .NET Framework, it will expect an IOrganizationService
from the Microsoft.CrmSdk.XrmTooling.CoreAssembly
package. If you use .NET Core it will use the Microsoft.PowerPlatform.Dataverse.Client
version instead.
To execute a query that spans multiple instances, pass each one to the connection constructor:
using (var con = new Sql4CdsConnection(svc1, svc2))
// ...
Queries will use the first instance (svc1
in this example) by default. Other instances can be referenced by the unique name of the organization.
Parameters can be added to the query as:
using (var con = new Sql4CdsConnection(connectionString))
using (var cmd = con.CreateCommand())
{
cmd.CommandText = "INSERT INTO account (name) VALUES (@name)";
var nameParam = cmd.CreateParameter();
nameParam.Name = "@name";
nameParam.Value = "My New Account";
cmd.Parameters.Add(nameParam);
// Add the new account
cmd.ExecuteNonQuery();
}
Values in primary key and lookup attributes are returned as SqlEntityReference
values. This encapsulates the guid and entity logical name in a similar way to the
SDK EntityReference
type, but also the name of the data source the value came from in cross instance queries and provides SQL-compatible NULL and ordering semantics.
This type can be cast to both the Guid
and EntityReference
types, so you any of these options work for extracting data from
a data reader:
// Get a guid value directly from the data reader
var guid = reader.GetGuid(0);
// Get the underlying SqlEntityReference value from the data reader and cast it
var raw = (SqlEntityReference)reader.GetValue(0);
var guid2 = (Guid)raw;
var entityReference = (EntityReference)raw;
If you prefer to only ever receive the underlying guid value, e.g. for compatibility with systems that cannot handle custom types
being returned from the IDataReader.GetValue
method, you can set the ReturnEntityReferenceAsGuid
property on the connection
to true
:
using (var con = new Sql4CdsConnection(org))
using (var cmd = con.CreateCommand())
{
con.ReturnEntityReferenceAsGuid = true;
cmd.CommandText = "SELECT accountid FROM account";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
var guid = (Guid)reader.GetValue(0);
// ...
}
}
}
The SQL 4 CDS ADO.NET provider is compatible with the popular Dapper micro-ORM, so you can retrieve data using code like:
class Account
{
public string Name { get; set; }
public int? Employees { get; set; }
}
var accounts = con.Query<Account>("SELECT name, employees FROM account WHERE createdon >= @yesterday", new { Yesterday = DateTime.Today.AddDays(-1) });
If you retrieve primary key or lookup fields you can choose to map these to Guid
, SqlEntityReference
or EntityReference
properties.
This is the default option - define your properties using the SqlEntityReference
type.
class Account
{
public SqlEntityReference AccountId { get;set; }
public string Name { get; set; }
public int? Employees { get; set; }
}
var accounts = con.Query<Account>("SELECT accountid, name, employees FROM account WHERE createdon >= @yesterday", new { Yesterday = DateTime.Today.AddDays(-1) });
If you want to pass a SqlEntityReference
value as a parameter you will need to register a custom type handler with Dapper:
class SqlEntityReferenceTypeHandler : SqlMapper.TypeHandler<SqlEntityReference>
{
public override SqlEntityReference Parse(object value)
{
if (value is SqlEntityReference ser)
return ser;
throw new NotSupportedException();
}
public override void SetValue(IDbDataParameter parameter, SqlEntityReference value)
{
parameter.Value = value;
}
}
SqlMapper.AddTypeHandler(new SqlEntityReferenceTypeHandler());
con.Execute("INSERT INTO account (name) VALUES (@name)", new { name = "Dapper" });
var id = con.ExecuteScalar<SqlEntityReference>("SELECT @@IDENTITY");
var name = con.ExecuteScalar<string>("SELECT name FROM account WHERE accountid = @id", new { id });
If you only require the guid part of identifiers, set the ReturnEntityReferenceAsGuid
property on the connectionas described above.
class Account
{
public Guid AccountId { get;set; }
public string Name { get; set; }
public int? Employees { get; set; }
}
con.ReturnEntityReferenceAsGuid = true;
var accounts = con.Query<Account>("SELECT accountid, name, employees FROM account WHERE createdon >= @yesterday", new { Yesterday = DateTime.Today.AddDays(-1) });
If you want to use the standard SDK EntityReference
type you will need to register a custom type handler with Dapper:
class EntityReferenceTypeHandler : SqlMapper.TypeHandler<EntityReference>
{
public override EntityReference Parse(object value)
{
if (value is SqlEntityReference ser)
return ser;
throw new NotSupportedException();
}
public override void SetValue(IDbDataParameter parameter, EntityReference value)
{
parameter.Value = (SqlEntityReference)value;
}
}
SqlMapper.AddTypeHandler(new EntityReferenceTypeHandler());
class Account
{
public EntityReference AccountId { get;set; }
public string Name { get; set; }
public int? Employees { get; set; }
}
var accounts = con.Query<Account>("SELECT accountid, name, employees FROM account WHERE createdon >= @yesterday", new { Yesterday = DateTime.Today.AddDays(-1) });