1

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:

  1. Foreign keys: how do I replace UserId foreign key constraints in related tables now that users won't exist in my database?

  2. Queries & joins: since I no longer have a User entity, how can I query user-related data without breaking existing relationships?

enter image description here

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();
    }
}
3
  • 1
    I kept my user table and added a field for the Entra ID to tie back to Azure. You'll be able to keep using your existing user id. Commented Mar 10 at 18:50
  • Hi @nop Could you confirm whether your issue is resolved or are you still looking for help? Commented Mar 13 at 4:02
  • @Sridevi, it hasn't been resolved yet. I would like to get more input from you guys. Keeping the user table and adding a field to link it to Entra ID is fine, but is that what everyone ends up doing? Commented Mar 13 at 13:56

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.