Dataverse SDK Laboratory: C# ServiceClient, Organization Requests, Execution Semantics, and Production Diagnostics

Engineering Microsoft Dataverse integrations through the .NET SDK, exploring service contracts, request execution, typed entities, batching, authentication, error handling, and observability.

Power Platform Engineering Academy — Five Years in Five Days

Application: APP-007 — Dataverse SDK Laboratory
Technical domain: Microsoft Dataverse
Technologies: C#, .NET, Microsoft.PowerPlatform.Dataverse.Client, Microsoft.Xrm.Sdk, Organization Service, Model-Driven Apps
Status: Pre-implementation engineering design — not yet executed


Introduction

The Dataverse SDK is often introduced as a convenient way to perform CRUD operations from C#.

That description is correct, but incomplete.

The SDK provides a programming model for interacting with the Dataverse Organization Service. Its abstractions include entity representations, metadata contracts, query objects, specialized requests, batch execution, optimistic concurrency, and platform-specific fault handling.

For an experienced C# developer, the challenge is not learning to call Create, Retrieve, Update, or Delete.

The challenge is understanding what those calls mean across the boundary between the client application and the Dataverse service.

When does an SDK operation execute? Which validations occur on the server? How are specialized messages processed? What happens when a request fails after reaching the service? How can the application distinguish transport errors, authorization failures, validation errors, and concurrency conflicts?

These questions become especially important when the SDK is used in Azure Functions, background workers, integration services, or enterprise applications.

APP-007 — Dataverse SDK Laboratory explores these mechanisms using an independent C# application, persistent execution diagnostics, and a functional Model-Driven App.

Our central engineering question is:

How can we use the Dataverse SDK as an explicit, observable service contract rather than treating it as a simple database access library?

1. Understanding the Dataverse SDK Architecture

The modern .NET client package is:

Microsoft.PowerPlatform.Dataverse.Client

It provides the ServiceClient class, which supports Dataverse authentication and service operations.

The SDK also uses types from namespaces such as:

Microsoft.Xrm.Sdk
Microsoft.Xrm.Sdk.Query
Microsoft.Xrm.Sdk.Messages
Microsoft.Xrm.Sdk.Metadata
Microsoft.Crm.Sdk.Messages

The principal service abstraction is IOrganizationService.

Its core operations include:

MethodPurpose
CreateCreate a Dataverse record
RetrieveRetrieve one record
RetrieveMultipleExecute a supported query
UpdateUpdate a record
DeleteDelete a record
AssociateAssociate related records
DisassociateRemove supported associations
ExecuteSubmit an Organization request

The Execute method is particularly important.

It exposes specialized operations through request and response classes.

For example:

var response = (WhoAmIResponse)service.Execute(
new WhoAmIRequest());

This executes a Dataverse message rather than querying a table.

The SDK is therefore not merely an object-relational mapper.

It is a client interface to a service that implements a much broader operation model.

Reference: Use the Dataverse SDK for .NET.

2. ServiceClient Versus IOrganizationService

ServiceClient provides a concrete client implementation.

IOrganizationService defines the core synchronous service contract.

A useful engineering pattern is to keep business logic dependent on the interface rather than directly coupling every component to ServiceClient.

public sealed class DataverseRepository
{
private readonly IOrganizationService _service;
public DataverseRepository(IOrganizationService service)
{
_service = service;
}
public Guid CreateRecord(Entity entity)
{
return _service.Create(entity);
}
}

This supports separation of concerns and controlled testing.

However, not every capability exposed by ServiceClient is represented by IOrganizationService.

Asynchronous operations and client-specific diagnostics may require additional interfaces or the concrete client.

The laboratory will examine these distinctions rather than assume that every SDK abstraction exposes identical functionality.

3. Application Architecture

The proposed architecture is:

