'EF Core One to Many with GenericId

I am trying to link one model to multiple other modells via a GenricId. All my id fields are Guid's and not int's.

Here ist the example models:


    public class BaseEntity
    {
        public Guid Id { get; set; }

        public string TenantId { get; set; }

        public DateTime? CreatedAt { get; set; }

        public Guid? CreatedBy { get; set; }

        public DateTime? ModifiedAt { get; set; }

        public Guid? ModifiedBy { get; set; }

        public bool Deleted { get; set; } = false;
    }

    public class Contact : BaseEntity
    {
        [StringLength(150)]
        [Required]
        public string CompanyName { get; set; } = String.Empty;

        public ContactType ContactType { get; set; }

        [StringLength(100)]
        [Required]
        public string Email { get; set; } = String.Empty;

        [StringLength(30)]
        [Required]
        public string Telefon1 { get; set; } = String.Empty;

        [StringLength(30)]
        [Required]
        public string Telefon2 { get; set; } = String.Empty;

        [StringLength(30)]
        [Required]
        public string Fax { get; set; } = String.Empty;

        public Guid PersonId { get; set; }

        public Person? Person { get; set; }
        // How do I tell the relation to use the genericId field on Address model for the relationship?
        public IList<Address> Addresses { get; set; } = new List<Address>();

        [NotMapped]
        public IList<Guid>? AddressIds { get; set; }
    }

    public class Address : BaseEntity
    {
        public Guid GenericId { get; set; }

        public AddressType AddressType { get; set; } = AddressType.Private;

        public bool CurrentResidence { get; set; } = true;

        [StringLength(200)]
        [Required]
        public string Street { get; set; } = String.Empty;

        [StringLength(20)]
        [Required]
        public string PostalCode { get; set; } = String.Empty;

        [StringLength(100)]
        [Required]
        public string City { get; set; } = String.Empty;

    }

How can I tell the ContactModel that the ForeignKey on the Remote Object (Address) is not ContactId but rather GenericId. Also I have other models which should also link to the address via the GenericId on Address Model? Preferably using annotations and NOT fluent API :)

Edit: Added Diagram for clarification enter image description here



Solution 1:[1]

As your relationship between Contact and Address is a One-to-Many, this is the Address which will hold the foreign key of the contact.

Did you try adding an Alternate Key Property ? https://docs.microsoft.com/en-us/ef/core/modeling/relationships?tabs=data-annotations%2Cdata-annotations-simple-key%2Csimple-key#principal-key

It seems that you cannot create an alternate key without using the Fluent API. Does annotations are absolutely required ?

Personally, I prefer using Fluent API. I like to have my models not linked to an implementation of how those models references each other. And you will probably have a DbContext anyway. But this is my opinion.

In your case, your DbContext should be

public class MyContext : DbContext
{
    public DbSet<Contact> Contacts{ get; set; }
    public DbSet<Address> Addresses{ get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Address>()
            .HasOne(s => s.Contact)
            .WithMany(c => c.Addresses)
            .HasForeignKey(s => s.ContactGenericId)
            .HasPrincipalKey(c => c.GenericId);
    }
}

public class Contact : BaseEntity
{
    //..previous stuff

    public Guid GenericId {get; set;}
  
    public IList<Address> Addresses { get; set; } = new List<Address>();

    //..not required !
    //[NotMapped]
    //public IList<Guid>? AddressIds { get; set; } 
}

public class Address : BaseEntity
{
    //not required
    //public Guid GenericId { get; set; }

    //...previous stuff



    public Contact Contact {get; set;}

    public Guid ContactGenericId {get; set;}

   

}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 dedieuma