Repository navigation
Data Providers Entity Framework Core
Answer FxMap requests straight from your DbContext: one projection query for each request, with only the values that were asked for.
dotnet add package FxMap.EntityFrameworkCoreAll FxMap.* packages must use the same version.
The owning service describes its entities with EntityConfigureOf<T>, registers them, and lists the DbContexts that hold them:
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
builder.Services.AddFxMap(cfg =>
{
cfg.AddEntitiesFromAssemblyContaining<SomeEntityAssemblyMarker>();
cfg.AddProfilesFromAssemblyContaining<SomeProfileAssemblyMarker>();
})
.AddEntityFrameworkCore(c => c.AddDbContexts(typeof(AppDbContext)));AddEntitiesFromAssemblyContaining (or AddEntityConfig<T>) must run before AddEntityFrameworkCore, and at least one DbContext must be listed: FxMap throws at startup if either is missing.
List them all. FxMap routes each entity to the DbContext that exposes it as a DbSet:
.AddEntityFrameworkCore(c => c.AddDbContexts(typeof(UsersDbContext), typeof(ReportingDbContext)));
// a list works too
.AddEntityFrameworkCore(c => c.AddDbContexts([typeof(UsersDbContext), typeof(ReportingDbContext)]));-
One query for each request. All the ids and all the expressions of a request are answered by one
AsNoTrackingquery with a projection. Navigation, filters, indexers, aggregations and projections of the expression language are turned into LINQ, so EF Core writes the SQL. - Each query runs in its own DI scope, with its own DbContext, so requests can run in parallel. It does not see uncommitted data held by the DbContext or the transaction of the caller.
-
Ids are parsed to the id type of the entity (
string,Guid,int,longand the other numeric types, nullable too), or to a strongly typed id that has a converter. Ids that cannot be parsed are ignored. -
Ids are answered the way they were sent. A Guid in upper case, without hyphens or in braces,
"007"for7, or an id with spaces around it is found, and matched back to the object that sent it. -
Aggregates over an empty collection:
:countand:sumgive0;:min,:maxand:avggivenull. -
ExposedName(x => x.Email, "UserEmail")renames a property in expressions; the original name no longer resolves.
Any database that EF Core supports works, because FxMap sends LINQ and not SQL. The provider tests of FxMap run against a real PostgreSQL 17: see Testing & performance.
By default, one expression that the owner cannot build fails the whole request, and with it every property of the same key and the same level. It happens when an expression has an unknown function, a syntax error, or an unknown property. A rolling deployment shows it: a new consumer asks for a property that an old owner does not have yet.
Since 4.1.3 the owner can answer the expressions it can build, and report the others in the response:
builder.Services.AddFxMap(cfg => { /* ... */ })
.AddEntityFrameworkCore(c =>
{
c.AddDbContexts(typeof(AppDbContext));
c.IsolateExpressionErrors();
});Without IsolateExpressionErrors() nothing changes: the request fails. With it, each expression that cannot be built is reported once in ItemsResponse.Errors as an ExpressionError(Expression, Message), whatever the number of rows, and also when no row matches. The value of such an expression is null in every row.
| The asking service | Result |
|---|---|
with ThrowIfException()
|
a DistributedMapException.ExpressionsFailed that holds all the errors (Errors, and every message in Message) |
| without it | a warning in the log for each error; the properties of these expressions are left alone and the others are mapped |
| with HotChocolate | the fields of a broken expression are null, and ThrowIfException() throws as above |
The errors travel with every transport, and a version that does not know them ignores them. MongoDB has the same setting: see the MongoDB provider.
Only the errors raised when the expression is built are isolated. A LINQ expression that EF Core builds but cannot translate into SQL fails when the query runs, and that fails the request.
// Domain models
public class User
{
public string Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string ProvinceId { get; set; }
public Province Province { get; set; }
}
public class Province
{
public string Id { get; set; }
public string Name { get; set; }
public Country Country { get; set; }
public string CountryId { get; set; }
}
public class Country
{
public string Id { get; set; }
public string Name { get; set; }
}
// Distributed key and entity config (owner side)
public sealed class UserDistributedKey : IDistributedKey;
public class UserConfig : EntityConfigureOf<User>
{
protected override void Configure()
{
Id(x => x.Id);
DefaultProperty(x => x.Name);
UseDistributedKey<UserDistributedKey>();
}
}
// DTO and profile (consumer side)
public class UserDetailResponse
{
public string UserId { get; set; }
public string? Name { get; set; }
public string? Email { get; set; }
public string? Province { get; set; }
public string? Country { get; set; }
}
public class UserDetailResponseProfile : ProfileOf<UserDetailResponse>
{
protected override void Configure() =>
UseDistributedKey<UserDistributedKey>()
.Of(x => x.UserId)
.For(x => x.Name)
.For(x => x.Email, "Email")
.For(x => x.Province, "Province.Name")
.For(x => x.Country, "Province.Country.Name");
}
// Usage
var response = new UserDetailResponse { UserId = userId };
await distributedMapper.MapDataAsync(response);Province.Country.Name is one navigation expression: the owner reads it with one projection query, and you do not write the join.
The mapper gathers the distinct ids of all the objects and sends one request for each distributed key and level, so the provider answers a hundred objects with one query, not a hundred. The N+1 article shows the difference.
FxMap, FxMap.EntityFrameworkCore and the transport package must have the same version. If you wrote a data provider of your own on QueryHandlerBuilder, override BuildProjectionWithErrors to support the isolation of 4.1.3.
Source: https://fxmapper.net/docs/data-providers/ef-core/ — the website is always the most up to date version.
Introduction
Getting Started
Configuration
- Service Registration
- Entities & Profiles
- Handlers
- Pipelines
- Strongly Typed IDs
- Error Handling
- Retry Policy
- Supervisor
- Telemetry
- Kestrel (HTTP/2)
- Analyzers
Expressions
- Overview
- Property Access
- Filters, Sorting & Paging
- Functions
- Aggregations
- Projections
- Coalesce & Ternary
- GroupBy
Data Providers
Transports
Integrations
Quality
AI Assistants
Migration
Release Notes