APP-007 Dataverse SDK Laboratory
│
├── C# SDK Runner
│ ├── Authentication
│ ├── WhoAmI
│ ├── CRUD Operations
│ ├── QueryExpression
│ ├── Organization Requests
│ ├── ExecuteMultiple
│ ├── Fault Inspection
│ └── Performance Measurement
│
├── Dataverse
│ ├── pea_sdkrecord
│ ├── pea_sdkoperation
│ └── pea_sdkexecution
│
└── Model-Driven App
├── Entry Dashboard
├── SDK Records
├── SDK Operations
├── Execution Diagnostics
└── Failure Analysis

The C# client executes SDK operations.

Dataverse stores the experimental data and diagnostics.

The Model-Driven App provides the operational interface for examining the results.

The application must expose actual persisted state, not simply display messages produced by the console application.

4. Dataverse Data Model

APP-007 introduces three custom tables.

4.1 SDK Record

Logical name: pea_sdkrecord

ColumnTypePurpose
pea_namePrimary nameRecord identifier
pea_externalcodeSingle line of textExternal reference
pea_descriptionMultiple lines of textExample payload
pea_amountDecimal numberNumeric test value
pea_statusChoiceLifecycle status
pea_lastupdatedDate and timeExperiment timestamp

4.2 SDK Operation

Logical name: pea_sdkoperation

ColumnTypePurpose
pea_namePrimary nameOperation name
pea_requesttypeSingle line of textSDK request type
pea_descriptionMultiple lines of textOperation objective
pea_expectedresultMultiple lines of textExpected behavior
pea_enabledYes/NoScenario availability

4.3 SDK Execution

Logical name: pea_sdkexecution

ColumnTypePurpose
pea_namePrimary nameExecution identifier
pea_operationSingle line of textOperation name
pea_requesttypeSingle line of textSDK request type
pea_successYes/NoExpected result matched
pea_durationmsWhole numberClient-observed duration
pea_recordidSingle line of textTarget record GUID
pea_errorcodeSingle line of textFault code
pea_messageMultiple lines of textSanitized diagnostics
pea_executedonDate and timeExecution timestamp

The execution table allows engineers to compare operation outcomes and investigate failures.

It must distinguish an operation that failed as expected from an operation that failed unexpectedly.

5. Authentication and Client Lifecycle

The SDK supports multiple authentication approaches.

For external applications, common approaches include delegated user authentication and application authentication.

For production integrations, the choice depends on the hosting environment, identity requirements, and supported authentication mechanism.

The laboratory will use an environment variable:

DATAVERSE_CONNECTION

This variable contains the connection information used to initialize ServiceClient.

Example:

string connectionString =
Environment.GetEnvironmentVariable(
"DATAVERSE_CONNECTION")
?? throw new InvalidOperationException(
"Missing connection string.");
using var client = new ServiceClient(connectionString);
if (!client.IsReady)
{
throw new InvalidOperationException(
$"Connection failed: {client.LastError}");
}

The connection string must not be committed to source control.

A production implementation should use an appropriate secret-management mechanism.

The experiment should also capture the authenticated Dataverse identity through WhoAmIRequest.

var response = (WhoAmIResponse)client.Execute(
new WhoAmIRequest());
Console.WriteLine(response.UserId);

This confirms which Dataverse identity is executing the operations.

It does not establish that the identity has all required privileges.

6. Late-Bound Versus Early-Bound Entities

The SDK supports different approaches to representing Dataverse records.

6.1 Late-bound entities

A late-bound entity uses the generic Entity class.

var record = new Entity("pea_sdkrecord");
record["pea_name"] = "SDK Test";
record["pea_amount"] = 125.50m;
Guid id = service.Create(record);

Advantages include flexibility and reduced dependency on generated entity classes.

Disadvantages include string-based column references and reduced compile-time checking.

6.2 Early-bound entities

Early-bound classes are generated from Dataverse metadata.

They provide typed properties and generated entity definitions.

This can improve discoverability and reduce some classes of schema-reference errors.

However, generated code introduces a dependency on the metadata version used during generation.

Schema changes may require regeneration and redeployment.

6.3 Engineering comparison

