Automapper, Mapping config isn't persisting - c#

So the problem.
Ive added
AutoMapperConfig.Configure();
to the application_Start in global.asax
it runs the code
Mapper.Initialize(x =>
{
x.AddProfile<DomainToViewModelMappingProfile>();
x.AddProfile<ViewModelToDomainMappingProfile>();
});
Mapper.AssertConfigurationIsValid();
which runs
public class DomainToViewModelMappingProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<DBO.User, ViewModels.UserViewModel>();
}
}
and
public class ViewModelToDomainMappingProfile : Profile
{
protected override void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ViewModels.UserViewModel, DBO.User>();
});
}
}
and everything compiles and runs fine.
but in the controller:
UserViewModel model = new UserViewModel();
User user = userService.GetUser(2);
model = Mapper.Map<User, UserViewModel>(user); //this line fails as mapping doesnt exist
return View();
but if i add the mapping config in the controller method
Mapper.CreateMap<ViewModels.UserViewModel,DBO.User>();
UserViewModel model = new UserViewModel();
User user = userService.GetUser(2);
model = Mapper.Map<User, UserViewModel>(user); //Works great
return View();
it works fine.
ignore the different syntax with automapper. Ive tried the deprecated and new way of mapping and both fail.
Thanks

The problem is that you're calling Initialize method inside your Profile which leads to overriding your already existed mappings:
public class ViewModelToDomainMappingProfile : Profile
{
protected override void Configure()
{
// you should not to call Initialize method inside your profiles.
Mapper.Initialize(cfg =>
{
cfg.CreateMap<ViewModels.UserViewModel, DBO.User>();
});
}
}
And here, you have two ways:
Way #1 (using the static API - deprecated)
public class DomainToViewModelMappingProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<DBO.User, ViewModels.UserViewModel>();
}
}
public class ViewModelToDomainMappingProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<ViewModels.UserViewModel, DBO.User>();
}
}
// initialize your mapper by provided profiles
Mapper.Initialize(x =>
{
x.AddProfile<DomainToViewModelMappingProfile>();
x.AddProfile<ViewModelToDomainMappingProfile>();
});
Mapper.AssertConfigurationIsValid();
Way #2 (using the instance API)
// in this case just call CreateMap from Profile class - its the same as CreateMap on `cfg`
public class DomainToViewModelMappingProfile : Profile
{
public DomainToViewModelMappingProfile()
{
CreateMap<DBO.User, ViewModels.UserViewModel>();
}
}
public class ViewModelToDomainMappingProfile : Profile
{
public ViewModelToDomainMappingProfile()
{
CreateMap<ViewModels.UserViewModel, DBO.User>();
}
}
// initialize you mapper config
var config = new MapperConfiguration(cfg => {
cfg.AddProfile<DomainToViewModelMappingProfile>();
cfg.AddProfile<ViewModelToDomainMappingProfile>();
});
// and then use it
var mapper = config.CreateMapper();
// or
var mapper = new Mapper(config);
var dest = mapper.Map<Source, Dest>(new Source());
In the Way #2 you will need to store you mapper configuration somewhere (static field, DI), and then use it inside your controller. I would like to suggest to inject the Mapper instance into your controller (e.g. using some DI container).
Hope it will help.

try to override "ProfileName" :
public class DomainToViewModelMappingProfile : Profile
{
public override string ProfileName
{
get
{
return "DomainToViewModelMappingProfile";
}
}
protected override void Configure()
{
Mapper.CreateMap<DBO.User, ViewModels.UserViewModel>();
}
}

OK, thanks to MaKCbIMKo for pointing me in the right direction
As described i dont have to initialize as its already being done in the automapperconfig.
the syntax once in the profile is simply.
CreateMap<ViewModels.UserViewModel, DBO.User>();

Related

Xunit new in memory database DBContext Class has live data

