Ehsan Ghanbari

Experience, DotNet, Solutions

What's been happened to HasMany() in Entity Framework Core

In earlier versions of Entity Framework( before Entity Framework .Core) when you wanted to create an inner table from two Entities, you would be able to use entity framework mapping in a way to create the inner table without creating a separate entity for it. For example, look at the following two classes:

 


 public class Blog

    {

        public int Id { get; set; }

 

        public string Title { get; set; }

 

        public virtual ICollection<Tag> Tags { get; set; }

    }

 

 

  public class Tag

    {

        public int Id { get; set; }

 

        public string Name { get; set; }

    

        public virtual ICollection<Blog> Blogs { get; set; }

 

    }

 

It was possible to create the inner table in the mapping of the above classes:

 

internal class BlogMapping : EntityTypeConfiguration<Blog>

    {

        public BlogMapping()

        {

            ToTable("Blog");

            HasKey(p => p.Id);

            Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

            Property(p => p.Title).IsRequired().HasMaxLength(400);

        

            HasMany(t => t.Tags).WithMany(p => p.Blogs).Map(m =>

            {

                m.MapLeftKey("BlogId");

                m.MapRightKey("TagId");

                m.ToTable("BlogTag");

            });

        }

    }

 

Now, in Entity Framework Core there is no longer the HasMany() method! It’s not supported anymore (I don't know why and I couldn't find the real reason as I personally would never have trouble with that. See here for more information) and you have to represent the many-to-many relationship by including an entity class for the join table and mapping two separate one-to-many relationships. So, firstly you have to change the Entities like below:

  

 

public class Blog

    {

        public int Id { get; set; }

 

        public string Title { get; set; }

 

        public virtual ICollection<BlogTag> BlogTags { get; set; }

    }

 

 

    public class Tag

    {

        public int Id { get; set; }

 

        public string Name { get; set; }

 

        public virtual ICollection<BlogTag> BlogTags { get; set; }

 

    }

 

    public class BlogTag

    {

        public int BlogId { get; set; }

 

        public Blog Blog { get; set; }

 

        public int TagId { get; set; }

 

        public Tag Tag { get; set; }

    }

 

Secondly, you have to map via Entity Framework Core like this:

  

 public class BlogTagMapping : IEntityTypeConfiguration<BlogTag>

    {

        public void Configure(EntityTypeBuilder<BlogTag> builder)

        {

            builder.HasOne(d => d.Blog).WithMany(e => e.BlogTags).HasForeignKey(w => w.BlogId);

            builder.HasOne(d => d.Tag).WithMany(e => e.BlogTags).HasForeignKey(w => w.TagId);

        }

    }

 

I would have seen a lot of developers writing these sort of mapping for entity framework to create inner tables, but I've hardly ever used this method, and now it seems we all have to use these ways in entity framework core!



Using In Memory Database of Entity Framework Core in asp.net core

Using the in-memory database of entity framework is super easy. Create an asp.net core and reference the following NuGet package if it's not referenced( it's been referenced in asp.net core 2 and above):

 

  • Microsoft.EntityFrameworkCore.InMemory

 

Then create the context just like the past:

 

 class SampleDbContext : DbContext

    {

        public SampleDbContext() { }

 

        public SampleDbContext(DbContextOptions<SampleDbContext> options)

        : base(options)

        { }

    }

 

And add the InMemory Configuration in ConfigureService() of startup class:

 
 

 public void ConfigureServices(IServiceCollection services)

        {

            services.Configure<CookiePolicyOptions>(options =>

            {

                // This lambda determines whether user consent for non-essential cookies is needed for a given request.

                options.CheckConsentNeeded = context => true;

                options.MinimumSameSitePolicy = SameSiteMode.None;

            });

 

            services.AddDbContext<SampleDbContext>(context => { context.UseInMemoryDatabase("Sample"); });

 

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);

        }

 



Register EF Core DBContext in data layer assembly

