Thursday, March 30, 2017

.Net Core Entity Framework Seperate Entity and Mapping Class

Follow the below steps to have separate entity and mapping definition for entity framework.

This blog assumes you have prior knowledge of the .Net Entity Framework and you have ApplicationDBContext created already.

First we will have BaseEntity Class


namespace TogetherWeCan.Data.IdentityManagement.Model
{
    public class EntityBase
    {
    }
}  
  

Then, we will have EntityMappingBase class with virtual function accpeting ModelBuilder as a parameter.


using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using System;
using System.Collections.Generic;
using System.Text;

namespace TogetherWeCan.Data.IdentityManagement.Model
{
    public abstract class EntityMappingBase<T> where T : EntityBase
    {

        public virtual void BuildAction(ModelBuilder builder)
        {
            builder.Entity<EntityBase>();
        }

    }
}

We are going to define two Entity Class.

  1. Group
  2. GroupTwo


namespace TogetherWeCan.Data.IdentityManagement.Model
{
    public class Group : EntityBase
    {
        public Guid GroupId { get; set; }
        public string Name { get; set; }
        public DateTime Created { get; set; }
    }
}
namespace TogetherWeCan.Data.IdentityManagement.Model
{
    public class GroupTwo : EntityBase
    {
        public Guid GroupTwoId { get; set; }
        public string Name { get; set; }
        public DateTime Created { get; set; }
    }
}  
  

Next we will define two mapping for Group and GroupTwo entity class.

  1. GroupMapping
  2. GroupTwoMapping


namespace TogetherWeCan.Data.IdentityManagement
{
    public class GroupMapping : EntityMappingBase<EntityBase>
    {
        public override void BuildAction(ModelBuilder builder)
        {
            var entityBuilder = builder.Entity<Group>();
            entityBuilder.HasKey(p => p.GroupId);
            entityBuilder.Property(p => p.Name).IsRequired();

        }

    }
}
  
namespace TogetherWeCan.Data.IdentityManagement
{
    public class GroupTwoMapping : EntityMappingBase<EntityBase>
    {
        public override void BuildAction(ModelBuilder builder)
        {
            var entityBuilder = builder.Entity<GroupTwo>();
            entityBuilder.HasKey(p => p.GroupTwoId);
            entityBuilder.Property(p => p.Name).IsRequired();

        }

    }
}  


Now we will have ApplicationDBContext class to initialize above mapping
In order to get the runtime assembly and class i have used below packages

For RuntimeEnvironment.GetRuntimeIdentifier() I have used  Microsoft.DotNet.InternalAbstractions;

For  DependencyContext.Default.GetRuntimeAssemblyNames(runtimeId).Where(w => w.FullName.Contains("TogetherWeCan.Data")); I have used Microsoft.Extensions.DependencyModel;



using System;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using TogetherWeCan.Data.IdentityManagement;
using Microsoft.EntityFrameworkCore;
using System.Reflection;
using System.Linq;
using TogetherWeCan.Data.IdentityManagement.Model;
using Microsoft.DotNet.InternalAbstractions;
using Microsoft.Extensions.DependencyModel;

namespace TogetherWeCan.Data
{
    public class ApplicationIdentityDBContext : IdentityDbContext<ApplicationUser>
    {
        public ApplicationIdentityDBContext(DbContextOptions<ApplicationIdentityDBContext> options) : base(options)
        {
            
        }
        public DbSet<Group> Group { get; set; }
        public DbSet<GroupTwo> GroupTwo { get; set; }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            var runtimeId = RuntimeEnvironment.GetRuntimeIdentifier();
            var assemblies = DependencyContext.Default.GetRuntimeAssemblyNames(runtimeId).Where(w => w.FullName.Contains("TogetherWeCan.Data"));

            foreach (var assembly in assemblies)

            {
                Assembly newtonsoftJson = Assembly.Load(assembly);
                foreach (var t in newtonsoftJson.GetTypes())
                {
                    var btyp = t.GetTypeInfo().BaseType;
                    if (btyp != null && btyp.IsConstructedGenericType && btyp.GetGenericTypeDefinition() == typeof(EntityMappingBase<>))
                    {
                        var entityBaseMapping = Activator.CreateInstance(t) as EntityMappingBase<EntityBase>;
                        entityBaseMapping.BuildAction(builder);
                    }

                }

            }
   

            base.OnModelCreating(builder);
        }

    }
}
  
  


Testing

For testing in Startup.cs inject ApplicationDBContext in Configure function and use context.Database.Migrate(); to force entity framework run the migration. Don't forget to add migrations.



using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using TogetherWeCan.Data;
using TogetherWeCan.Data.IdentityManagement;

namespace TogetherWeCan
{
    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddDbContext<ApplicationIdentityDBContext>(options => {
                options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
                });


            services.AddIdentity<ApplicationUser, IdentityRole>()
       .AddEntityFrameworkStores<ApplicationIdentityDBContext>()
       .AddDefaultTokenProviders();

            // Add framework services.
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, ApplicationIdentityDBContext context)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();
            context.Database.Migrate();

            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                app.UseBrowserLink();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }

            app.UseStaticFiles();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}
  
  

2 comments:

  1. Not as good as Scott Hanselman ;)

    ReplyDelete
  2. Broooo, Your class ApplicationUser inherits from IdentityUser if I'm not mistaken, IdentityUser is a base class and not an interface, c# only allows inheritance from one base, I recommend you make a base Interface forcing an Id and then set a type of int for each of the Identity domain classes e.g:

    public class ApplicationUser : IdentityUser, IEntityBase
    {
    public virtual ICollection AspFiles { get; set; }
    }

    public interface IEntityBase
    {
    int Id { get; set; }
    }

    public class EntityBase : IEntityBase
    {
    public int Id { get; set; }
    }

    Then cast it and create an instance of IIdentityBase:

    EntityMappingBase;

    From Mikeyyyyy

    ReplyDelete