DimensionLate-boundEarly-bound
Entity representationGeneric EntityGenerated classes
Column referencesStringsGenerated properties
Compile-time assistanceLimitedGreater
Schema flexibilityHighMore strongly coupled
Code generationNot requiredRequired
Metadata synchronizationDeveloper-managedRegeneration workflow

The laboratory should evaluate both approaches.

The initial executable sample will use late-bound entities to keep the SDK contract visible.

7. CRUD Operations and the Service Boundary

The SDK exposes simple methods for CRUD operations.

Create

var record = new Entity("pea_sdkrecord");
record["pea_name"] = "SDK Laboratory";
record["pea_amount"] = 250m;
Guid id = service.Create(record);

Retrieve

Entity record = service.Retrieve(
"pea_sdkrecord",
id,
new ColumnSet(
"pea_name",
"pea_amount"));

Update

var update = new Entity("pea_sdkrecord", id);
update["pea_amount"] = 300m;
service.Update(update);

Delete

service.Delete("pea_sdkrecord", id);

Each operation crosses the service boundary.

The client sends a request and receives either a result or an error.

A returned success indicates that the operation completed according to the service contract.

However, a client-side timeout does not necessarily prove that the server failed to commit a write.

This is the same ambiguous-outcome problem explored in APP-004 and APP-006.

8. OrganizationRequest and OrganizationResponse

Specialized operations use OrganizationRequest and OrganizationResponse.

Examples include:

  • WhoAmIRequest
  • RetrieveEntityRequest
  • RetrieveAttributeRequest
  • UpsertRequest
  • ExecuteMultipleRequest
  • ExecuteTransactionRequest

These requests expose operations beyond ordinary CRUD.

For example:

var request = new RetrieveEntityRequest
{
LogicalName = "pea_sdkrecord",
EntityFilters = EntityFilters.Attributes
};
var response = (RetrieveEntityResponse)
service.Execute(request);

This retrieves table metadata.

The result describes the table’s attributes rather than its business records.

This connects directly to APP-001’s metadata exploration.

9. ExecuteMultiple and Batch Processing

ExecuteMultipleRequest allows multiple Organization requests to be submitted together.

Its purpose is to reduce the overhead of making many separate service calls.

However, batching does not automatically make the operations transactional.

A batch may contain successful and failed requests.

The caller must inspect individual responses and faults.

Conceptually:

var batch = new ExecuteMultipleRequest
{
Settings = new ExecuteMultipleSettings
{
ContinueOnError = true,
ReturnResponses = true
},
Requests = new OrganizationRequestCollection()
};
batch.Requests.Add(new CreateRequest
{
Target = firstRecord
});
batch.Requests.Add(new CreateRequest
{
Target = secondRecord
});
var response = (ExecuteMultipleResponse)
service.Execute(batch);

Each item must be inspected.

ContinueOnError affects how processing proceeds after failures.

ReturnResponses controls which successful responses are returned.

Neither setting turns the batch into an all-or-nothing transaction.

The distinction between ExecuteMultipleRequest and ExecuteTransactionRequest will become central in APP-008.

Reference: Execute multiple requests using the SDK.

10. Fault Handling

Dataverse SDK operations can return service faults.

A robust application should distinguish:

  • Authentication failures.
  • Authorization failures.
  • Invalid metadata references.
  • Validation errors.
  • Concurrency conflicts.
  • Service protection throttling.
  • Transport failures.
  • Timeouts with unknown write outcomes.

SDK service faults may be exposed through FaultException<OrganizationServiceFault>.

A useful diagnostic pattern is:

catch (FaultException<OrganizationServiceFault> ex)
{
Console.Error.WriteLine(
$"Dataverse fault code: {ex.Detail.ErrorCode}");
Console.Error.WriteLine(
$"Message: {ex.Detail.Message}");
throw;
}

A negative test should verify the expected fault condition.

It must not treat every exception as proof that the platform correctly rejected an invalid operation.

11. Independent C# SDK Laboratory

The proposed project is:

