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.SdkMicrosoft.Xrm.Sdk.QueryMicrosoft.Xrm.Sdk.MessagesMicrosoft.Xrm.Sdk.MetadataMicrosoft.Crm.Sdk.Messages
The principal service abstraction is IOrganizationService.
Its core operations include:
| Method | Purpose |
|---|---|
Create | Create a Dataverse record |
Retrieve | Retrieve one record |
RetrieveMultiple | Execute a supported query |
Update | Update a record |
Delete | Delete a record |
Associate | Associate related records |
Disassociate | Remove supported associations |
Execute | Submit 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
| Column | Type | Purpose |
|---|---|---|
pea_name | Primary name | Record identifier |
pea_externalcode | Single line of text | External reference |
pea_description | Multiple lines of text | Example payload |
pea_amount | Decimal number | Numeric test value |
pea_status | Choice | Lifecycle status |
pea_lastupdated | Date and time | Experiment timestamp |
4.2 SDK Operation
Logical name: pea_sdkoperation
| Column | Type | Purpose |
|---|---|---|
pea_name | Primary name | Operation name |
pea_requesttype | Single line of text | SDK request type |
pea_description | Multiple lines of text | Operation objective |
pea_expectedresult | Multiple lines of text | Expected behavior |
pea_enabled | Yes/No | Scenario availability |
4.3 SDK Execution
Logical name: pea_sdkexecution
| Column | Type | Purpose |
|---|---|---|
pea_name | Primary name | Execution identifier |
pea_operation | Single line of text | Operation name |
pea_requesttype | Single line of text | SDK request type |
pea_success | Yes/No | Expected result matched |
pea_durationms | Whole number | Client-observed duration |
pea_recordid | Single line of text | Target record GUID |
pea_errorcode | Single line of text | Fault code |
pea_message | Multiple lines of text | Sanitized diagnostics |
pea_executedon | Date and time | Execution 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
| Dimension | Late-bound | Early-bound |
|---|---|---|
| Entity representation | Generic Entity | Generated classes |
| Column references | Strings | Generated properties |
| Compile-time assistance | Limited | Greater |
| Schema flexibility | High | More strongly coupled |
| Code generation | Not required | Required |
| Metadata synchronization | Developer-managed | Regeneration 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:
WhoAmIRequestRetrieveEntityRequestRetrieveAttributeRequestUpsertRequestExecuteMultipleRequestExecuteTransactionRequest
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.DataverseSdkLabcd APP007.DataverseSdkLabdotnet add package Microsoft.PowerPlatform.Dataverse.Client
The first executable experiment will:
- Authenticate using
ServiceClient. - Confirm the Dataverse identity.
- Create a record.
- Retrieve it.
- Update a numeric value.
- Retrieve the updated value.
- Execute a query.
- Delete the record.
- Verify deletion.
- 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); } }}// CREATErecordId = 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}");// RETRIEVEEntity retrieved = Measure( "Retrieve", () => service.Retrieve( RecordTable, recordId, new ColumnSet( "pea_name", "pea_amount")), record => $"Retrieved {record.Id}");// UPDATEMeasure( "Update", () => { var update = new Entity(RecordTable, recordId) { ["pea_amount"] = 300m, ["pea_lastupdated"] = DateTime.UtcNow }; service.Update(update); return true; }, _ => "Updated amount");// VERIFY UPDATEdecimal 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}");// QUERYint 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}");// DELETEMeasure( "Delete", () => { service.Delete(RecordTable, recordId); return true; }, _ => "Record deleted");// VERIFY DELETEint 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.
| Characteristic | ExecuteMultiple | ExecuteTransaction |
|---|---|---|
| Multiple requests in one service call | Yes | Yes |
| Individual requests processed independently | Yes | No |
| Shared database transaction | No | Yes |
| Partial success possible | Yes | No, for transactionally included Dataverse changes |
| Rollback when one request fails | Only the failing request’s transaction | Entire request transaction |
| Primary purpose | Reduce round trips | Atomic 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
| Metric | Purpose |
|---|---|
| Total SDK executions | Number of recorded operations |
| Successful executions | Operations marked successful |
| Failed executions | Operations marked unsuccessful |
| Average duration | Mean client-measured execution time |
| Latest execution | Most recent operation |
| Fault distribution | Errors 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
| Experiment | Expected observation |
|---|---|
| Invalid table logical name | Dataverse service fault |
| Invalid column logical name | Dataverse service fault |
| Missing Create privilege | Authorization failure |
| Invalid record GUID | Record retrieval failure |
| Invalid attribute value | Validation failure |
| Duplicate alternate key | Unique-key violation |
| Network timeout | Potentially ambiguous write outcome |
| ExecuteMultiple with one invalid request | Individual fault and possible partial success |
| ExecuteTransaction with one invalid request | Transaction rollback |
| Concurrent updates | Potential 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
- Use the SDK for .NET — Organization Service architecture and SDK operations.
- Quickstart: SDK for .NET sample — C# connection and service operations.
- Use messages with the SDK for .NET — Organization request/response contracts.
- Execute multiple requests using the SDK — Batch execution behavior.
- Execute messages in a single database transaction — Transaction semantics.
- Don’t use batch request types in plug-ins and workflow activities — Server-side batch guidance.
- 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.
