Wednesday, March 29, 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?}");
            });
        }
    }
}
  
  

Saturday, March 18, 2017

Entity Framework Code First Unit/Integration Testing Using Effort

We can unit test Entity Framework Code First using Effort.
Effort can be installed in test project using nuget package https://www.nuget.org/packages/Effort.EF6/ 

Following below steps we can have basic EF code first unit test.

Create Student Domain Class

 
 public class Student
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Email { get; set; }
        public int CollegeId { get; set; }
        public DateTime DOB { get; set; }

    }



Create Mapping Class

 
 public class StudentMap : EntityTypeConfiguration
    {
        public StudentMap()
        {

            this.ToTable("Student");

            this.HasKey(s => s.Id);

            this.Property(s => s.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

            this.Property(s => s.Email).IsRequired().HasMaxLength(50);

            this.Property(s => s.FirstName).IsRequired().HasMaxLength(50);

            this.Property(s => s.LastName).IsRequired().HasMaxLength(50);

            this.Property(s => s.DOB).IsRequired();
          
        }
    }



Create School Context Class

Note: There is DatabaseInitializer which is drop and create always required for unit testing.
 
 public class SchoolContext : DbContext
    {
        public SchoolContext() : base()
        {
            
        }

        ///         /// We need this constructor to pass the Effort dbConnection to process data in memory
        ///         public SchoolContext(DbConnection dbConnection) : base(dbConnection, true)
        {
            
            Database.SetInitializer(
      new DropCreateDatabaseAlways());
        }

        public DbSet Student { get; set; }
                
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            modelBuilder.Configurations.Add(new StudentMap());

            base.OnModelCreating(modelBuilder);
        }
    }



Create StudentRepository and Student Service class to perform Insert/Update Operations.

IStudentRepository and StudentRepository



public interface IStudentRepository
    {
        Student GetStudent(int studentId);

        int AddStudent(Student student);

    }
 
 public class StudentRepository : IStudentRepository
    {
        private SchoolContext context;

        public StudentRepository(SchoolContext context)
        {
            this.context = context;
        }

        public Student GetStudent(int studentId)
        {
            return this.context.Student.Find(studentId);
        }

        public int AddStudent(Student student)
        {
            this.context.Student.Add(student);
            this.context.SaveChanges();

            return student.Id;
        }
    }



IStudentService and StudentService



 public interface IStudentService
    {
        Student GetStudent(int studentId);

        int AddStudent(Student student);
    }
 
   public class StudentService : IStudentService
    {
        private IStudentRepository _iStudentRepository;

        public StudentService(IStudentRepository iStudentRepository)
        {
            _iStudentRepository = iStudentRepository;
        }

        public Student GetStudent(int studentId)
        {
            return _iStudentRepository.GetStudent(studentId);
        }

        public int AddStudent(Student student)
        {
            return _iStudentRepository.AddStudent(student);
        }

    }



Unit Test Class

Add Effort.EF6 Nuget package to unit test library



 [TestClass]
    public class EntityFrameworkTest
    {
        private SchoolContext context;

        [TestInitialize]
        public void Initialize()
        {
            context = new SchoolContext(Effort.DbConnectionFactory.CreateTransient());
        }


        [TestMethod]
        public void TestConnection()
        {
            IStudentService studentService = new StudentService(new StudentRepository(context));

            studentService.AddStudent(new Student()
            {
                FirstName = "Murtaza",
                LastName = "Ali",
                Email = "murtaza@great",
                DOB = DateTime.Now,
                CollegeId = 75
            });

            var student = studentService.GetStudent(1);


        }
    }