PowerPlatformEngineeringAcademy/
└── Dataverse/
└── APP007.DataverseSdkLab/
├── APP007.DataverseSdkLab.csproj
└── Program.cs

Create the project:

dotnet new console --name APP007.DataverseSdkLab
cd APP007.DataverseSdkLab
dotnet add package Microsoft.PowerPlatform.Dataverse.Client

The first executable experiment will:

  1. Authenticate using ServiceClient.
  2. Confirm the Dataverse identity.
  3. Create a record.
  4. Retrieve it.
  5. Update a numeric value.
  6. Retrieve the updated value.
  7. Execute a query.
  8. Delete the record.
  9. Verify deletion.
  10. Record execution diagnostics.

The implementation will measure each SDK operation separately.

It will also inspect service faults rather than silently swallowing exceptions.

12. Complete C# SDK Runner

The following Program.cs demonstrates the core service operations and persists their results in the laboratory’s diagnostic table.

It assumes that pea_sdkrecord and pea_sdkexecution exist with the columns defined earlier.

The program uses a unique external code for each execution to avoid collisions with previous experiments.

using System;
using System.Diagnostics;
using System.ServiceModel;
using Microsoft.Crm.Sdk.Messages;
using Microsoft.PowerPlatform.Dataverse.Client;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
const string RecordTable = "pea_sdkrecord";
const string ExecutionTable = "pea_sdkexecution";
string connectionString =
Environment.GetEnvironmentVariable("DATAVERSE_CONNECTION")
?? throw new InvalidOperationException(
"DATAVERSE_CONNECTION is missing.");
using var client = new ServiceClient(connectionString);
if (!client.IsReady)
throw new InvalidOperationException(
$"Connection failed: {client.LastError}");
IOrganizationService service = client;
var identity = (WhoAmIResponse)service.Execute(
new WhoAmIRequest());
Console.WriteLine($"Connected user: {identity.UserId}");
string externalCode = $"APP007-{Guid.NewGuid():N}";
Guid recordId = Guid.Empty;
T Measure<T>(
string operation,
Func<T> action,
Func<T, string> resultDescription)
{
var stopwatch = Stopwatch.StartNew();
bool success = false;
string message = "";
string errorCode = "";
try
{
T result = action();
success = true;
message = resultDescription(result);
return result;
}
catch (FaultException<OrganizationServiceFault> ex)
{
errorCode = ex.Detail.ErrorCode.ToString("X8");
message = ex.Detail.Message;
throw;
}
catch (Exception ex)
{
message = $"{ex.GetType().Name}: {ex.Message}";
throw;
}
finally
{
stopwatch.Stop();
Console.WriteLine(
$"{operation}: {stopwatch.ElapsedMilliseconds} ms");
var execution = new Entity(ExecutionTable)
{
["pea_name"] = $"EXEC-{Guid.NewGuid():N}",
["pea_operation"] = operation,
["pea_requesttype"] = operation,
["pea_success"] = success,
["pea_durationms"] = checked(
(int)stopwatch.ElapsedMilliseconds),
["pea_recordid"] = recordId == Guid.Empty
? ""
: recordId.ToString(),
["pea_errorcode"] = errorCode,
["pea_message"] = message,
["pea_executedon"] = DateTime.UtcNow
};
try
{
service.Create(execution);
}
catch (Exception logException)
{
Console.Error.WriteLine(
$"Diagnostic logging failed: " +
logException.GetType().Name);
}
}
}
// CREATE
recordId = Measure(
"Create",
() =>
{
var record = new Entity(RecordTable)
{
["pea_name"] = "SDK Laboratory",
["pea_externalcode"] = externalCode,
["pea_amount"] = 250m,
["pea_lastupdated"] = DateTime.UtcNow
};
return service.Create(record);
},
id => $"Created record {id}");
// RETRIEVE
Entity retrieved = Measure(
"Retrieve",
() => service.Retrieve(
RecordTable,
recordId,
new ColumnSet(
"pea_name",
"pea_amount")),
record => $"Retrieved {record.Id}");
// UPDATE
Measure(
"Update",
() =>
{
var update = new Entity(RecordTable, recordId)
{
["pea_amount"] = 300m,
["pea_lastupdated"] = DateTime.UtcNow
};
service.Update(update);
return true;
},
_ => "Updated amount");
// VERIFY UPDATE
decimal updatedAmount = Measure(
"VerifyUpdate",
() =>
{
Entity updated = service.Retrieve(
RecordTable,
recordId,
new ColumnSet("pea_amount"));
return updated.GetAttributeValue<decimal>(
"pea_amount");
},
value => $"Updated amount = {value}");
if (updatedAmount != 300m)
throw new InvalidOperationException(
$"Unexpected amount: {updatedAmount}");
// QUERY
int queryCount = Measure(
"QueryExpression",
() =>
{
var query = new QueryExpression(RecordTable)
{
ColumnSet = new ColumnSet(
"pea_name",
"pea_externalcode")
};
query.Criteria.AddCondition(
"pea_externalcode",
ConditionOperator.Equal,
externalCode);
return service.RetrieveMultiple(query)
.Entities.Count;
},
count => $"Matching records: {count}");
if (queryCount != 1)
throw new InvalidOperationException(
$"Expected one record, found {queryCount}");
// DELETE
Measure(
"Delete",
() =>
{
service.Delete(RecordTable, recordId);
return true;
},
_ => "Record deleted");
// VERIFY DELETE
int remainingCount = Measure(
"VerifyDelete",
() =>
{
var query = new QueryExpression(RecordTable)
{
ColumnSet = new ColumnSet(
"pea_name")
};
query.Criteria.AddCondition(
"pea_externalcode",
ConditionOperator.Equal,
externalCode);
return service.RetrieveMultiple(query)
.Entities.Count;
},
count => $"Remaining records: {count}");
if (remainingCount != 0)
throw new InvalidOperationException(
"Deleted record is still visible.");
Console.WriteLine("SDK laboratory completed.");