I am trying to unit test with the EF Core in-memory database like so:
namespace ContosoTests
{
public class TrendServiceTests
{
private static Microsoft.EntityFrameworkCore.DbContextOptions<TestContext> options = new
Microsoft.EntityFrameworkCore.DbContextOptionsBuilder<TestContext>()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
private static TestContext _context = new TestContext(options);
private readonly TrendService trendService = new TrendService(_context);
private void SeedInMemoryDb()
{
if (!_context.TrendHistories.Any())
{
_context.TrendHistories.Add(new TrendHistory { IsActive = true, Quarter = E_Quarter.Q1,
TrendYear = 2020 });
}
if (!_context.Controls.Any())
{
_context.Controls.Add(new Control { IsActive = true});
_context.Controls.Add(new Control { IsActive = true});
_context.Controls.Add(new Control { IsActive = false});
}
_context.SaveChanges();
}
}
My TestContext inherits from my live context class to avoid the error:
Services for database providers 'Microsoft.EntityFrameworkCore.InMemory', 'Microsoft.EntityFrameworkCore.SqlServer' have been registered in the service provider. Only a single database provider can be registered in a service provider
So my TestContext just looks like:
public class TestContext : ContosoContext
{
public TestContext(DbContextOptions<TestContext> options)
{
}
public TestContext()
{
}
}
When I use the new instance of TestContext (_context) in my test class, all the live data for ContosoContext is there.
I was expecting it to be a new instance of the context class with no data so that I could test my DAL code with controlled data. But this is not the case.
I am fairly new to unit testing so any help is much appreciated.
Edit:
Here are the relevant parts of my contoso context class
public class ContosoContext: IdentityDbContext<ContosoApplicationUser>
{
public ContosoContext(DbContextOptions<ContosoContext> options) : base
(options)
{
}
public ContosoContext()
{
}
//all my DBSets here
protected override void OnConfiguring(DbContextOptionsBuilder
optionsBuilder)
{
optionsBuilder.UseSqlServer("MyConnectionString");
}
}
So the issue is that my onconfiguring method handles the connection string directly??
To fix the error you need to remove optionsBuilder.UseSqlServer("MyConnectionString"); in OnConfiguring method to avoid registering multiple database providers, change it to this:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if(!optionsBuilder.IsConfigured)
optionsBuilder.UseSqlServer("MyConnectionString");
}

Is there a way to Mock a View in EF Core?

