FluentDynamics QueryBuilder

A modern fluent QueryExpression builder for Dynamics 365 / Dataverse. Write faster, cleaner, testable queries: complex filters, joins, pagination, async execution, FetchXML conversion – all with chainable syntax.

NuGet License Line Coverage Tests
🚀 Install via NuGet ⭐ View on GitHub Give a Star

Line Coverage

85.35%

Branch Coverage

72.72%

Method Coverage

94.55%

Tests

180

Key Features

Reduce query boilerplate, prevent errors, and improve readability & maintainability.

🔄 Fluent Chaining

Eliminates verbose QueryExpression instantiation and nested objects.

🧠 Rich Filter DSL

Operators like .Equal(), .In(), .LastXDays(), .IsNull(), hierarchy/user/fiscal helpers.

Async + Pagination

High-volume datasets with page iteration & true async via IOrganizationServiceAsync2.

🔗 Intuitive Joins

.InnerJoin(), .LeftOuterJoin(), .CrossApplyJoin(), .ExistsJoin() etc.

🛠️ Advanced Options

.Distinct(), .NoLock(), .ForceSeek(), .QueryHint().

🧩 FetchXML Export

Convert the built query to FetchXML instantly for tools or reporting.

Fluent API
QueryExpression
Dataverse
Dynamics 365
FetchXML
Async
Pagination
Joins

Installation

Add via NuGet and start composing queries immediately.

dotnet add package FluentDynamics.QueryBuilder

Package ID: FluentDynamics.QueryBuilder

.NET Standard 2.0 – compatible with .NET Framework 4.7.2+, .NET 6/7/8.

Basic Example

using FluentDynamics.QueryBuilder;
using FluentDynamics.QueryBuilder.Extensions;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;

// Get first 5 active accounts
var query = Query.For("account")
    .Select("name", "accountnumber", "telephone1")
    .Where(f => f.Equal("statecode", 0))
    .OrderBy("name")
    .Top(5);

EntityCollection result = query.RetrieveMultiple(orgService);

foreach (var acc in result.Entities)
{
    Console.WriteLine(acc.GetAttributeValue<string>("name"));
}

Code Samples

Real-world snippets for common scenarios.


var recentContacts = Query.For("contact")
    .Select("firstname", "lastname", "emailaddress1", "createdon")
    .Where(f => f
        .Equal("statecode", 0)
        .And(a => a
            .LastXDays("createdon", 30)
            .IsNotNull("emailaddress1"))
        .Or(o => o
            .Like("emailaddress1", "%@example.com")
            .In("address1_city", "Seattle", "London", "Berlin"))
    )
    .OrderBy("lastname")
    .OrderBy("firstname");

var results = recentContacts.RetrieveMultiple(service);

var opportunities = Query.For("opportunity")
    .Select("name", "estimatedvalue", "closeprobability")
    .Where(f => f.Equal("statecode", 0))
    .InnerJoin("account", "customerid", "accountid", link => link
        .Select("name", "accountnumber")
        .As("acc")
        .Where(f => f.Equal("statecode", 0)))
    .LeftOuterJoin("contact", "customerid", "contactid", link => link
        .Select("fullname", "emailaddress1")
        .As("con"));

var data = opportunities.RetrieveMultiple(service);

foreach (var o in data.Entities)
{
    var accountName = o.GetAttributeValue<AliasedValue>("acc.name")?.Value;
    var contactName = o.GetAttributeValue<AliasedValue>("con.fullname")?.Value;
}

// Paginate through all records
var baseQuery = Query.For("account")
    .Select("name")
    .OrderBy("name")
    .Top(1000); // optional limit

int page = 1;
int size = 500;
bool more;
string cookie = null;
var all = new List<Entity>();

do {
    var pageResult = baseQuery.RetrieveMultiple(service, page, size);
    all.AddRange(pageResult.Entities);
    more = pageResult.MoreRecords;
    cookie = pageResult.PagingCookie;
    page++;
} while (more);

// True async version
var asyncResults = await baseQuery.RetrieveMultipleAllPagesAsync(asyncService);

// Convert current query to FetchXML
var q = Query.For("account")
    .Select("name")
    .Where(f => f.Like("name", "Contoso%"));

var fetchExpression = q.ToFetchExpression(service);
Console.WriteLine(fetchExpression.Query);

Advanced Usage

Filter DSL (Syntactic Sugar)

Minimizes verbose ConditionOperator usage.

var tasks = Query.For("task")
  .Select("subject","scheduledend","ownerid")
  .Where(f => f
      .Equal("statecode", 0)
      .And(a => a.OnOrAfter("scheduledend", DateTime.Today))
      .And(a => a.EqualUserId("ownerid"))
  )
  .OrderBy("scheduledend");

Clone & Debug

Fork queries safely and inspect structure with .DebugView().

var baseQ = Query.For("account")
    .Select("name","accountnumber")
    .Where(f => f.Equal("statecode",0));

var variant = baseQ.DeepClone()
    .Where(f => f.Equal("name","Contoso"));

Console.WriteLine(baseQ.DebugView());
Console.WriteLine(variant.DebugView());

FAQ

Which SDK objects does this wrap?

It uses Microsoft Power Platform Dataverse Client & Microsoft.Xrm.Sdk primitives, providing only a fluent query construction layer.

Does it improve runtime performance?

Your server-side query execution characteristics remain those of standard QueryExpression. Gains come from reduced code noise, fewer errors, faster iteration, and easier optimization (hints, ForceSeek, NoLock made accessible).

Why FetchXML conversion?

Various tools, dashboards and advanced reporting scenarios require FetchXML; instant conversion speeds prototyping.

Framework compatibility?

.NET Standard 2.0 – works with .NET Framework 4.7.2+, .NET Core 3.1+, .NET 5/6/7/8.

What about test coverage?

Lines 85%+, Methods 94%+, Branches 72%+; 180 passing tests.

Can I contribute?

Yes. Open Issues / PRs on GitHub. Licensed MIT.

Start Building Cleaner Queries Today

Readable, maintainable Dataverse / Dynamics query composition in minutes.