12.1 What the code demonstrates

The implementation explicitly separates business operations from execution diagnostics.

It records the duration of each SDK operation and captures service fault information.

It also verifies persisted state after update and delete operations.

This is stronger than assuming that a successful method return proves every intended business invariant.

12.2 Limitations

The example does not implement retries, concurrency testing, asynchronous execution, or batch processing.

Its diagnostic logging uses the same Dataverse connection as the experiment. A failure affecting the service can therefore also prevent diagnostic persistence.

Additionally, the Measure wrapper records successful service calls before certain assertions are evaluated. An operation can therefore be logged as successful even if a subsequent business assertion fails.

A production-quality test harness should distinguish transport success, platform success, and business validation success.

13. ExecuteMultiple Versus ExecuteTransaction

This distinction deserves particular attention.

CharacteristicExecuteMultipleExecuteTransaction
Multiple requests in one service callYesYes
Individual requests processed independentlyYesNo
Shared database transactionNoYes
Partial success possibleYesNo, for transactionally included Dataverse changes
Rollback when one request failsOnly the failing request’s transactionEntire request transaction
Primary purposeReduce round tripsAtomic execution

Microsoft documents that each ExecuteMultipleRequest item is processed in its own database transaction. ExecuteTransactionRequest, by contrast, executes its contained operations in a single transaction and rolls back changes when one fails. <Cite refs={[“turn136106search2″,”turn136106search0”]}/>

Neither mechanism should be treated as a general distributed transaction spanning Dataverse and arbitrary external systems.

Microsoft also advises against using batch request types inside synchronous plug-ins and workflow activities. <Cite ref=”turn136106search7″/>

APP-008 will investigate rollback and transaction boundaries experimentally.

14. Performance and Throughput

SDK performance depends on several factors:

  • Network latency.
  • Authentication and connection reuse.
  • Number of service calls.
  • Payload size.
  • Query complexity.
  • Dataverse security evaluation.
  • Plug-ins and synchronous business logic.
  • Service protection limits.
  • Client-side concurrency.