I'm using EF Core "In Memory" database but having trouble mocking a view.
var options = new DbContextOptionsBuilder<MyContext>()
.UseInMemoryDatabase(databaseName: "TestDatabase")
.Options;
// Insert seed data into the database using one instance of the context
using (var context = new MyContext(options))
{
// insert test values into entities here (omitted)...
// I want some test values for a View
context.MyView.AddRange( viewEntity1, viewEntity, ... etc );
context.SaveChanges();
}
At runtime, when attempting to add the view entities, the following exception is raised:
System.InvalidOperationException: Unable to track an instance of type
'MyView' because it does not have a primary key. Only entity types
with primary keys may be tracked.
The reason I want to mock the View is that I want to test some LINQ queries that join to the View.
Any ideas?
// Entity
public class MyView
{
public string UserId {get;set;}
public int CentreId {get;set;}
}
// Context
...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
...
modelBuilder.Entity<MyView>()
.HasNoKey()
.ToView("MyView");
}
No, the in-memory provider doesn't support views. It's a relational operation, same as the FromSql* and ExecuteSql* methods.
AddRange won't work on a view regardless of the provider.
You can test views; there are a couple of options:
Do integration tests with a real database
Use a better provider, possibly SQLite
Use a testing package that allows you to mock the view
I maintain one of said testing packages, EntityFrameworkCore.Testing, which does allow you to easily unit test SUTs that depend on views.
You should not mock anything at the Entity Framework level. Don't mock what you don't own.
In general, you should not be mocking implementation detail, which is what a view is - The consumer of your code should not need to know that it's interacting with a view, but that it's interacting with some data source. Otherwise your tests will end up breaking when you change the implementation detail, which is not what should be happening.
If you want to test how your controllers (or similar) interact with Entity Framework, you should use the Repository pattern and mock the Repository instead. If that's the case, you should not be mocking anything and instead should use a real database to test against.
If you want to test how your repositories interact with the database, you can use an integration test to test this. Repositories should be simple with very little business logic, so there is not much reason to unit test them to begin with.
Yes, you sure can....
Here is what I am using for my setup
Moq (for...mocks)
Lamar (for Dependency Injection)
STANDARD EF CORE VIEW:
Here the code for one of my view-definitions.
// EntityConfiguration
internal class ProjectStatusDetailConfiguration : IEntityTypeConfiguration<ProjectStatusDetail>
{
public void Configure(EntityTypeBuilder<ProjectStatusDetail> builder)
{
// SQL View
builder.ToView("vProjectStatus", "dbo")
.HasKey(e => e.ProjectId);
}
}
// Concrete DbContext
public class ProjectDbContext : DbContext
{
private IConfigurationRoot _settings;
public ProjectDbContext()
{
// NOTE: You must have this constructor for Migrations to work
BuildAppConfiguration();
}
public ProjectDbContext(DbContextOptions<ProjectDbContext> options) : base(options)
{
// NOTE: This constructor is used at the IServiceProvider layer
BuildAppConfiguration();
}
public virtual DbSet<Project> Project { get; set; }
public virtual DbSet<ProjectStatusDetail> vProjectStatusDetail { get; set; }
private void BuildAppConfiguration()
{
var builder = new ConfigurationBuilder();
builder.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json");
_settings = builder.Build();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// Default Configuration (if none is provided by the app)
if (!optionsBuilder.IsConfigured)
optionsBuilder.UseSqlServer(_settings.GetConnectionString(JsonSettings.ConnectionStrings.DefaultDb));
}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
// GLOBAL Configurations
builder.ApplyGlobalEntityConfiguration(new AuditableStandardUserNameGlobalConfiguration());
// ENTITY Configurations
builder.ApplyConfiguration(new ProjectConfiguration());
// SQL VIEW Configurations
builder.ApplyConfiguration(new ProjectStatusDetailConfiguration());
}
}
// Lamar ServiceRegistry (for the business-layer)
public class ContainerRegistry : ServiceRegistry
{
public ContainerRegistry()
{
Scan(scan =>
{
scan.TheCallingAssembly();
scan.WithDefaultConventions();
scan.LookForRegistries();
scan.SingleImplementationsOfInterface();
});
// --------
// DATABASE
For<DbContext>().Use<ProjectDbContext>();
For(typeof(IAuditableRepository<>)).Use(typeof(GenericAuditableRepository<>));
ForConcreteType<ProjectUnitOfWork>().Configure.Setter<DbContext>().Is<ProjectDbContext>();
// Policies
Policies.Add<GenericAuditableRepositoryConfiguredInstancePolicy>(); //<-- Sets CTOR at Runtime
Policies.Add<UnitOfWorkConfiguredInstancePolicy>(); //<-- Sets CTOR at Runtime
}
}
// SINGLE POINT OF ENTRY: Consumes & "serves-up" your App's Registry
public static class IoC
{
public static ServiceRegistry Build()
{
var registry = new ServiceRegistry();
// Registration
registry.IncludeRegistry<ContainerRegistry>();
return new ServiceRegistry(registry);
}
}
UNIT TESTING SETUP:
Here the code for my unit testing setup. Obviously, I am still working-on the DbSetProxy's query...but here it is anyway.
// Lamar ServiceRegistry (for the unit test)
internal class ContainerRegistry : ServiceRegistry
{
public ContainerRegistry()
{
// This is where you will SHIM all your Stubs/Fakes
For<IWindowsIdentityHelper>().Use<UnitTests.WindowsIdentityHelperStub>(); //<-- Stub
}
}
// SINGLE POINT OF ENTRY: Consume all Registry's (for the unit testing-layer)
public static class IoC
{
public static ServiceRegistry Build()
{
var registry = new ServiceRegistry();
// Notice you can bring-in many ServiceRegistry's
// Just be sure to apply your "stubbing" registry last
registry.IncludeRegistry<Bushido.WorkflowComponent.DependencyResolution.ContainerRegistry>();
registry.IncludeRegistry<Bushido.Project.Business.DependencyResolution.ContainerRegistry>();
registry.IncludeRegistry<Bushido.WorkflowComponent.UnitTests.DependencyResolution.ContainerRegistry>();
return new ServiceRegistry(registry);
}
}
// This base class class does the heavy-lifting
public class UnitTestBase
{
protected IContainer Container { get; private set; }
protected Builder Builder { get; set; } = new Builder();
[TestInitialize()]
public virtual void Initialize()
{
IHostBuilder hostBuilder = CreateHostBuilder(null);
IHost host = hostBuilder.Build();
IServiceScope serviceScope = host.Services.CreateScope();
IServiceProvider serviceProvider = serviceScope.ServiceProvider;
Container = serviceProvider.GetRequiredService<IContainer>();
}
[TestCleanup()]
public virtual void Cleanup()
{
Container.Dispose();
Container = null;
}
private IHostBuilder CreateHostBuilder(string[] args)
{
var builder = new HostBuilder()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
})
// You must apply the Lamar Factory
.UseServiceProviderFactory<ServiceRegistry>(new LamarServiceProviderFactory())
.ConfigureServices((hostContext, services) =>
{
var databaseName = Guid.NewGuid().ToString();
// Add Lamar & Apply Scoping here (not in your ServiceRegistry's)
services.AddLamar(IoC.Build());
services.AddScoped<ProjectUnitOfWork>();
services.AddScoped<WorkflowComponentUnitOfWork>();
services.AddScoped<ProjectDbContext>();
services.AddScoped<WorkflowComponentDbContext>();
services.AddDbContext<ProjectDbContext>((provider, options) => options.UseInMemoryDatabase(databaseName), ServiceLifetime.Singleton);
services.AddDbContext<WorkflowComponentDbContext>((provider, options) => options.UseInMemoryDatabase(databaseName), ServiceLifetime.Singleton);
});
return builder;
}
}
// This base class contains the common setup for your VIEWS DbSetProxy
public class DataScenarioBase
{
protected static DbSet<TEntity> CreateDbSet<TEntity>(IQueryable<TEntity> collection) where TEntity : class
{
var stubDbSet = new Mock<DbSet<TEntity>>();
stubDbSet.As<IQueryable<TEntity>>().Setup(m => m.Provider).Returns(collection.Provider);
stubDbSet.As<IQueryable<TEntity>>().Setup(m => m.Expression).Returns(collection.Expression);
stubDbSet.As<IQueryable<TEntity>>().Setup(m => m.ElementType).Returns(collection.ElementType);
stubDbSet.As<IQueryable<TEntity>>().Setup(m => m.GetEnumerator()).Returns(collection.GetEnumerator());
return stubDbSet.Object;
}
}
// This is a sample DataScenario
public class ProjectDefaultDataScenario : DataScenarioBase
{
public void Load(IContainer container)
{
var unitOfWork = container.GetInstance<ProjectUnitOfWork>();
// DATA
LoadDefaultData(unitOfWork) //<-- you can pre-load default data into the UnitOfWork here too
// VIEWS
var dbContext = ((ProjectDbContext)unitOfWork.DbContext);
dbContext.vProjectStatusDetail = CreateDbSet<ProjectStatusDetail>(ProjectStatusDetailQuery(unitOfWork));
// Inject
container.GetNestedContainer().Inject(unitOfWork); //<-- This (along with scoping) ensures the right instance is always passed back
}
private IQueryable<ProjectStatusDetail> ProjectStatusDetailQuery(ProjectUnitOfWork unitOfWork)
{
var query = (from project in unitOfWork.Project
select new ProjectStatusDetail
{
ProjectId = project.Id,
ProjectName = project.ProjectName,
ObjectStateName = "Testing 123",
ObjectStateDateTime = DateTime.Now,
});
return query;
}
}
// Here is a sample Unit Test
[TestMethod]
public void ProjectProvider_WorkflowProvider_ObjectState_New()
{
// -----
// ARRANGE
Builder.WorkflowDefaultSetup.Load(Container);
Builder.ProjectDefaultSetup.Load(Container);
var project = new Project();
project.ProjectName = "Red Barchetta";
project.Description = "To keep it as new has been our dearest dream";
// -----
// ACT
var provider = Container.GetRequiredService<ProjectProvider>();
provider.CreateProject(project);
// -----
// ASSERT
Assert.IsTrue(project.Id > 0);
Assert.IsNotNull(project.ObjectState);
Assert.IsInstanceOfType(project.ObjectState, typeof(Bushido.Project.Business.StateManagement.Project.New));
}

