Multiple AutoMapper.Configure() in Global.asax - c#

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);
});
}

Related

Application_Start event on ASP.NET Core 6.0

How would I go about setting global variables in ASP.NET Core 6.0(razor pages)?
I have some information in the database, for example, ServiceName, ContactEmail and so on, and want to save it to my static class.
I don't want to access the database every time I need to display the information.
In addition, there aren't Global.asax in ASP.NET Core .
In ASP.NET MVC 5 (based on .net framework), I could do it like
// global.asax
protected void Application_Start() {
var context = new DefaultConnection();
MyConfig.ServiceName = context.GlobalSettings.SingleOrDefault().ServiceName;
// MyConfig is my static class
}
But I don't know where I should do it in ASP.NET Core project.
How can I do that? Please help me.
So lazy-loading is probably a very good choice for you.
Step 1: Create a data service that provides your data.
public interface IStaticDbData // Think of a better name!
{
public Task<string> GetContactEmailAsync();
public Task<string> GetServiceNameAsync();
// Etc.
}
public class StaticDbData : IStaticDbData
{
// Since we want a singleton, we'll have to synchronize the data fetching.
private object _lock = new object();
private string _contactEmail;
private string _serviceName;
// Etc.
// Try to create a single function that loads all of the data in one round trip to the DB.
// This will run in its own thread, so the calling thread can be awaited.
private Task LoadAllDataAsync()
=> Task.Run(() =>
{
lock (_lock)
{
//Re-check after locking.
if (_contactEmail != null)
{
return;
}
// Database code here to extract your data.
// Save to the individual fields.
}
});
public async Task<string> GetContactEmailAsync()
{
// See if data is there.
if (_contactEmail != null)
{
return _contactEmail;
}
// Data was not there. Load data.
await LoadAllDataAsync();
return _contactEmail;
}
public async Task<string> GetServiceNameAsync()
{
if (_serviceName != null)
{
return _serviceName;
}
await LoadAllDataAsync();
return _serviceName;
}
}
Step 2: Now that you have your service interface and service implementation, register the m in the IoC container. In program.cs:
builder.Services.AddSingleton<IStaticDbData, StaticDbData>();
Step 3: Consume the service as you would any other service.
public class SomeOtherServiceOrControllerOrWhatever
{
private IStaticDbData StaticDbDataSvc { get; }
// Constructor-injected.
public SomeOtherServiceOrControllerOrWhatever(IStaticDbData staticDbDataSvc)
{
StaticDbDataSvc = staticDbDataSvc;
}
}
NOTE: Make sure that your consuming services are also registered and resolved using the IoC container.
This is sudo code
You can create a static class with static properties:
public static class MyConfig
{
public static string Setting1 {set; get;}
...
}
then write a method to fetch data from your database and fill MyConfig and in the Program.cs file just call that method:
app.MapControllers();
app.Run();
CallYourMethodHere(); <-----
another is you can do this:
first create a static class:
public static class MyConfig
{
private static Dictionary<string, string> MyConfigs {set; get;}
private static Dictionary<string, string> GetConfigFromDatabase(bool forceToFill)
{
if(MyConfigs == null || MyConfigs.Any() == false || forceToFill == true)
{
//Fetch Data From Database and Fill MyConfig
}
}
public static string GetConfig(string configName)
{
return GetConfigFromDatabase(false)[configName];
}
}
In solution 2 you have to consider some thread-safe and race condition concepts.

Call particular filter for particular Razor Pages Route?

