0

I modelled the database this way and while it works just fine, I'm unable to integrate it into an existing system because the User is in that system is representated by two tables: Employee and Customer. There are internal (Employee) and external (Customer) to the system users. If you look at the schema I did below, you will see that UserId is PK and FK in UserNotificationSettings. This has to change.

The question is what should I change in the database design below so that the UserId becomes both EmployeeId and CustomerId. By the way, either EmployeeId or CustomerId should be used, but not both.

public class EventType
{
    public Guid Id { get; set; }
    public string Name { get; set; } = default!;

    public ICollection<UserNotificationSettings> UserNotificationSettings { get; set; } = default!;
    public ICollection<GlobalNotificationSettings> GlobalNotificationSettings { get; set; } = default!;
    public ICollection<Notification> Notifications { get; set; } = default!;
}

public class GlobalNotificationSettings
{
    public bool IsInternalNotificationsEnabled { get; set; }
    public bool IsEmailNotificationsEnabled { get; set; }
    public bool IsSmsNotificationsEnabled { get; set; }
    
    public Guid EventTypeId { get; set; }
    public EventType EventType { get; set; } = default!;
}

public class Notification
{
    public Guid Id { get; set; }
    public bool Seen { get; set; }
    public DateTime CreatedAt { get; set; }
    
    public Guid UserId { get; set; }
    public User User { get; set; } = default!;
    
    public Guid EventTypeId { get; set; }
    public EventType EventType { get; set; } = default!;
}

public class Sound
{
    public Guid Id { get; set; }
    public string Name { get; set; } = default!;
    public string Url { get; set; } = default!;
    
    public ICollection<UserNotificationSettings> UserNotificationSettings { get; set; } = default!;
}

public class User
{
    public Guid Id { get; set; }
    public string Username { get; set; } = default!;
    public string Password { get; set; } = default!;
    public string Email { get; set; } = default!;
    public string Phone { get; set; } = default!;

    public ICollection<UserNotificationSettings> UserNotificationSettings { get; set; } = default!;
}

public class UserNotificationSettings
{
    public bool IsInternalNotificationsEnabled { get; set; }
    public bool IsEmailNotificationsEnabled { get; set; }
    public bool IsSmsNotificationsEnabled { get; set; }
    public DeliveryOption EmailDeliveryOption { get; set; }
    public DeliveryOption SmsDeliveryOption { get; set; }
    
    public Guid UserId { get; set; }
    public User User { get; set; } = default!;
    
    public Guid EventTypeId { get; set; }
    public EventType EventType { get; set; } = default!;

    public Guid? SoundId { get; set; }
    public Sound? Sound { get; set; }
}