FluentValidation with Mediatr and Unity

I'm trying to use FluentValidation in a WebApi project (not asp.net Core).
I have the following code:
public static class UnityConfig
{
public static void RegisterComponents(UnityContainer container)
{
// Register validators
RegisterValidators(container);
// Mediatr
container.RegisterType<IMediator, Mediator>();
container.RegisterTypes(AllClasses.FromAssemblies(true, Assembly.GetExecutingAssembly()), WithMappings.FromAllInterfaces, GetName, GetLifetimeManager);
container.RegisterInstance<SingleInstanceFactory>(t => container.Resolve(t));
container.RegisterInstance<MultiInstanceFactory>(t => container.ResolveAll(t));
// Automapper profiles
var profileTypes = typeof(BaseProfile).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(BaseProfile)));
var config = new MapperConfiguration(cfg => new MapperConfiguration(x =>
{
foreach (var type in profileTypes)
{
var profile = (BaseProfile)Activator.CreateInstance(type);
cfg.AddProfile(profile);
}
}));
container.RegisterInstance<IConfigurationProvider>(config);
GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(container);
}
static LifetimeManager GetLifetimeManager(Type type)
{
return IsNotificationHandler(type) ? new ContainerControlledLifetimeManager() : null;
}
static string GetName(Type type)
{
return IsNotificationHandler(type) ? string.Format("HandlerFor" + type.Name) : string.Empty;
}
private static void RegisterValidators(IUnityContainer container)
{
var validators = AssemblyScanner.FindValidatorsInAssembly(Assembly.GetExecutingAssembly());
validators.ForEach(validator => container.RegisterType(validator.InterfaceType, validator.ValidatorType));
}
}
I'm scanning the assemblies and registrering the validators, of which there's only one right now, it sits here: (don't mind the weird validations, I'm trying to have it fail)
public class Query : IRequest<Result>
{
public Guid? Id { get; set; }
}
public class QueryValidator : AbstractValidator<Query>
{
public QueryValidator()
{
RuleFor(q => q.Id).Empty();
RuleFor(q => q.Id).Equal(Guid.NewGuid());
}
}
My Application_start looks like this:
protected void Application_Start()
{
var container = new UnityContainer();
UnityConfig.RegisterComponents(container);
GlobalConfiguration.Configure(WebApiConfig.Register);
var factory = new UnityValidatorFactory2(GlobalConfiguration.Configuration);
FluentValidationModelValidatorProvider.Configure(GlobalConfiguration.Configuration, x => x.ValidatorFactory = factory);
}
And I have the following validatorFactory:
public class UnityValidatorFactory2 : ValidatorFactoryBase
{
private readonly HttpConfiguration _configuration;
public UnityValidatorFactory2(HttpConfiguration configuration)
{
_configuration = configuration;
}
public override IValidator CreateInstance(Type validatorType)
{
var validator = _configuration.DependencyResolver.GetService(validatorType) as IValidator;
return validator;
}
}
Now; when I call the action on the controller, 'CreateInstance' tries to resolve a validatorType of the type:
IValidator<Guid>
instead of:
IValidator<Query>
and of course finds nothing, this means that my validations does not run.
Does anyone have an ideas as to why this is? it seems faily straight forward, so I have trouble seeing what goes wrong.
After having slept on it, I found the answer myself.
I was posting a Guid to my controller instead of the model I was trying to validate (which only contains a guid)
After posting the right model, it now validates correctly.