The laboratory should compare individual requests against ExecuteMultipleRequest using the same operations and controlled datasets.

A batch can reduce network round trips, but it does not automatically execute its contained operations in parallel.

Microsoft explicitly documents sequential processing within batch requests. <Cite ref=”turn136106search7″/>

Performance measurements should report total elapsed time, completed operations, failures, and throughput.

A faster execution that silently loses failed operations is not a valid improvement.

15. Model-Driven App: PEA Dataverse SDK Laboratory

The operational application will be named:

PEA Dataverse SDK Laboratory

Entry dashboard

MetricPurpose
Total SDK executionsNumber of recorded operations
Successful executionsOperations marked successful
Failed executionsOperations marked unsuccessful
Average durationMean client-measured execution time
Latest executionMost recent operation
Fault distributionErrors grouped by fault code

The dashboard should include a recent-executions view.

SDK Records

The SDK Records page allows engineers to inspect the records created by the C# application.

SDK Operations

The SDK Operations page documents supported experimental scenarios and their expected results.

SDK Executions

The SDK Executions page displays request types, execution durations, outcomes, fault codes, and diagnostic messages.

The Model-Driven App must allow engineers to verify the actual records and inspect the diagnostic evidence.

16. Failure Injection

ExperimentExpected observation
Invalid table logical nameDataverse service fault
Invalid column logical nameDataverse service fault
Missing Create privilegeAuthorization failure
Invalid record GUIDRecord retrieval failure
Invalid attribute valueValidation failure
Duplicate alternate keyUnique-key violation
Network timeoutPotentially ambiguous write outcome
ExecuteMultiple with one invalid requestIndividual fault and possible partial success
ExecuteTransaction with one invalid requestTransaction rollback
Concurrent updatesPotential lost-update behavior without concurrency control

Each negative test should verify the specific expected failure rather than accept any exception.

17. Production Engineering Considerations

The SDK simplifies Dataverse interaction, but it does not eliminate architectural responsibilities.

A robust integration should define:

Identity: Which Dataverse user or application user executes operations?

Authorization: Which privileges are required?

Consistency: Are operations independent, atomic, or eventually consistent?

Idempotency: Can requests be safely repeated?

Concurrency: How are conflicting updates detected?

Observability: Can engineers reconstruct failures and identify affected records?

Resilience: Which failures should be retried, and under what conditions?

ALM: How are tables, keys, plug-ins, and application components deployed consistently?

These concerns must be addressed explicitly rather than delegated to the SDK.

18. Microsoft Learn Technical References

  1. Use the SDK for .NET — Organization Service architecture and SDK operations.
  2. Quickstart: SDK for .NET sample — C# connection and service operations.
  3. Use messages with the SDK for .NET — Organization request/response contracts.
  4. Execute multiple requests using the SDK — Batch execution behavior.
  5. Execute messages in a single database transaction — Transaction semantics.
  6. Don’t use batch request types in plug-ins and workflow activities — Server-side batch guidance.
  7. Service protection API limits — Request limits and throttling.

Conclusion

The Dataverse SDK is more than a convenient C# CRUD library.

It exposes a structured service contract that includes business operations, metadata requests, specialized messages, batching, and transaction capabilities.

Understanding this contract requires examining execution semantics, failure handling, identity, security, and the boundaries between client and server.

APP-007 provides a foundation for investigating these mechanisms through reproducible C# experiments and a Model-Driven App that exposes persisted results and diagnostics.

The central engineering principle is:

Treat every SDK operation as a service interaction with explicit identity, execution, failure, and consistency semantics—not as an ordinary in-process database method.

The implementation remains pending execution and validation in a Dataverse environment.


Power Platform Engineering Academy — Five Years in Five Days

Article 007/100 — Dataverse SDK Laboratory

Understand the contract. Inspect the response. Verify the state. Engineer for failure.

Edvaldo Guimrães Filho Avatar

Published by