In Asp.net core samples, you always see the AddDbContext method In services to register Entity framework DbContext. I mean something like below:

 

  public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
            services.AddApiVersioning(o => o.ApiVersionReader = new HeaderApiVersionReader("api-version"));
            services.AddDbContext<BaseDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MyConnection")));        
        }
}

 

Suppose that we have an architecture that forces us to have loosely coupled services. For example, our EF DbContext lives in the Data Access layer and  the presentation layer should not know anything about that as it is talking to a service layer:

  1. Presentation
  2. Service
  3. Data Access

In order to register each of the above configurations in its own layer, we should create an extension method over IServiceCollection :

 

public static class DataRegistery
    {
        public static IServiceCollection RegisterData(this IServiceCollection services, IConfiguration configuration)
        {
            services.AddScoped(typeof(IRepository<>), typeof(Repository<>));

            services.AddEntityFrameworkSqlServer().AddDbContext<DataContext>(options =>
            {
                options.UseSqlServer(configuration.GetConnectionString("MyConnection"));
            });

            return services;
        }
    }

 

And we can register this in Service layer configuration :

 

 public static class ServiceRegistery
    {
        public static IServiceCollection RegisterService(this IServiceCollection services, IConfiguration configuration)
        {
            services.RegisterData(configuration);

            return services;
        }
    }

 

And finally in asp.net core Startup class:

 

 public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Latest);
            services.RegisterService(Configuration);
        }
}

 

Cheers!



Entity Framework Core mapping configuration

Mapping in entity framework core has a little bit different configuration in comparison to the previous version of entity framework. Let's create a mapping for the following class:


 
public class Sail
    {
        public int Id { get; set; }

        public int SailerId { get; set; }

        public decimal Amount { get; set; }
    }

 

You should implement an interface named IEntityTypeConfiguration<T> like below:

  

 public class SailConfiguration : IEntityTypeConfiguration<Sail>

    {

        public void Configure(EntityTypeBuilder<Sail> builder)

        {

            builder.HasKey(c => c.Id);

            builder.Property(c => c.Amount);

            builder.Property(c => c.SailerId);

        }

    }

 

In entity framework core, we don't have ToTable(tableName,schemaName) extension method in configuration anymore! In order to set table name or schema name,  you should call ToTable in Entity<> of ModelBuilder in OnModelCreating() of DbContext derived class:

 

 public class MyDbContext : DbContext

    {

        public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)

        {

        }

 

        protected override void OnModelCreating(ModelBuilder modelBuilder)

        {

            base.OnModelCreating(modelBuilder);

 

            modelBuilder.Entity<Sail>().ToTable("Sail", "MySchema");

            modelBuilder.ApplyConfiguration(new SailConfiguration());

        }

    }

 

I personally would rather create my own extension method over ModelBuilder :

 

 public static class SailConfiguration

    {

        public static ModelBuilder MapSail(this ModelBuilder modelBuilder)

        {

            var entity = modelBuilder.Entity<Sail>();

            entity.ToTable("Saile", "MySchema");

            entity.HasKey(c => c.Id);

            entity.Property(c => c.Amount);

            entity.Property(c => c.SailerId);

            return modelBuilder;

        }

    }

 

And then map it  OnModelCreating():

 

  protected override void OnModelCreating(ModelBuilder modelBuilder)

        {

            base.OnModelCreating(modelBuilder);

 

            modelBuilder.MapSail();

        }

 



About Me

Ehsan Ghanbari

Hi! my name is Ehsan. I'm a developer, passionate technologist, and fan of clean code. I'm interested in enterprise and large-scale applications architecture and design patterns and I'm spending a lot of my time on architecture subject. Since 2008, I've been as a developer for companies and organizations and I've been focusing on Microsoft ecosystem all the time. During the&nb Read More

Post Tags
Pending Blog Posts
Strategic design
Factory Pattern
time out pattern in ajax
Selectors in Jquery
Peridic pattern
How to use PagedList In asp.net MVC
How to query over Icollection<> of a type with linq
Domain driven design VS model driven architecture
Redis as a cache server
What's the DDD-lite?