Balsoft.Hive.D365FO.OData 1.0.0

Prefix Reserved
dotnet add package Balsoft.Hive.D365FO.OData --version 1.0.0
                    
NuGet\Install-Package Balsoft.Hive.D365FO.OData -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Balsoft.Hive.D365FO.OData" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Balsoft.Hive.D365FO.OData" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Balsoft.Hive.D365FO.OData" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Balsoft.Hive.D365FO.OData --version 1.0.0
                    
#r "nuget: Balsoft.Hive.D365FO.OData, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Balsoft.Hive.D365FO.OData@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Balsoft.Hive.D365FO.OData&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Balsoft.Hive.D365FO.OData&version=1.0.0
                    
Install as a Cake Tool

Balsoft.Hive.D365FO.OData

The reusable core of a typed OData client for Dynamics 365 Finance and Operations. It knows how D365FO wants to be talked to; the entity classes for a specific tenant come from your own generated proxy package.

CI NuGet License

  • Entity-set aware CRUD on any generated DataServiceContext: First, Query, QueryNoTracking, FetchAllNoTracking, QueryPaged, AddAndCommitAsync, UpdateAndCommitAsync, DeleteAndCommitAsync. The entity set comes from the [EntitySet] attribute the OData code generator emits, never from a string in your code.
  • Write policy. D365FO rejects request bodies that carry read-only, computed or create-only fields ("update not allowed for field 'Type'"). Declare per entity what may be sent, which fields are create-only, and which key fields the server assigns; the request pipeline trims every POST and PATCH accordingly.
  • Authentication with a client secret or a managed identity (DefaultAzureCredential), cached and refreshed before expiry. Tokens go out as bearer headers on every request.
  • API Management ready. Subscription key header, Prefer: return=minimal so the client survives the backend's own @odata.context, optional Environment header.
  • Cross-company queries by default, retry on transient GET failures, per-request timeout.
  • Detach on failure. A rejected create, update or delete is detached from the context so one bad entity cannot poison every later commit in the same scope.
  • A base class for generated entity services, so a customer package only adds typed FindByKey methods.
Package Targets Dependencies
Balsoft.Hive.D365FO.OData net8.0, net10.0 Microsoft.OData.Client 8, Azure.Identity, Microsoft.Extensions.Http, Microsoft.Extensions.Caching.Memory, Polly 8

Install

dotnet add package Balsoft.Hive.D365FO.OData

Register

Resources below is the context class your OData code generator produced from the tenant's $metadata. It lives in your customer-specific package, not here.

using Balsoft.Hive.D365FO.OData;
using Microsoft.Dynamics.DataEntities;

services.Configure<D365ODataOptions>(configuration.GetSection("D365OData"));
services.Configure<D365AuthenticationOptions>(configuration.GetSection("D365Authentication"));

services.AddD365OData<Resources>(writePolicy: policy => policy
    .Entity<Worker>("PersonnelNumber", "FirstName", "LastName", "Gender", "SeniorityDate")
    .CreateOnly<Worker>("PartyNumber")
    .Entity<EmploymentV2>("PersonnelNumber", "LegalEntityId", "EmploymentId", "EmploymentStartDate", "EmploymentEndDate", "DimensionDisplayValue")
    .Entity<WorkerContactV3>("PersonnelNumber", "ElectronicAddressId", "Type", "Description", "Locator", "IsPrimary", "IsMobilePhone", "IsPrivate")
    .CreateOnly<WorkerContactV3>("Type")
    .ServerAssignedKeys("EmploymentId", "ElectronicAddressId", "AddressLocationId", "PartyNumber"));
{
  "D365OData": {
    "ServiceUrl": "https://my-apim.azure-api.net/d365fo-odata",
    "ApimSubscriptionKey": "<from Key Vault>",
    "Environment": "UAT"
  },
  "D365Authentication": {
    "Resource": "https://tenant.sandbox.operations.eu.dynamics.com",
    "TenantId": "…",
    "ClientId": "…",
    "ClientSecret": "<from Key Vault>"
  }
}

For a managed identity set "UseManagedIdentity": true (and ManagedIdentityClientId for a user-assigned one) instead of the client secret. The identity must be registered in D365FO under System administration > Setup > Azure Active Directory applications like any app registration.

Use

public sealed class WorkerService : D365EntityService<Worker>
{
    public WorkerService(ID365ODataClient client, ILogger<WorkerService> logger) : base(client, logger) { }

    public Worker? FindByKey(string personnelNumber) => First(x => x.PersonnelNumber == personnelNumber);
}

var worker = workers.FindByKey("229");
if (worker is null)
{
    await workers.UpsertAsync(new Worker { PersonnelNumber = "229", FirstName = "Julian", LastName = "Imhäuser" });
}
else
{
    worker.LastName = "Imhaeuser";
    await workers.UpdateAsync(worker);   // PATCH of the tracked instance, body trimmed by the write policy
}

Register the client once per scope. The context tracks entities, and a PATCH needs the tracked instance that a First or Query returned. Reads that only need data use the NoTracking variants.

OData actions and anything context-specific are reachable through ID365ODataClient<Resources>:

await client.ExecuteAsync(ctx => ctx.SomeActionImport(...).ExecuteAsync());

What a customer package adds

  • the generated entity classes and context (from the tenant's $metadata, including custom fields and custom entities),
  • one D365WritePolicy registration for the entities it writes,
  • one D365EntityService<T> subclass per entity it uses,
  • anything that needs generated types, such as financial dimension helpers.

That package changes whenever an entity or a custom field is added; this one does not.

Building

dotnet build
dotnet test

Releases are author-signed with the Balsoft GmbH certificate, whose key is held in Google Cloud KMS, and published to nuget.org by the release workflow with trusted publishing. Two equivalent release scripts exist: eng/release.ps1 for a Windows host with the KMS certificate provider, and eng/release.sh for macOS or Linux, which signs through eng/kms-nuget-sign (a small tool that calls KMS directly). Verify a package with dotnet nuget verify --all <package>.nupkg (see SECURITY.md).

License

Apache-2.0. Copyright Balsoft GmbH.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 113 9/24/2026