Multiple AutoMapper.Configure() in Global.asax

I am using AutoMapper to map between DTO objects and my business objects. I've two AutoMapperConfiguration.cs files - one in my service layer and another one in my web api layer.
As shown in the answer at the following link
Where to place AutoMapper.CreateMaps?
I am calling the Configure() of both these files in my Global.asax class
AutoMapperWebConfiguration.Configure();
AutoMapperServiceConfiguration.Configure();
but it seems like the my Service Configure call (the second call) is overwriting the mappings of the web api layer (the first call) and I get an exception saying the Mapping is missing.
If I reverse the Configure calls to look like this
AutoMapperServiceConfiguration.Configure();
AutoMapperWebConfiguration.Configure();
I don't get the exception for web api mapping but I get the same mapping exception for the Service layer.
Am I doing something wrong because this is clearly marked as an answer in the above stack overflow link?
Here's my code:
public static class AutoMapperServiceConfiguration
{
public static void Configure()
{
Mapper.Initialize(x =>
{
x.AddProfile<CmciFlowTestToGenericFlowTestSimpleMappingProfile>();
x.AddProfile<FsrsFlowTestToGenericFlowTestSimpleMappingProfile>();
});
}
}
public class FsrsFlowTestToGenericFlowTestSimpleMappingProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<FsrsFlowTest, GenericFlowTest>()
.ConvertUsing<FsrsFlowTestToGenericFlowTestSimpleConverter>();
}
}
public class FsrsFlowTestToGenericFlowTestSimpleConverter : TypeConverter<FsrsFlowTest, GenericFlowTest>
{
protected override GenericFlowTest ConvertCore(FsrsFlowTest source)
{
if (source == null)
{
return null;
}
return new GenericFlowTest
{
FlowTestDate = source.FlowTestDates,
StaticPsi = source.HydrantStaticPsi.ToString(),
ResidualPsi = source.HydrantResidualPsi.ToString(),
TotalFlow = source.NffGallonsPerMinute.ToString(),
FlowTestLocation = source.FsrsFlowTestLocations.Any()
? source.FsrsFlowTestLocations.First().LocationDescription
: null
};
}
public class CmciFlowTestToGenericFlowTestSimpleMappingProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<CmciFlowTest, GenericFlowTest>()
.ConvertUsing<CmciFlowTestToGenericFlowTestSimpleConverter>();
}
}
public class CmciFlowTestToGenericFlowTestSimpleConverter : TypeConverter<CmciFlowTest, GenericFlowTest>
{
protected override GenericFlowTest ConvertCore(CmciFlowTest source)
{
if (source == null)
{
return null;
}
return new GenericFlowTest
{
FlowTestDate = source.FlowTestDates,
StaticPsi = source.HydrantStaticPsi.ToString(),
ResidualPsi = source.HydrantResidualPsi.ToString(),
TotalFlow = source.CalculatedHydrantGallonsPerMinute.ToString(),
FlowTestLocation = source.StaticLocationHydrantFlowPSI
};
}
}
public static class AutoMapperWebConfiguration
{
public static void Configure()
{
Mapper.Initialize(x =>
{
x.AddProfile<ServiceToWebApiMappingProfile>();
x.AddProfile<WebApiToServiceMappingProfile>();
});
}
}
public class ServiceToWebApiMappingProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<ServiceFlowTest, FlowTest>();
}
}
public class WebApiToServiceMappingProfile : Profile
{
protected override void Configure()
{
Mapper.CreateMap<PropertyAddress, ServicePropertyAddress>();
}
}
To get around this issue, I am adding the service profiles in the AutoMapperWebConfiguration class and only calling AutoMapperWebConfiguration.Configure() in global.asax.
The calls to Mapper.Initialize reset the mapper so everything that's gone before is wiped.
Move the calls to AddProfile into one Mapper.Initialize call and all should be well:
Mapper.Initialize(x =>
{
x.AddProfile<CmciFlowTestToGenericFlowTestSimpleMappingProfile>();
x.AddProfile<FsrsFlowTestToGenericFlowTestSimpleMappingProfile>();
x.AddProfile<ServiceToWebApiMappingProfile>();
x.AddProfile<WebApiToServiceMappingProfile>();
});
#GruffBunny's answer is correct, but I tried to make it a bit neater for scalability (e.g. if you have many, complex Mapper.Initialize() methods, and might add more in the future).
I did this by implementing the following structure in all of my AutoMapperConfiguration.cs files:
Extract the Action<IConfiguration> from your existing Mapper.Initialize() method into a public property
I call it ConfigAction in each one.
public static Action<IConfiguration> ConfigAction = new Action<IConfiguration>(x =>
{
x.AddProfile<SomeProfile>();
x.AddProfile<SomeOtherProfileProfile>();
//... more profiles
});
This allows you to invoke the action from anywhere you need to call Mapper.Initialize.
Mapper.Initialize() inside Configure() now just references this property
public static void Configure()
{
Mapper.Initialize(ConfigAction);
}
You can then invoke all your different ConfigActions in your single, centralized call to Mapper.Initialize()
AutoMapperConfiguration.Configure() in Application_Start() becomes
Mapper.Initialize(x =>
{
Project1.AutoMapperConfiguration.ConfigAction.Invoke(x);
Project2.AutoMapperConfiguration.ConfigAction.Invoke(x);
Project3.AutoMapperConfiguration.ConfigAction.Invoke(x);
//... keep adding as your project grows
});
This eliminates the need to copy-and-paste the method body from each separate Mapper.Initialize() call into your central call. DRY and all that.
To update #theyetiman's brilliant answer for AutoMapper 5.2 & .NET Core.
public static class AutoMapperConfiguration
{
public static void Configure()
{
Mapper.Initialize(ConfigAction);
}
public static Action<IMapperConfigurationExpression> ConfigAction = cfg =>
{
cfg.AddProfile<SomeProfile>();
cfg.AddProfile<SomeOtherProfileProfile>();
};
}
API or web startup.
public Startup(IHostingEnvironment env)
{
Mapper.Initialize(x =>
{
Project1.AutoMapperConfiguration.ConfigAction.Invoke(x);
Project2.AutoMapperConfiguration.ConfigAction.Invoke(x);
Project3.AutoMapperConfiguration.ConfigAction.Invoke(x);
});
}

