I have a DbContext that is empty. Mappings are created dynamically and the DbContext is used generically using Set();
The following is my generic DbContext.
/// <summary>
/// Object context
/// </summary>
public class MethodObjectContext : DbContext, IDbContext
{
private readonly IEventPublisher _eventPublisher;
public MethodObjectContext(string nameOrConnectionString, IEventPublisher eventPublisher)
: base(nameOrConnectionString)
{
_eventPublisher = eventPublisher;
}
public MethodObjectContext(DbConnection existingConnection, bool contextOwnsConnection, IEventPublisher eventPublisher)
: base(existingConnection, contextOwnsConnection)
{
_eventPublisher = eventPublisher;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
_eventPublisher.Publish(new ModelCreating(modelBuilder));
base.OnModelCreating(modelBuilder);
}
public new IDbSet<TEntity> Set<TEntity>() where TEntity : class
{
return base.Set<TEntity>();
}
}
I am trying write a unit test that will assert that the database is out of sync if I change the mappings (from the ModelCreating event).
The following is my test code.
[TestClass]
public class MigrationTests
{
private string _connectionString = string.Empty;
private string _testDb = string.Empty;
public MigrationTests()
{
_testDb = Path.Combine("C:\\", System.Reflection.Assembly.GetExecutingAssembly().GetName().Name.Replace(".", "") + ".sdf");
if (File.Exists(_testDb))
File.Delete(_testDb);
_connectionString = string.Format("Data Source={0};Persist Security Info=False;", _testDb);
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
}
[TestMethod]
public void ThrowsErrorForOutOfDateDatabase()
{
// The initializer will handle migrating the database.
// If ctor param is false, auto migration is off and an error will be throw saying the database is out of date.
Database.SetInitializer(new MigrationDatabaseInitializer<MethodObjectContext>(false));
// Create the initial database and do a query.
// This will create the database with the conventions of the Product1 type.
TryQueryType<Product1>("Product");
// The next query will create a new model that has conventions for the product2 type.
// It has an additional property which makes the database (created from previous query) out of date.
// An error should be thrown indicating that the database is out of sync.
ExceptionAssert.Throws<InvalidOperationException>(() => TryQueryType<Product2>("Product"));
}
private void TryQueryType<T>(string tableName) where T : class
{
using (var context = new MethodObjectContext(_connectionString, new FakeEventPublisher(x => x.ModelBuilder.Entity<T>().ToTable(tableName))))
{
var respository = new EfRepository<T>(context);
var items = respository.Table.ToList();
}
}
}
My Product1 class is a POCO object, and my Product2 class is the same object with an additional db field.
My problem is that when I new() up the MethodObjectContext the second time and do a query, the ModelCreating method isn't called, causing me to get the following error.
The entity type Product2 is not part of the model for the current context.
Product2 would be a part of the context of the ModelCreating event was being called, but it is not. Any ideas?
NOTE: I am expecting errors since we are using the same connection string (sdf) and the db being created didn't create the additional field that my second call (Product2) requires.
My DbCompiledModel was being cached. The following flushed the cache.
private void ClearDbCompiledModelCache()
{
var type = Type.GetType("System.Data.Entity.Internal.LazyInternalContext, EntityFramework");
var cmField = type.GetField("CachedModels",System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
var cachedModels = cmField.GetValue(null);
cachedModels.GetType().InvokeMember("Clear", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.InvokeMethod, null, cachedModels, null);
}
Related
Expected/desired behavior: "Members" table is created in "mySchema"
Actual behavior: "Members" table is created in "public" (the default schema)
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace Test
{
class MyContext : DbContext
{
readonly string connectionString;
readonly string schema;
readonly bool isParameterless;
public MyContext()
{
connectionString = #"Server = 127.0.0.1; Port = 5432; Database = empty; User Id = postgres; Password = 123;";
isParameterless = true;
}
public MyContext(string connectionString, string schema)
{
this.connectionString = connectionString;
this.schema = schema;
}
public DbSet<Member> Members { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
if (!isParameterless)
modelBuilder.HasDefaultSchema(schema);
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseNpgsql(connectionString);
}
}
class Member
{
public string id { get; set; }
}
class Programs
{
static void Main(string[] args)
{
var context = new MyContext(#"Server = 127.0.0.1; Port = 5432; Database = test; User Id = postgres; Password = 123;", schema: "mySchema");
context.Database.EnsureDeleted();
Console.WriteLine(context.Model.GetDefaultSchema()); // "mySchema"
Console.WriteLine(context.Model.FindEntityType(typeof(Member)).GetSchema()); // "mySchema"
context.Database.Migrate();
Console.WriteLine(context.Model.GetDefaultSchema()); // "mySchema"
Console.WriteLine(context.Model.FindEntityType(typeof(Member)).GetSchema()); // "mySchema"
context.Database.ExecuteSqlRaw(#"CREATE TABLE public.""Members""()"); // Npgsql.PostgresException: '42P07: relation "Members" already exists'
context.Database.ExecuteSqlRaw(#"CREATE TABLE ""mySchema"".""Members""()"); // Npgsql.PostgresException: '3F000: schema "mySchema" does not exist'
}
}
}
Migrations were generated for this code using the following command:
dotnet ef migrations add init -v
Further technical detail
EF Core version: 3.1.0
Database provider: Npgsql.EntityFrameworkCore.PostgreSQL (3.1.0)
Target framework: NET Core 3.0
VS Project.zip
You haven't posted a full program so it's not possible to be sure.
However, my guess is that you've created a context via the parameterless constructor before doing so with the other constructor. The EF Core model building, which includes running OnModelCreating, runs only once when you first use the context; it isn't redone for every single context you instantiate. As a result, you can't have multiple contexts with different default schemas.
I am using entity framework for my DAL and want to convert entities objects to business objects and vice versa. This is taking place in my BLL project. I am hoping to setup automapper in my BLL project to take... let say Customer.cs auto generated by EF and convert it to CustomerWithDifferentDetail.cs (my business obj)
I attempted to create an AutoMapperBLLConfig.cs under BLL project with the following code:
public static void Configure()
{
Mapper.Initialize(cfg =>
{
cfg.AddProfile(new CustomerProfile());
});
}
public class CustomerProfile : Profile
{
protected override void Configure()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerWithDifferentDetail>();
cfg.CreateMap<CustomerWithDifferentDetail, Customer>();
});
}
}
Then I created CustermerService.cs under BLL project with the following code to test if it's working:
public void CustomerToCustomerWithDifferentDetail()
{
AutoMapperBLLConfiguration.Configure();
Customer source = new Customer
{
Account = 1234,
Purchase_Quantity = 100,
Date = "05/05/2016",
Total = 500
};
Models.CustomerWithDifferentDetail testCustomerDTO = Mapper.Map<Customer, Models.CustomerWithDifferentDetail>(source)
}
I get this Error:
Missing type map configuration or unsupported mapping.
I am not sure what I did wrong. I don't have a start_up or global.aspx. This is a class library. I'm not sure what I'm missing or did wrong.
I have a separate project calls Models which hold all the business objects including CustomerWithDifferentDetail.cs. In this case, CustomerWithDifferentDetail only has two properties: Account and Total. If mapped, it should give me Account = 1234 and Total = 500 - basically the same data as entity object just in different shape.
======================= UPDATE=================================
AutoMapperBLLConfig.cs - stay the same as noted above
CustomerProfile.cs
public class CustomerProfile : Profile
{
protected override void Configure()
{
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerWithDifferentDetail>().ReverseMap(); //cut it down to one line with ReverseMap
});
}
CreateMap<Customer, CustomerWithDifferentDetail>().ReverseMap(); //missed this one line before; hence, the error
}
CustomerService.cs
static CustomerService()
{
AutoMapperBLLConfiguration.Configure(); //per #Erik Philips suggestion, move this call to a static constructor
}
public void CustomerToCustomerWithDifferentDetail()
{
Customer source = new Customer
{
Account = 1234,
Purchase_Quantity = 100,
Date = "05/05/2016",
Total = 500
};
Models.CustomerWithDifferentDetail testCustomerDTO = Mapper.Map<Customer, Models.CustomerWithDifferentDetail>(source);
}
Result: my testCustomerDTO returns exactly what I expected.
Since you are using the instance method of AutoMapper:
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<Customer, CustomerWithDifferentDetail>();
cfg.CreateMap<CustomerWithDifferentDetail, Customer>();
});
Then you need to use the instance for mapping:
Models.CustomerWithDifferentDetail testCustomerDTO =
config.Map<Customer, Models.CustomerWithDifferentDetail>(source)
I personally haven't really thought this through in my applications (I need to move to the instance method instead of the static method). (Migrating from status API).
Off the cuff, based on your code, I'd probably do something like:
public class PersonDataObject
{
public string Name { get; set; }
}
public class PersonBusinessObject
{
private readonly MapperConfiguration _mapper;
public string Name { get; set; }
PersonBusinessObject()
{
_mapper = new MapperConfiguration(cfg =>
{
cfg.CreateMap<PersonDataObject,PersonBusinessObject>();
});
}
public static PersonBusinessObject MapFrom(PersonDataObject data)
{
return _mapper.Map<PersonBusinessObject>(data);
}
}
Then you can simply:
PersonDataObject data = new PersonDataObject();
PersonBusinessObject business = PersonBusinessObject.MapFrom(data);
I have a data context where I discover entities during run time. In my OnModelCreating method, I loop through all sub classes of EntityTypeConfiguration<> and add them to DbModelBuilder.
After enabling migrations for this context, if I call Add-Migration to create an initial migration, I get empty Up and Down methods.
When adding entities to DbContext during run time, is there way to use Add-Migration so that it automatically generates migration code or do I have to manually write required migration code?
It looks like I need to figure out a way to pass in required list of EntityTypeConfiguration when Add-Migration calls parameter less constructors.
Here is code
public class GenericSQLDBContext: DbContext
{
protected List<Type> _dataMapTypes;
public new IDbSet<TEntity> Set<TEntity>() where TEntity : class
{
return base.Set<TEntity>();
}
#region OnModelCreating
/// <summary>
/// This method loads all the *Map files in the assembly
/// </summary>
/// <param name="modelBuilder"></param>
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
if (_dataMapTypes != null && _dataMapTypes.Count > 0)
{
var typesToRegister = _dataMapTypes.Where(type => !String.IsNullOrEmpty(type.Namespace))
.Where(type => type.BaseType != null && type.BaseType.IsGenericType && type.BaseType.GetGenericTypeDefinition() == typeof(EntityTypeConfiguration<>));
foreach (var type in typesToRegister)
{
dynamic configurationInstance = Activator.CreateInstance(type);
modelBuilder.Configurations.Add(configurationInstance);
}
}
base.OnModelCreating(modelBuilder);
}
public GenericSQLDBContext(List<Type> listOfMaps) : base()
{
_dataMapTypes = listOfMaps;
}
}
The reason nothing is being generated is because you're likely not defining your DbSet<TEntity> properties...
example...
public class ExampleContext : DbContext
{
public ExampleContext() : base("ExampleDbConnection"){}
public DbSet<Foo> Foos { get; set; }
}
I haven't tested the code above just wrote it on the fly here... but it should work... See that property of type DbSet there, that's what you're likely missing... Can't really say more without seeing your code though
You can do auto migration at run-time
var config = new DbMigrationsConfiguration<MyContext> { AutomaticMigrationsEnabled = true };
var migrator = new DbMigrator(config);
migrator.Update();
For more details about dynamically building model at run-time in entity framework take a look at this link:
http://dynamicef.codeplex.com/
I am having issues right now with initializing the database using code first. I'm basically having problems on how to trigger my initializer if my database does not exist.
I've tried the following,
https://stackoverflow.com/a/28960111/639713
but my problem with this is I have to call this method on my initial page for it to trigger the create database. Even with that, the tables will not be created unless I manually do it. This will be an issue if I'm going to integrate this with an app that is already using sql server and already have about 50 tables on the dbcontext.
Anyway, here's my code:
DbContext
public class TestMigrationsDatabase : DbContext
{
public TestMigrationsDatabase()
: base(nameOrConnectionString: "TestMigrations.Domain.Entities.TestMigrationsDatabase")
{
//Database.SetInitializer<TestMigrationsDatabase>(null);
Database.SetInitializer<TestMigrationsDatabase>(new TestMigrations.Domain.TestMigrationsInitializer());
}
public DbSet<Base> Bases { get; set; }
public DbSet<Fighter> Fighters { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema("public"); // postgresql specific
base.OnModelCreating(modelBuilder);
}
public override int SaveChanges()
{
return base.SaveChanges();
}
}
Initializer:
public class TestMigrationsInitializer : CreateDatabaseIfNotExists<TestMigrationsDatabase>
{
protected override void Seed(TestMigrationsDatabase context)
{
this.CreateDatabase(ConfigurationManager.ConnectionStrings["TestMigrations.Domain.Entities.TestMigrationsDatabase"].ToString());
base.Seed(context);
LoadTestTables(context);
}
private void LoadTestTables(TestMigrationsDatabase context){
Base base = new Base();
base.Name = "Test 1 Base";
context.Bases.Add(base);
context.SaveChanges();
}
public void CreateDatabase(string connectionString)
{
var builder = new NpgsqlConnectionStringBuilder(connectionString);
var databaseName = "TestMigrations"; // REMEMBER ORIGINAL DB NAME
builder.Database = "postgres"; // TEMPORARILY USE POSTGRES DATABASE
// Create connection to database server
using (var connection = new NpgsqlConnection(builder.ConnectionString))
{
connection.Open();
// Create database
var createCommand = connection.CreateCommand();
createCommand.CommandText = string.Format(#"CREATE DATABASE ""{0}"" ENCODING = 'UTF8'", databaseName);
createCommand.ExecuteNonQuery();
connection.Close();
}
}
}
Controller
public class HomeController : Controller
{
public TestMigrationsDatabase _context = new TestMigrationsDatabase();
//
// GET: /Home/
public ActionResult Index()
{
TestMigrations.Domain.TestMigrationsInitializer initializer = new Domain.TestMigrationsInitializer();
initializer.CreateDatabase(ConfigurationManager.ConnectionStrings["TestMigrations.Domain.Entities.TestMigrationsDatabase"].ToString());
return View();
}
}
So with all that, my questions are:
1. Is putting that initializer on the first controller to trigger it correct? Or should I just instantiate the context so that the constructor will trigger teh initializer?
2. How do I properly create the tables after the database is created?
Thanks!!!
I'm using Entity Framework 6 and I have a class with a method that adds some records to the database:
interface IRecordsContext
{
DbSet<MyRecord> MyRecords { get; }
int SaveChanges();
}
class MyService
{
public MyService(IRecordsContext ctx)
{
_context = ctx;
}
private readonly IRecordsContext _context;
public void AddRecords(int count)
{
_context.MyRecords.AddRange(
from id in Enumerable.Range(1, count)
select new MyRecord { Value = id }
);
_context.SaveChanges();
}
}
Now I am using Moq library to create unit tests:
void AddRecords_ShouldAddThemToDatabase()
{
var contextMock = new Mock<IRecordsContext>();
// ...
}
How can I write a test that ensures that the Records collection now contains 10 extra records using mocks, without modifying any actual database?
I'm also eager to listen to opinions on whether this architecture is un-testable and how it should be refactored.
Have one variable that is set to .Count() of the DbSet before the method is run, and check that against the value of .Count() afterwards.