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
UserIdforeign key constraints in related tables now that users won't exist in my database?Queries & joins: since I no longer have a
Userentity, 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: https://stackoverflow.com/questions/55749717/entity-framework-cannot-bind-value-object-in-entity-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: https://stackoverflow.com/questions/55749717/entity-framework-cannot-bind-value-object-in-entity-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();
}
}
