This is going to be a post regarding an “issue” I’ve encountered before with Entity Framework Code First and the configuration of the mapping and how the classes map to database tables, specifically many to many relations.
A while ago I was involved in a project where the database and data access (with EF Code First) were implemented by another more experienced developer. At the time Entity Framework was at version 5 and Code First was not as mature as it is today with version 6. I remember us having discussions about certain issues he was facing, especially because he came from a NHibernate background and was more familiar and comfortable working with NHibernate as his ORM of choice.
One of the issues, if I recall correctly, was not having the option to easily and cleanly define a Many to Many table relation without having double associations in the domain classes, meaning both classes had to have collection navigation properties. Now this was a long time ago and I may remember things wrongly but the point of this post stands as we will see two ways to define many to many relations.
The example project we will look at consists of two simple domain classes. Product and User and the relation between them can be seen as the User bookmarking Products in an application for maybe later purchase or viewing. The most basic thing you can do in your domain classes to have a many to many relation is, as mentioned using two navigation properties:
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public virtual List<Product> Products { get; set; }
}
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public virtual List<User> Users { get; set; }
}
Both Product and User have collections of each other defined as virtual that enables lazy loading. What this means essentially is that we can read the basic information for a User from the database, for example the username, but the list of products will not be loaded until we actually need the Products, for example iterate over them to calculate the total price.
The table structure created in the database with an initial migration for the given entities is shown in the following diagram:
The many to many relationship is inferred by entity framework and the naming for the table and fields are generated by EF out of the box conventions based on the names of the domain classes.
Now this might be good enough and I will choose not go into a discussion about changing this many to many relation to a completely new entity that may contain something like DateSaved, or other Meta information about linking products to users and then have single association to Product and User entities as that is not the point of the post.
But if we want to, for example, keep the Product entity clean, not have it contain a collection of Users, as we would never want to get all the Users that a given product has been associated with, which would even allow us to use the Product class as a view model (not actually a good approach) we will end up with a one to many association if we remove the collection of Users from the Product entity.
Given the following code for the Product entity
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
}
The database diagram produced with adding a new migration and updating the database is drastically different from the first one which does not serve our needs.
After removing the User collection from the Product object, EF interprets this as a one to many relationship and creates a FK property on the Products table pointing to User, which is clearly not what we want. We will not look at a way to still keep out the Users collection from product, and still get a many to many relationship.
Entity Type Configurations
The entire concept behind entity type configurations was introduced in EF6, if I recall correctly. This approach allows us to define EF Fluent API configurations for each entity and then load them up in the OnModelCreating event in the context.
With this approach we are going to specifically configure the Many to Many relation on the Users entity. The entity type configuration class for the User entity is defined in the following snippet:
public class UserEntityTypeConfiguration:BaseEntityTypeConfiguration<User>
{
public UserEntityTypeConfiguration()
{
HasMany(user => user.Products)
.WithMany();
}
}
UPDATE: There was a mistake in the above snippet. The WithMany call had a ‘product’ as a parameter which was wrong and probably left over from testing. Updated after the actual colleague that was mentioned in the beginning of the post pointed it out in the discussion on Disqus.
The configuration inherits from our own base abstract generic class. I found this convenient because I usually keep the configurations in a separate project from my entities and the migrations which leads to lesser folder hierarchies and better organization. The abstract generic class in turn allows for easier configuration loading in the OnModelCreating event which we will see shortly.
The key point in the configuration is the specification that the User has many Products, and the Products in turn have many Users which is specified with the WithMany() call. The WithMany() method has an overload that would allow us to specify the navigation property (collection) on the Product that signifies this many to many relationship. If we went with our first example we could have called the method like so:
public class UserEntityTypeConfiguration:BaseEntityTypeConfiguration<User>
{
public UserEntityTypeConfiguration()
{
HasMany(user => user.Products)
.WithMany(product=>product.Users);
}
}
Using the method with no arguments still tells EF that there is a many to many association here which solves our initial problem of keeping the Product class clean. The database diagram generated is exactly the same as the one where both entities had navigation property collection to each other.
Now there are other configurations and settings that can be done using the EntityTypeConfiguration approach. I’ve personally haven’t explored other significant usages, as I also just started using EntityTypeConfigurations on a recent project. One additional thing I’ve done, is if we want to modify the table and foreign key naming for the table that gets auto generated by EF by convention. The full configuration code will then be extended by a Map method that takes a map configuration callback provided here via a lambda expression.
public class UserEntityTypeConfiguration:BaseEntityTypeConfiguration<User>
{
public UserEntityTypeConfiguration()
{
HasMany(user => user.Products)
.WithMany()
.Map(map =>
{
map.ToTable("UserFavouriteProducts");
map.MapLeftKey("UserId");
map.MapRightKey("ProductId");
});
}
}
And the final database diagram based on the configuration:
To wrap things up we are just going to see how the User EntityTypeConfiguration is loaded on the OnModelCreating overloaded method on the context.
public class AppContext : DbContext
{
#region Ctor
public AppContext()
: base("EntityTypeConfigConnection")
{
}
#endregion
#region Sets
public DbSet<User> Users { get; set; }
public DbSet<Product> Product { get; set; }
#endregion
#region Overides
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// add all the found entity type configurations that are in the same
// assembly as the base abstract generic config.
modelBuilder.Configurations.AddFromAssembly(Assembly.GetAssembly(typeof(BaseEntityTypeConfiguration<>)));
}
#endregion
}
Loading all configurations from a given assembly specified by the base abstract class allows us to both separate them, as mentioned, in another project and to add additional entity configurations for example for the Product entity without having to worry about modifying the DbContext.
There was one other thing related to setting up EF code first that I’ve learned recently and that is the issue of naming conventions. EF by default generates FK’s using underscores which is not how I personally prefer the FK’s to be named. This can also be easily solved using a custom naming convention but that would have to wait for another post.
The final example project can be found at this repository and I hope someone finds it useful!