I have an Organization class and a User class as follows, & I want to be able to assign an Address to each individually - without having the need for two separate many-to-many tables for AddressUser & AddressOrganization, AddressAnything, etc. The reason being is I may have multiple entities that I want to assign an Address to & don't want an in-between join table for every single entity that needs an Address record.
I also don't want to store a foreign keys like Address.OrganizationId or Address.UserId for each entity that needs an address associated with it.
Is there a way Fluent API or Code-First Data Annotations can accommodate some sort of composite generic foreign key with an object type?
Like a DomainObjectType table?
DomainObjectType:
public class DomainObjectType
{
[Key]
public int Id { get; set; } // seeded
public string ObjectType { get; set; } // User or Organization
}
DomainObjectType would be seeded with:
var objTypes = new List<DomainObjectType>
{
new DomainObjectType() { Id = 1, ObjectType = "User" },
new DomainObjectType() { Id = 2, ObjectType = "Organization" }
};
objTypes.ForEach(c => context.DomainObjectTypes.Add(c));
context.SaveChanges();
Organization:
public class Organization
{
// Primary key
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
#region Navigation Properties
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
#endregion
}
User
public class User
{
[Key]
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int OrganizationId { get; set; }
[ForeignKey("OrganizationId")]
public virtual Organization Organization { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
}
Address
public class Address
{
// Primary Key
public int Id { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
public string AddressType { get; set; }
#region Foreign Keys
// not completely sure about this? (maybe move to many-to-many "join" table)
public int DomainObjectId { get; set; } // Composite key; would be either an OrganizationId or UserId
public string DomainObjectTypeId { get; set; } // Composite key
#endregion
#region Navigation Properties
public virtual ICollection<Organization> Organizations { get; set; }
public virtual ICollection<User> Users { get; set; }
#endregion
}
Fluent API stuff:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Configure Code First to ignore PluralizingTableName convention
// If you keep this convention then the generated tables will have pluralized names.
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
// one-to-many Organization-to-Users
modelBuilder.Entity<User>()
.HasRequired(u => u.Organization)
.WithMany(o => o.Users)
.HasForeignKey(u => u.OrganizationId);
// what goes here for generic AddressDomainObjectType?
}
You can achieve what you want - having only a single many-to-many table between Address
and "other entities" - by creating a base class for those "other entities", for example:
public abstract class DomainObjectWithAddresses
{
public int Id { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
}
public class User : DomainObjectWithAddresses
{
// other properties but without Id which is inherited from base class
}
public class Organization : DomainObjectWithAddresses
{
// other properties but without Id which is inherited from base class
}
public class Address
{
public int Id { get; set; }
// other properties
public virtual ICollection<DomainObjectWithAddresses> DomainObjects
{ get; set; }
}
Mapping with Fluent API:
modelBuilder.Entity<DomainObjectWithAddresses>()
.HasMany(d => d.Addresses)
.WithMany(a => a.DomainObjects)
.Map(x =>
{
x.ToTable("DomainObjectAddresses");
x.MapLeftKey("DomainObjectId");
x.MapRightKey("AddressId");
});
modelBuilder.Entity<User>()
.ToTable("Users"); // TPT inheritance mapping
modelBuilder.Entity<Organization>()
.ToTable("Organizations"); // TPT inheritance mapping
Your DbContext
has a DbSet
for the base type:
public DbSet<DomainObjectWithAddresses> DomainObjects { get; set; }
I don't like it. I would prefer to introduce multiple join tables for every type that has a many-to-many relationship with addresses. I have doubt that it is a good idea to introduce an abstraction in the domain model only for the sake of saving a few tables in the database. This abstract DomainObjectWithAddresses
class is a very technical ("table-savings-helper") artifact that has no valuable meaning in your domain model. For me it sounds much more natural to say that a user has addresses and an organization has addresses instead of saying that a user is an object with addresses and an organization is an object with addresses. On the long run this will only complicate working with your model.