I have 2 domains (.com and .ru) and 2 URLs like site.com/about-us and site.ru/o-nas which should be redirected to the same page. The site uses Razor Pages.
Also, the particular URL should be available in the appropriate domain. For example:
site.COM/o-nas should not work and return Not Found (404)
site.RU/about-us should not work and return Not Found (404)
I found that filters work OK, but for both for site.com/about-us and site.ru/o-nas both filters are called.
How to call only 1 for particular URL, is it possible? Thank you, my current code is below.
public static class DomainFilters
{
public static IPageApplicationModelConvention DomainEng(
this PageConventionCollection con, string pageName, string route = "")
{
return con.AddPageApplicationModelConvention(pageName, model =>
{
model.Filters.Add(new EnglishActionFilter(route));
});
}
public static IPageApplicationModelConvention DomainRussian(
this PageConventionCollection con, string pageName, string route = "")
{
return con.AddPageApplicationModelConvention(pageName, model =>
{
model.Filters.Add(new RussianActionFilter(route));
});
}
}
public class EnglishActionFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.HttpContext.Request.Host.ToString().Contains(".ru"))
{
context.Result = new NotFoundResult();
}
}
public void OnResultExecuted(ResultExecutedContext context) { }
}
public class RussianActionFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.HttpContext.Request.Host.ToString().Contains(".com"))
{
context.Result = new NotFoundResult();
}
}
public void OnResultExecuted(ResultExecutedContext context) { }
}
And finally ConfigureServices method from Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc()
.AddRazorPagesOptions(options =>
{
options.Conventions.DomainEng("/AboutUs", "about-us");
options.Conventions.DomainRussian("/AboutUs", "o-nas");
})
}
Consider implementation of a custom FilterFactory:
public class LanguageFilterFactory : Attribute, IFilterFactory
{
public bool IsReusable => false;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
var context = serviceProvider.GetService<IHttpContextAccessor>();
if (context.HttpContext.Request.Host.ToString().Contains(".com"))
{
return new EnglishActionFilter();
}
return new RussianActionFilter();
}
}
This factory will create either an English or Russian filter (depending on the domain). That's all about its responsibilities. The rest goes to Filters themselves (you'll need to change a code inside the filters to make them validate the page locator):
public class RussianActionFilter : IResultFilter
{
public void OnResultExecuting(ResultExecutingContext context)
{
// you may want to play with RouteData in order to make this check more elegant
if (context.HttpContext.Request.Path.Value.Contains("About"))
{
context.Result = new NotFoundResult();
}
}
}
The filter factory is applied in the same way as other filters:
[LanguageFilterFactory]
public class IndexModel : PageModel
The Startup.cs file update:
.AddMvcOptions(options =>
{
options.Filters.Add<LanguageFilterFactory>();
});

Automapper, Mapping config isn't persisting

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>();

DbContext ChangeTracker loses Changes in Web request. via Action Filter

I'm trying to create a UnitOfWork Action Filter.
I'm hooking into the OnActionExecuted method where i want to save all changes to the DBContext depending on three rules :-
The Context is NOT NULL.
The Context ChangeTracker HasChanges
No Exceptions have been caught throughout the lifetime of the ActionMethods existence.
Throughout the action methods in the WEB API, I only ever attach entities to the DbContext, its only when the action itself has completed without any errors do I commit changes.
The DbContext in setup using Ninject witha lifestyle of "InRequestScope".
Here is the UnitOfWork ActionFilterAttribute :-
public class UnitOfWorkActionFilterAttribute : ActionFilterAttribute
{
public virtual IActionTransactionHelper ActionTransactionHelper
{
get { return WebContainerManager.Get<IActionTransactionHelper>(); }
}
public override bool AllowMultiple
{
get { return false; }
}
public override void OnActionExecuting(HttpActionContext actionContext)
{
ActionTransactionHelper.BeginTransaction();
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
ActionTransactionHelper.EndTransaction(actionExecutedContext);
ActionTransactionHelper.CloseSession();
}
}
In Ninject, the DbContext is configured like this :-
container.Bind<MyDbContext>().ToSelf().InRequestScope();
Here is the ActionTransactionHelper Class :-
public class ActionTransactionHelper : IActionTransactionHelper
{
public bool TransactionHandled { get; private set; }
public bool SessionClosed { get; private set; }
public void BeginTransaction()
{
var sessionContext = WebContainerManager.Get<MyDbContext>();
if (sessionContext == null)
{
throw new NullReferenceException("sessioncontext");
}
}
public async Task EndTransaction(HttpActionExecutedContext filterContext)
{
var sessionContext = WebContainerManager.Get<MyDbContext>();
if (sessionContext != null && sessionContext.ChangeTracker.HasChanges() && filterContext.Exception == null)
{
var x = await sessionContext.SaveChangesAsync();
}
TransactionHandled = true;
}
public void CloseSession()
{
var sessionContext = WebContainerManager.Get<MyDbContext>();
if (sessionContext == null) return;
sessionContext.Dispose();
SessionClosed = true;
}
}
I have an Action method that attaches enities to the DBContext like this :-
context.Claims.Add(entity);
When the EndTransaction() Method is fired on ActionExecuted, the context object has no record in the ChangeTracker of any changes and the SaveChangesAsync() method is never fired.
However, If i change the Ninject Binding for the DbContext to a Singleton, the code works fine, the change is tracked and the object is saved to the database.
I don't understand why this isn't working per web request?
I dont want to use Singletons in this case.
This problem was resolved by adding the Ninject.WeApi2 Nuget Package.
In the NinjectWebCommon i replaced the NinjectDependencyResolver Implementation with the one referenced in the Ninject.WeApi2
**Ninject.Web.WebApi.NinjectDependencyResolver**
InRequestScope() now works fine with Action Filters and the DBContext tracks changes.

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