public enum DeliveryOption
{
    Immediate,
    DailySummary
}

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options)
    {
    }

    public DbSet<User> Users => Set<User>();
    public DbSet<UserNotificationSettings> UserNotificationSettings => Set<UserNotificationSettings>();
    public DbSet<Sound> Sounds => Set<Sound>();
    public DbSet<EventType> EventTypes => Set<EventType>();
    public DbSet<Notification> Notifications => Set<Notification>();

    public DbSet<GlobalNotificationSettings> GlobalNotificationSettings => Set<GlobalNotificationSettings>();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<UserNotificationSettings>(entity =>
        {
            entity.HasKey(uns => new { uns.UserId, uns.EventTypeId });
            
            entity.HasOne(uns => uns.User)
                .WithMany(u => u.UserNotificationSettings)
                .HasForeignKey(uns => uns.UserId)
                .OnDelete(DeleteBehavior.Cascade);
            
            entity.HasOne(uns => uns.EventType)
                .WithMany(et => et.UserNotificationSettings)
                .HasForeignKey(uns => uns.EventTypeId)
                .OnDelete(DeleteBehavior.Cascade);
            
            entity.HasOne(uns => uns.Sound)
                .WithMany(s => s.UserNotificationSettings)
                .HasForeignKey(uns => uns.SoundId)
                .OnDelete(DeleteBehavior.SetNull);
        });
        
        modelBuilder.Entity<GlobalNotificationSettings>(entity =>
        {
            entity.HasKey(gns => gns.EventTypeId);

            entity.HasOne(gns => gns.EventType)
                .WithMany(et => et.GlobalNotificationSettings)
                .HasForeignKey(gns => gns.EventTypeId)
                .OnDelete(DeleteBehavior.Cascade);
        });
        
        modelBuilder.Entity<Notification>(entity =>
        {
            entity.HasKey(n => n.EventTypeId);

            entity.HasOne(n => n.EventType)
                .WithMany(et => et.Notifications)
                .HasForeignKey(n => n.EventTypeId)
                .OnDelete(DeleteBehavior.Cascade);
            
            entity.HasOne(n => n.User)
                .WithMany(u => u.Notifications)
                .HasForeignKey(n => n.UserId)
                .OnDelete(DeleteBehavior.Cascade);
        });
    }
}
nop
  • 4,711
  • 6
  • 32
  • 93
  • 1
    Generally, you add a flag column to your tables to indicate employee or customer. – Gilbert Le Blanc Mar 29 '23 at 16:41
  • @GilbertLeBlanc, thanks and what about the PK? We cannot have a nullable PK – nop Mar 29 '23 at 16:42
  • 1
    I don't understand. The primary key is either an employee number or a customer number. The flag is a separate column. Why would the primary key be null? – Gilbert Le Blanc Mar 29 '23 at 16:46
  • @GilbertLeBlanc, I mean for example https://pastebin.com/zCJBRuPZ. An employee can have multiple notifications, but so does a customer. Same thing for `UserNotificationSettings` where currently `UserId` and `EventTypeId` form a composite PK. The PK would become `EmployeeId`, `CustomerId` and `EventTypeId` but then they all will be required? How do I keep either `EmployeeId` or `CustomerId` empty because only one of them can be specified? – nop Mar 29 '23 at 17:27
  • @GilbertLeBlanc, what I mean is there will be records that specify EmployeeId as PK and others that specify CustomerId as PK. – nop Mar 29 '23 at 17:29
  • You have one and only one column as the primary key. That column contains either the employee number or the customer number. Call the column whatever you want. – Gilbert Le Blanc Mar 29 '23 at 18:39
  • @GilbertLeBlanc but which table should the relationship be with? – nop Mar 29 '23 at 19:04
  • The table represented by the flag. I don't understand what's so hard about this concept. You have a number column and a flag column. If the flag is "E", join with the employee table. If the flag is "C", join with the customer table. You may have to write two SQL joins for each table – Gilbert Le Blanc Mar 29 '23 at 21:26
  • @GilbertLeBlanc, I think I started to understand. Can you visualize it? – nop Mar 29 '23 at 21:50
  • Does this answer your question? [How can you represent inheritance in a database?](https://stackoverflow.com/questions/3579079/how-can-you-represent-inheritance-in-a-database) – philipxy Mar 30 '23 at 05:14

1 Answers1

1

Based on your question, you have a customer table and an employee table.

Customer
--------
Customer number   PK
Other customer information

Employee
--------
Employee number   PK
Other employee information

Now, you need to create multiple additional tables with the following structure.

Additional
----------
Number   PK
COE Flag   (Customer or employee)
Additional information

You write the SQL to join this table with the customer table this way. The syntax will vary depending on which relational database you're using.

SELECT Additional information, Customer information
FROM Additional, Customer
WHERE Additional informaton meets some criteria
AND Number = Customer Number
AND COE Flag = 'C'  

You write the SQL to join this table with the employee table this way. The syntax will vary depending on which relational database you're using.

SELECT Additional information, Employee information
FROM Additional, Employee
WHERE Additional informaton meets some criteria
AND Number = Employee Number
AND COE Flag = 'E'     

I don't know how to make the concept any clearer.

Gilbert Le Blanc
  • 50,182
  • 6
  • 67
  • 111