I'm integrating Microsoft Entra ID (Azure AD) into my ASP.NET Core application for authentication. Since Entra ID is an external identity provider (SSO), I will no longer store users in my database, meaning I won't have a User
entity. However, my existing database schema relies on a User
table for foreign key relationships in multiple entities, such as Bot
, UserExchange
, and VirtualBalance
.
Since Entra ID will handle authentication, my application will receive the authenticated user's ID (sub
claim from the JWT). However, I won't have a User
entity stored in my database anymore, which creates the following problems:
Foreign keys: how do I replace
UserId
foreign key constraints in related tables now that users won't exist in my database?Queries & joins: since I no longer have a
User
entity, how can I query user-related data without breaking existing relationships?
public class User : BaseAuditableEntity
{
public User(string email, string password)
{
Id = Guid.CreateVersion7();
Email = email;
Password = password;
}
public string Email { get; private set; }
public string Password { get; private set; }
public ICollection<UserExchange> UserExchanges { get; } = new List<UserExchange>();
public ICollection<VirtualBalance> VirtualBalances { get; } = new List<VirtualBalance>();
}
public class Bot : BaseAuditableEntity
{
// EF Core cannot bind value object in EF constructor:
[UsedImplicitly]
private Bot() { }
public Bot(Guid userId, Guid exchangeId, Pair pair, decimal investment)
{
Id = Guid.CreateVersion7();
UserId = userId;
ExchangeId = exchangeId;
Pair = pair;
Investment = investment;
}
public Guid UserId { get; private set; }
public Guid ExchangeId { get; private set; }
public Pair Pair { get; private set; } = null!;
public decimal Investment { get; private set; }
public User User { get; private set; } = null!;
public Exchange Exchange { get; private set; } = null!;
}
public class Exchange : BaseAuditableEntity
{
public Exchange(string name)
{
Id = Guid.CreateVersion7();
Name = name;
IsActive = true;
}
public string Name { get; private set; }
public bool IsActive { get; private set; }
public void Activate()
{
IsActive = true;
}
public void Deactivate()
{
IsActive = false;
}
}
public class Order : BaseAuditableEntity
{
// EF Core cannot bind value object in EF constructor:
[UsedImplicitly]
private Order() { }
public Order(Guid botId, Pair pair, OrderSide side, decimal quantity, decimal price, string? clientOrderId = null)
{
Id = Guid.CreateVersion7();
BotId = botId;
Pair = pair;
Side = side;
Status = new OrderState(OrderStatus.New);
Quantity = quantity;
Price = price;
ClientOrderId = clientOrderId;
CreateOrderPlacedEvent();
}
public Guid BotId { get; private set; }
public Pair Pair { get; private set; } = null!;
public OrderSide Side { get; private set; }
public OrderState Status { get; private set; } = null!;
public decimal Quantity { get; private set; }
public decimal Price { get; private set; }
public string? ClientOrderId { get; private set; }
public Bot Bot { get; private set; } = null!;
public void Cancel()
{
AddDomainEvent(new OrderCanceledEvent());
Status = Status.Cancel();
}
public void CreateOrderPlacedEvent()
{
AddDomainEvent(new OrderPlacedEvent(Bot.Exchange.Name));
}
}
public class UserExchange
{
public UserExchange(Guid userId, Guid exchangeId, string apiKey, string secretKey, string? passphrase = null)
{
UserId = userId;
ExchangeId = exchangeId;
ApiKey = apiKey;
SecretKey = secretKey;
Passphrase = passphrase;
}
public Guid UserId { get; private set; }
public Guid ExchangeId { get; private set; }
public string ApiKey { get; private set; }
public string SecretKey { get; private set; }
public string? Passphrase { get; private set; }
public User User { get; private set; } = null!;
public Exchange Exchange { get; private set; } = null!;
}
public class VirtualBalance : BaseAuditableEntity
{
public VirtualBalance(string asset, decimal available, decimal locked)
{
Id = Guid.CreateVersion7();
Asset = asset;
Available = available;
Locked = locked;
}
public string Asset { get; private set; }
public decimal Available { get; private set; }
public decimal Locked { get; private set; }
public Guid UserId { get; private set; }
public User User { get; private set; } = null!;
}
public class UserConfiguration : IEntityTypeConfiguration<User>
{
public void Configure(EntityTypeBuilder<User> builder)
{
builder.ToTable("User", SchemaNames.Settings);
builder.HasKey(x => x.Id);
builder.Property(x => x.Id).ValueGeneratedOnAdd();
builder.Property(x => x.Email).HasMaxLength(256).IsRequired();
builder.Property(x => x.Password).HasMaxLength(256).IsRequired();
builder.Property(x => x.CreatedAt).IsRequired();
builder.HasIndex(x => x.Email).IsUnique();
}
}
public class UserExchangeConfiguration : IEntityTypeConfiguration<UserExchange>
{
public void Configure(EntityTypeBuilder<UserExchange> builder)
{
builder.ToTable("UserExchange", SchemaNames.Settings);
builder.HasKey(x => new { x.UserId, x.ExchangeId });
builder.Property(x => x.UserId).IsRequired();
builder.Property(x => x.ExchangeId).IsRequired();
builder.Property(x => x.ApiKey).HasMaxLength(256).IsRequired();
builder.Property(x => x.SecretKey).HasMaxLength(256).IsRequired();
builder.Property(x => x.Passphrase).HasMaxLength(256).IsRequired(false);
builder.HasOne(x => x.User)
.WithMany(x => x.UserExchanges)
.HasForeignKey(x => x.UserId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
builder.HasOne(x => x.Exchange)
.WithMany()
.HasForeignKey(x => x.ExchangeId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
builder.HasIndex(x => new { x.UserId, x.ExchangeId }).IsUnique();
}
}