Entity Framework Migrations - Enable AutoMigrations along with added migration

I'm utilizing Entity Framework 4.3 Migrations in my project. I would like to use Automatic migrations so that when I make modifications to my domain objects and my context class, my database automatically updates when I run the project. I have this working so far.
I would also like to use some Added Migrations in addition to the automatic migrations, and I would like the application to automatically jump to the latest version (based on my added migrations) when I run the application.
In order to do this I have placed this in the global.asax file...
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, Core.Migrations.Configuration>());
Now this works, but when I do this it no longer automatically updates the database based on my domain objects.
I would like to be able to completely delete the database and then run the application and have all the automatic migrations run and then have my explicit migrations run and bring the database up to the latest version.
I know I've had this working in a previous project, but I'm not sure what I'm doing wrong in this instance.
Thanks
You need to pass a configuration that has the AutomaticMigrationsEnabled set to true in the constructor. Something like this should help:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, MyConfiguration>());
with MyConfiguration being something like:
public class MyConfiguration : Core.Migrations.Configuration
{
public MyConfiguration { this.AutomaticMigrationsEnabled = true; }
}
DISCLAIMER: Just hacked this in, so small tweaks might be required to get this to compile
EDIT:
Just checked with EF 4.3.1 and the code is like this for the initializer:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<DataContext, MyConfiguration>());
and this for the configuration class:
public class MyConfiguration : System.Data.Entity.Migrations.DbMigrationsConfiguration<DataContext>
{
public MyConfiguration()
{
this.AutomaticMigrationsEnabled = true;
}
}
After banging my head on this for several hours, I finally came up with a solution that creates the database if necessary or upgrades it if out of date. We use this technique in Gallery Server Pro to make it easy to install the first time or upgrade previous versions.
private static void InitializeDataStore()
{
System.Data.Entity.Database.SetInitializer(new System.Data.Entity.MigrateDatabaseToLatestVersion<GalleryDb, GalleryDbMigrationConfiguration>());
var configuration = new GalleryDbMigrationConfiguration();
var migrator = new System.Data.Entity.Migrations.DbMigrator(configuration);
if (migrator.GetPendingMigrations().Any())
{
migrator.Update();
}
}
public sealed class GalleryDbMigrationConfiguration : DbMigrationsConfiguration<GalleryDb>
{
protected override void Seed(GalleryDb ctx)
{
MigrateController.ApplyDbUpdates();
}
}
I wrote up a blog post with a few more details:
Using Entity Framework Code First Migrations to auto-create and auto-update an application
Same solution that Roger did but using an static constructor on the DbContext.
Full code below.... this allow the initialization code to live on the class itself and is self-invoked at the first instantiation of the DataDbContext class.
public partial class DataDbContext : DbContext
{
public DataDbContext()
: base("name=DefaultConnection")
{
}
static DataDbContext() // This is an enhancement to Roger's answer
{
Database.SetInitializer(new DataDbInitializer());
var configuration = new DataDbConfiguration();
var migrator = new DbMigrator(configuration);
if (migrator.GetPendingMigrations().Any())
migrator.Update();
}
// DbSet's
public DbSet<CountryRegion> CountryRegion { get; set; }
// bla bla bla.....
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
//Configuration.ValidateOnSaveEnabled = false;
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.AddFromAssembly(Assembly.GetExecutingAssembly()); // Discover and apply all EntityTypeConfiguration<TEntity> of this assembly, it will discover (*)
}
}
internal sealed class DataDbInitializer : MigrateDatabaseToLatestVersion<DataDbContext, DataDbConfiguration>
{
}
internal sealed class DataDbConfiguration : DbMigrationsConfiguration<DataDbContext>
{
public DataDbConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(DataDbContext context)
{
DataSeedInitializer.Seed(context);
base.Seed(context);
}
}
internal static class DataSeedInitializer
{
public static void Seed(DataDbContext context)
{
SeedCountryRegion.Seed(context);
// bla bla bla.....
context.SaveChanges();
}
}
internal static class SeedCountryRegion
{
public static void Seed(DataDbContext context)
{
context.CountryRegion.AddOrUpdate(countryRegion => countryRegion.Id,
new CountryRegion { Id = "AF", Name = "Afghanistan" },
new CountryRegion { Id = "AL", Name = "Albania" },
// bla bla bla.....
new CountryRegion { Id = "ZW", Name = "Zimbabwe" });
context.SaveChanges();
}
}
public class CountryRegionConfiguration : EntityTypeConfiguration<CountryRegion> // (*) Discovered by
{
public CountryRegionConfiguration()
{
Property(e => e.Id)
.IsRequired()
.HasMaxLength(3);
Property(e => e.Name)
.IsRequired()
.HasMaxLength(50);
}
}
public partial class CountryRegion : IEntity<string>
{
// Primary key
public string Id { get; set; }
public string Name { get; set; }
}
public abstract class Entity<T> : IEntity<T>
{
//Primary key
public abstract T Id { get; set; }
}
public interface IEntity<T>
{
T Id { get; set; }
}
We can see that the Seed method is running again and again..
We can avoid this by checking if a migration already exits, since one is applied automatically when the database is create.. then we can refactor the DataDbConfiguration as follows...
internal sealed class DataDbConfiguration : DbMigrationsConfiguration<DataDbContext>
{
private readonly bool _isInitialized;
public DataDbConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
var migrator = new DbMigrator(this);
_isInitialized = migrator.GetDatabaseMigrations().Any();
}
protected override void Seed(DataDbContext context)
{
InitializeDatabase(context);
}
public void InitializeDatabase(DataDbContext context)
{
if (!_isInitialized)
{
if (context.Database.Connection.ConnectionString.Contains("localdb"))
{
DataSeedInitializer.Seed(context); // Seed Initial Test Data
}
else
{
// Do Seed Initial Production Data here
}
}
else
{
// Do any recurrent Seed here
}
}
}
Here is my current solution, which I'm not completely satisfied with.
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
var context = new KCSoccerDataContext();
var initializeDomain = new CreateDatabaseIfNotExists<KCSoccerDataContext>();
var initializeMigrations = new MigrateDatabaseToLatestVersion<KCSoccerDataContext, Core.Migrations.Configuration>();
initializeDomain.InitializeDatabase(context);
initializeMigrations.InitializeDatabase(context);
}
I'm actually creating two different initializers. The first, using CreateDatabaseIfNotExists, succcessfully goes through and creates tables based on my Domain objects. The second, using MigrateDatabaseToLatestVersion, executes all of my explicit migrations.
I don't like it because Automatic Migrations are basically disabled. So in order to add or change my Domain model I have to completely drop the database and recreate it. This won't be acceptable once I've moved the application to production.
If your application contains Startup.cs class, you can use DbMigrator Class as follows
Go to your App_Start folder, open Startup.Auth
Paste these lines of code inside of ConfigureAuth method
var configuration = new Migrations.Configuration();
var dbmigrator = new DbMigrator(configuration);
dbmigrator.Update();
NOTE: Remember to use this namespace- using System.Data.Entity.Migrations;
what this does is to update your database to the latest version anytime the application starts up
You just need to do
private static void InitializeDataStore()
{
System.Data.Entity.Database.SetInitializer(new System.Data.Entity.MigrateDatabaseToLatestVersion<GalleryDb, GalleryDbMigrationConfiguration>());
System.Data.Entity.Database.Initialize(false);
}

Categories

Resources