DevPack4Dataverse aka DevPack For Dataverse is a set of instructions built on top of the SDK for Dataverse. Designed with the idea of optimizing and speeding up work with external communication with Dataverse API.
graph TB
A[DataverseDevPack]
B[ExecuteMultiple]
C[FieldDrill]
D[SdkProxy]
E[CreateLinqExpressionBuilder]
A --> |Can use ExecuteMultiple.CreateRequestBuilder|B
A --> C
A --> D
A --> E
E --> |LINQ expressions as param|A
C --> D
B --> D
Connections are created in the background. Wait for the required pool size before running work:
dataverseDevPack.WaitForConnections(4);
await dataverseDevPack.WaitForConnectionsAsync(4, cancellationToken);The synchronous method waits until the count is reached; the asynchronous overload supports cancellation. Both return immediately when the count is already available. They throw InvalidOperationException if fewer creators are configured than requested or if the remaining creators fail. A zero count returns immediately. The same methods are available directly on dataverseDevPack.SdkProxy.
Gives ability to quickly call dataverse endpoint with optimized packs of requests, multiple connections will be used to multiply speed and skip request per minute limits. In example bellow you can see how you can build unlimited in size requests that later will be sent to datavserse endpoint in packs.
Entity[] recordsInCrm = dataverseDevPack.SdkProxy.RetrieveMultiple(new QueryExpression
{
EntityName = EntityName,
ColumnSet = new ColumnSet(false)
});
ExecuteMultipleRequestBuilder executeMultipleRequestBuilder = dataverseDevPack.ExecuteMultiple
.CreateRequestBuilder();
foreach (var item in recordsInCrm)
{
executeMultipleRequestBuilder.AddDelete(item.ToEntityReference());
}
ExecuteMultipleLogicResult executeStatistic = await dataverseDevPack.ExecuteMultiple
.ExecuteAsync(executeMultipleRequestBuilder,
new ExecuteMultipleRequestSettings
{
ErrorReport = (OrganizationRequest obj, string error) => Console.WriteLine($"Error: {error}"),
ReportProgress = (int cur, int max) => Console.Title = $"Progress[{cur}/{max}]",
ReportProgressInterval = TimeSpan.FromMilliseconds(100)
});Production notes:
- Requests are split into chunks of
RequestSize; it must be greater than zero. Resultsare ordered by the original request order, across all chunks. This matters for multi-block file uploads/downloads.- The full
ExecuteMultipleRequestSettingsoverload continues after request failures and reports them throughErrorReport; the simple settings overload stops at the first fault. ErrorReport,ReportProgressand the callbacks are optional;ReportProgressIntervalmust be positive.- The builder is thread-safe and preserves insertion order. A snapshot is taken when execution starts.
- Cancellation returns the partial result with
Cancelled = trueinstead of throwing. - Automatic parallelism uses the available connections but always uses at least one worker.
Executes request steps one after another. Each step can read the results of every previous step from the SequentialRequestContext passed to its factory:
SequentialRequestContext results = await dataverseDevPack
.CreateSequentialRequestBuilder()
.Create(new Entity("account") { ["name"] = "Contoso" })
.ExecuteAction(
"new_Action",
context => new EntityReference("account", context.CreatedId(0)),
(context, request) => request["flag"] = true)
.Retrieve(
"account",
context => context.CreatedId(0),
new ColumnSet("name"))
.ExecuteAsync();
// Inner logic with access to the results of steps 0, 1 and 2.
Guid createdId = results.CreatedId(0);
OrganizationResponse actionResponse = results.Response<OrganizationResponse>(1);
Entity retrieved = results.RetrievedEntity(2);Step indexes are zero-based. Execute() provides the synchronous variant and ExecuteAsync(cancellationToken) the asynchronous one. Steps stop at the first exception; there is no automatic transaction or rollback. The builder is mutable and not thread-safe, so create one builder per sequence. The same context is available to step factories through CreatedId, RetrievedEntity, Response<T>, indexing and Last.
Gives ability to go down by lookup fields simply by defining path as string with specified delimiter or by providing path as array of fields.
For example when we have lookup that points to record X, we can call FieldDrill with these parameters
await dataverseDevPack.FieldDrill.RetreiveAsync<string>(item.ToEntityReference(), "ownerid.fullname");
It will retrieve field fullname from lookup named ownerid in starting record entity reference.
FieldMethods.MoneyFieldTypeMethods provides helpers for transaction-currency and base-currency amounts:
MoneyFieldTypeMethods money = dataverseDevPack.FieldMethods.MoneyFieldTypeMethods;
money.SetValue(account, "creditlimit", 1500m);
decimal? amount = money.GetValue(account, "creditlimit");
decimal? baseAmount = money.GetBaseValue(account, "creditlimit");
decimal exchangeRate = await money.GetExchangeRateAsync(account);
decimal baseValue = money.ConvertToBase(amount.Value, exchangeRate);
decimal transactionValue = money.ConvertFromBase(baseValue, exchangeRate);
string formatted = money.GetFormattedValue(account, "creditlimit");SetValue sets only the transaction Money field; Dataverse calculates the read-only <field>_base field when the record is saved. The base value formula is transaction amount / exchange rate. If the record does not contain exchangerate, GetExchangeRate and GetExchangeRateAsync retrieve it from the transaction currency record. SetTransactionCurrency and GetTransactionCurrency work with the transactioncurrencyid lookup.
Gives ability to quickly execute any type of request to dataverse endpoint.
Gives ability to build expression that later can be used in Where method of early or late bound in OrganizationServiceContext class. In this example we'll try to retrieve 100 records by name. Maximum limit of expressions is 500, it's hard limit specified by SDK.
ILinqExpressionBuilder<Entity> builder = connectionManager.CreateLinqExpressionBuilder<Entity>();
foreach (string item in Enumerable.Range(1, 100).Select(p => $"Name {p}"))
{
builder.AddOr(p => p["xx_name"] == item);
}
if (builder.ExpressionsAdded > 0)
{
Expression<Func<Entity, bool>> finalExpression = builder.Result;
serviceContext.xxSet.Where(finalExpression).ToArray();
}Production notes:
- Builders are mutable and not thread-safe; create one builder per query and use it from a single thread.
- Check
ExpressionsAdded > 0before usingResult; an empty builder throwsInvalidOperationException. If there are no conditions, skip theWherecall to return unfiltered data. - Top-level
AddAnd(p => true)andAddOr(p => false)are boolean identities and are ignored.AddAnd(p => false)andAddOr(p => true)throwArgumentExceptionbecause the Dataverse LINQ provider cannot translate a generic always-false/always-true filter. - The Dataverse LINQ provider supports a limited set of expression shapes. The builder combines expression trees but does not validate them; unsupported expressions fail during query translation.
- Adding an empty builder, a builder to itself, or a builder whose
Resultis null throws an exception.
- Add model to model mapper for integration
- QueryBuilder for faster and better requests / also for LINQ
- LINQ support
- and more...