Skip to content

Migration Migrate from OfX

quyvu edited this page Oct 14, 2026 · 1 revision

Migrate from OfX

FxMap is the successor of OfX. This guide maps the OfX attribute-based API to the FxMap FluentAPI, step by step.

Curious why the attribute design gave way to a fluent API? Read the story behind OfX and FxMap, or the original write-up on GitBook (in Vietnamese).

What Changes and What Stays

Changes

  • Mapping is declared with ProfileOf<T> and EntityConfigureOf<T> instead of attributes on your DTOs and models.
  • Distributed keys are IDistributedKey types instead of OfXAttribute subclasses.
  • Package names, namespaces and a few registration methods are renamed (tables below).

Stays the same

  • The expression language: Country.Name, Orders:sum(Total) and every other expression work unchanged. The one exception is the indexer and the filter, which 5.0 removed: see Upgrade to 5.0 if your expressions use them.
  • Transport registration: AddNats, AddRabbitMq, AddKafka, AddAzureServiceBus, AddSqs, AddGrpcClients.
  • Configuration options: ThrowIfException, SetRetryPolicy, SetRequestTimeOut, SetMaxNestingDepth, SetMaxConcurrentProcessing.

Step 1: Swap the Packages

Remove every OfX package and add the matching FxMap package. All FxMap.* packages must use the same version.

OfX package FxMap package
OfX FxMap
OfX.EntityFrameworkCore FxMap.EntityFrameworkCore
OfX.MongoDb FxMap.MongoDb
OfX.Grpc FxMap.Grpc
OfX.Nats FxMap.Nats
OfX.RabbitMq FxMap.RabbitMq
OfX.Kafka FxMap.Kafka
OfX.Azure.ServiceBus FxMap.Azure.ServiceBus
OfX.Aws.Sqs FxMap.Aws.Sqs
OfX.HotChocolate FxMap.HotChocolate
OfX-Analyzers FxMap.Analyzers

Step 2: Replace the Attribute with a Key

// OfX
public sealed class UserOfAttribute(string propertyName) : OfXAttribute(propertyName);
 
// FxMap
public sealed class UserDistributedKey : IDistributedKey;

Step 3: Move the Model Attribute to an Entity Config

On the service that owns the data, the [OfXConfigFor] attribute becomes an EntityConfigureOf<T> class. Your model stays free of mapping code.

// OfX
[OfXConfigFor<UserOfAttribute>(nameof(Id), nameof(Name))]
public sealed class User
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}
 
// FxMap
public sealed class User
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
}
 
public class UserConfig : EntityConfigureOf<User>
{
    protected override void Configure()
    {
        Id(x => x.Id);                              // first argument of [OfXConfigFor]
        DefaultProperty(x => x.Name);               // second argument of [OfXConfigFor]
        UseDistributedKey<UserDistributedKey>();    // the attribute type
    }
}

Step 4: Move the DTO Attributes to a Profile

On the consuming service, each attribute on a DTO property becomes a .For(...) call in a profile. The expression string is copied as it is.

// OfX
public sealed class SomeDataResponse
{
    public string UserId { get; set; }
 
    [UserOf(nameof(UserId))]
    public string UserName { get; set; }
 
    [UserOf(nameof(UserId), Expression = "Email")]
    public string UserEmail { get; set; }
}
 
// FxMap
public sealed class SomeDataResponse
{
    public string UserId { get; set; }
    public string UserName { get; set; }
    public string UserEmail { get; set; }
}
 
public class SomeDataResponseProfile : ProfileOf<SomeDataResponse>
{
    protected override void Configure() =>
        UseDistributedKey<UserDistributedKey>()
            .Of(x => x.UserId)                  // nameof(UserId)
            .For(x => x.UserName)               // no expression: DefaultProperty
            .For(x => x.UserEmail, "Email");    // Expression = "Email"
}

Step 5: Update the Registration

// OfX
builder.Services.AddOfX(cfg =>
{
    cfg.AddAttributesContainNamespaces(typeof(UserOfAttribute).Assembly);
    cfg.AddModelConfigurationsFromNamespaceContaining<User>();
    cfg.AddNats(c => c.NatsOpts(o => o.Url = "nats://localhost:4222"));
}).AddOfXEFCore(o => o.AddDbContexts(typeof(AppDbContext)));
 
// FxMap
builder.Services.AddFxMap(cfg =>
{
    cfg.AddEntitiesFromAssemblyContaining<User>();                  // service that owns the data
    cfg.AddProfilesFromAssemblyContaining<SomeDataResponse>();      // service that maps DTOs
    cfg.AddNats(c => c.NatsOpts(o => o.Url = "nats://localhost:4222"));
}).AddEntityFrameworkCore(o => o.AddDbContexts(typeof(AppDbContext)));
OfX FxMap
AddOfX(cfg => ...) AddFxMap(cfg => ...)
cfg.AddAttributesContainNamespaces(...) removed (there are no mapping attributes any more)
cfg.AddModelConfigurationsFromNamespaceContaining() cfg.AddEntitiesFromAssemblyContaining() and cfg.AddProfilesFromAssemblyContaining()
.AddOfXEFCore(...) .AddEntityFrameworkCore(...)
app.MapOfXGrpcService() app.MapFxMapperGrpc()
IDistributedMapper.MapDataAsync(value, parameters, token) IDistributedMapper.MapDataAsync(value, token)

Before You Deploy

  • Migrate the data-owning services and the consuming services together: a client on one library cannot talk to a server on the other.
  • If you passed the parameters argument to MapDataAsync, review that call: the FxMap method takes the value and a cancellation token only.
  • Turn on cfg.ThrowIfException() in development while you migrate, so a missing key or entity config fails loudly instead of leaving a property empty.
  • Add the FxMap.Analyzers package to catch misconfigured profiles and expressions at compile time.

Need Help?

Ask in the Discord server or open an issue on GitHub. Migration stories help us improve this guide.


Source: https://fxmapper.net/docs/migrate-from-ofx/ — the website is always the most up to date version.

Clone this wiki locally