One Context class, two Model classes - c#

I'm learning MVC, and I understand how to create a single model class, and then a single context class, and then create a controller and a view based on the model/context combination, and commit the model to a database using code-first.
Now I want to bring a second Model (and therefore table) into the mix. I initially tried creating a second context class, and then a second model class, and then a controller and a view in the same way. As a result of this I found out that I could only commit one model to the database at a time. Whenever I enabled automatic migration on one model, and then ran the Update-Database command in the package manager, the other table disappeared from my database, and the add/remove/edit/view functionality for the other model broke.
Surely you must be able to use this techinque to manage more than one table? My question is, if I have two model classes like below:
[Table("TableOne")]
public class ModelOne
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string Foo { get; set; }
}
[Table("TableTwo")]
public class ModelTwo
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string Bar { get; set; }
}
What does my Context class need to look like if I want to work with both? My idea was to try this:
public class FooBar : DbContext
{
public FooBar()
: base("DefaultConnection")
{
}
public DbSet<ModelOne> TableOne { get; set; }
public DbSet<ModelTwo> TableTwo { get; set; }
}
Then in the Add Controller wizard, I could select MVC controller with read/write actions and views, using Entity Framework from the Template drop-down list, ModelOne from the Model class drop-down list, and FooBar from the Data context class drop-down list for the first model, and the same options only with ModelTwo in the Model class drop-down list for the second model and everything would work as expected, but whenever I try to create the first controller based on model one, I get this error:
There was an error generating FooBar. Try rebuilding your project.
I have tried rebuilding my project, and I get the same error, so obviously I am doing something greater wrong. What am I doing wrong and how do I need to amend my FooBar context class to get things to work, assuming it is my FooBar context class which is wrong?

Generally, you won't have a 1-to-1 between context classes and model classes. All your associated models all go into one context. I generally have one context per area in my app, for instance, but if you aren't using areas, you can still group models logically in a single context.
Where the problem comes in is in having multiple contexts. EF only likes to migrate one context. Without migrations, you can pretty easily get all the context classes to use the same DB by specifying it manually on the context class.
public class MyContext: DbContext
{
public MyContext()
: base("name=MyConnectionName")
{
}
}
If each context is defined like that, they will all use the same DB the named connection string points to.
As far as getting migrations to play along, Julie Lerman, the author of Programming Entity Framework: DbContext (and pretty much all the relevant EF books out there), has a solution utilizing a generic base class (edit: actually Julie attributes the pattern to Arthur Vickers of the EF team):
public class BaseContext<TContext> : DbContext
where TContext : DbContext
{
static BaseContext()
{
Database.SetInitializer<TContext>(null);
}
protected BaseContext()
: base("name=breakaway")
{
}
}
And, then you have each individual context inherit from BaseContext:
public class TripPlanningContext : BaseContext<TripPlanningContext>
public class SalesContext : BaseContext<SalesContext>
The relevant section starts on page 231 of the aforementioned book.
UPDATE
Actually I lied to you. The pattern Julie gives is simply to make it easier to have them all share the same DB. To get past the migrations for only one context issue, you have to cheat and essentially create one big context with everything in it that you then use only for database initialization/migration. For everything else in your code, you would use your other more specific contexts.

Related

C# Entity Framework Adding New Column to Entity that's from external library

On a project I am working on I am having some troubles trying to add properties to an entity from an external NuGet package.
The external team originally used the EntityFramework to create their database, and awhile back my team used it initially in order to create ours, now having two separate databases but initial creation used the common NuGet package.
On the external team's side, they haven't changed the table at at all, but on our side we've added new columns and properties to our database and now we need it within our DBContext. How do I map these new fields to an Entity so that I can access and set the properties. I hoped it was protected but since it is public I can't just overwrite the DbSet<Profile> Profile call.
External Package:
DataContext (Class that extends DBContext and has a public DbSet<Profile> Profile {get;set;})
Profile (Entity that is mapped to the "Profile" table in the database)
Since I can't modify the Profile class, how do I go about adding new columns that are there in the table?
My initial approach was to create:
DataContextExt (class that extends DataContext and added public DbSet<ProfileExt> ProfileExt {get;set;}
ProfileExt (Entity that extends Profile and has the additional fields that aren't part of the original
This seems to get me the furthest, but since ProfileExt extends Profile, I get an error when using it due to the "Discriminator" column since they are both the same entity technically.
I then tried to remove Profile by overriding the OnModelCreating() and map my ProfileExt to Profile but that failed as well, it didn't seem to change the model builder at all.
public class DataContextExt : DataContext
{
public DbSet<ProfileExt> ProfileExt { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Ignore<Profile>();
modelBuilder.Entity<ProfileExt>().ToTable("Profile");
Database.SetInitializer<ApplicationDbContext>(null);
}
}
Does anyone have any suggestions on what I should try next going forward?
EDIT:
Currently the project is design to access the information VIA a Stored Procedure and then I mapped that to my ProfileExt, but when it comes to saving it is designed to use
Entity = await DB.Set<TModel>().FindAsync(Key.Compile()(Model)).ConfigureAwait(false);
Model is instance of ProfileExt when it reaches this point
If I try to pass a ProfileExt through(without its own DbSet) as Profile it fails saying ProfileExt is not in the context, and if I do register it (with its own DbSet) it throws the Discriminator error since once is an instance of another.
From the sounds of things you are using a library and initial schema provided by some third party. You don't share code modifications with that team but you've gone and changed part of their schema in your copy of this Profile table.
Why not keep the two DbContexts completely separate rather than trying to inherit to override?
One option would be not to add columns to a table/entity that you do not have ownership of to extend. Move your custom columns to something like a MasterProfile table which shares a ProfileId as it's PK. From there you can declare a MasterProfile entity with a one-to-one relationship with Profile.
public class MasterProfile
{
[Key]
public int ProfileId { get; set; }
// add custom columns here...
public virtual Profile Profile { get; set; }
}
then configure relationship:
modelBuilder.Entity<MasterProfile>()
.HasRquired(x => x.Profile)
.WithOptional();
This way you can read your custom object in your DbContext along with Profile without a breaking change to the schema.
Another option you could explore is to define your own Profile entity definition for your DbContext while reusing the other entity declarations from the 3rd party.
For example: Given a 3rd party library which defines the following classes:
3rdParty.User
3rdParty.Profile
3rdParty.TableA
3rdParty.TableB
3rdParty.TableC
and they are accessed by a 3rdParty.DataContext
I can define a MyApp.DataContext that does not need to extend 3rdParty.DataContext. That DbContext can reference a Profiles collection that is declared as:
MyApp.Profile
which contains the full set properties from our Profile table. Provided you don't have to worry about references to 3rdParty.Profile you don't need to create custom entities for every table, you can reference 3rdParty.TableA etc. in your MyApp.DataContext.
I.e.
public class DataContext : DbContext
{
public DbSet<Profile> Profiles { get; set; }
public DbSet<3rdParty.TableA> TableAs { get; set; }
public DbSet<3rdParty.TableB> TableBs { get; set; }
public DbSet<3rdParty.TableC> TableCs { get; set; }
}
The catch would be that this will only work if the class you define is not referenced by many other entities. Every 3rdParty entity definition we include in our DbContext can no longer reference a 3rdParty.Profile since our DbContext cannot have two entities mapped to the same table.
For instance, if Profile references a User, that isn't a problem since MyApp.Profile can reference 3rdParty.User, however if 3rdParty.User has a reference back like:
public virtual ICollection<Profiles> { get; set; }
which will be pointing back to the Profile in the 3rdParty assembly, this is a deal breaker. We will need to recreate a MyApp.User as well. This could cascade if something like User needs to be re-declared and that class is referenced by the majority of other entities. (I.e. public virtual User CreatedBy { get; set; })
It may be an option worth exploring.

Entity Framework 6 throws exception for missing SQL table not required by DbContext

I have created a DbContext, similar to the one below
public class ProductsDB : DbContext
{
public virtual DbSet<Product> Products { get; set; }
//..other stuff..
}
Here's Product;
[Table("product")]
public class Product
{
[Column("Product_ID")]
public int ID { get; set; }
//other fields...
}
This all works fine until I create a class that inherits from "Product";
[Table("CentralProducts")]
public class CentralProduct : Product
{
//fields...
}
When I run integration tests I get an error that states that dbo.CentralProducts doesn't exist. It doesn't, and for the database that ProductsDB is connecting to I don't want it to.
I have tried the various inheritance options but they either require the addition of a table to my database, or the addition of a discriminator column to the Product table.
I had hoped that since my context doesn't consume CentralProducts it would not require it to exist. I was using table-per-type inheritance so there shouldn't be any need for something consuming dbo.Product to be able to access dbo.CentralProduct. Is there a way to configure EF6 to allow for this?
The reason I am trying to do this is that I have two databases, each with a common set of tables. I would like to create two DbContexts, one for each database, each containing the core entities but also including a few differences. In this particular case, the second DbContext has a Product table that includes an additional field, hence my attempt at inheritance to solve the problem.

Force Entity Framework to recognise a TPT Inheritance Structure

Given an example structure as below, Entity Framework doesn't recognise the base Entity inheritance, and thus doesn't map it as a TPT Inheritance between User and Entity.
// Where DbSet<User> Users { get; set; } is used
public class User : User<int> { }
public class User<TTest> : Entity {
public TTest Whatever { get; set; }
}
public abstract class Entity {
public int EntityId { get; set; }
}
I believe this is because EF will only look at the first level inheritance structure and never see the Entity as the base class, just that it has its properties.
So my question is, how can I force EF to recognise that Entity is indeed the base class? Just defining that User has a ForeignKey to Entity in the migration obviously isn't enough, as it still doesn't create that underlying row.
Bonus points: I've already noted that I can't go another level down (ie. Employee : User), but if you'd like to correct me on that I'll be forever in your debt also.
Update: Repro available here on github.com.
Update2: Gert Arnold's theory about not being able to map generic classes and thus breaking the chain unfortunately generated the same migration wherein User didn't fall through to Entity.
Update3: I can confirm that the stricken out "bonus" above does indeed work, when User maps correctly. The inheritance structure of Entity : User : Employee works when all 3 are tables, it is obviously not working when User can't even map back to Entity, which I believe now to be a bug in EF.
You need to add a DbSet for each type that you want EntityFramework to add a table for, like this:
public virtual DbSet<Entity> Entities { get; set; }
public virtual DbSet<User> Users { get; set; }

Adding ViewModel class adds codefirst model to database

I need a view-model in my ASP.NET MVC 5 project, but when I added one to the models folder, a new entity was added to the database and I was forced to add a migration and update my database. I do not want this to happen, as it is a view-model I am adding and not a model I need to persist back to the database. I want to scaffold some of the controller and views so I have added a primary key to the class. I did not add the newly created view-model to my DbContext class.
ViewModel:
public class RolesViewModel
{
public int RolesViewModelId { get; set; }
public string Role { get; set; }
}
Is there a way to create a view-model that doesn't automatically get added to the DbContext class, and therefore cause the data model to change?
Many thanks,
Jason.
Whether you call it a view model, an entity, etc. it's just semantics. Everything is just a class, and the context it's used in determines what you refer to it as. In the case of entities, that's adding a reference either explicitly or implicitly in your DbContext, and that's the only way you'll end up with something added to your database. I emphasized the "or implicitly* part because if any class that is referenced in the your DbContext, or any class connected to any class referenced there, also references your "view model", it will end up in your database. Entity Framework will automatically follow your class hierarchies and create tables for all relationships, even if you do not reference a particular class in those hierarchies directly in your DbContext.
In your case Scaffolding will add the following code below in your appContext Class
public DbSet<RolesViewModel> RolesViewModel { get; set; }
You can still use scaffolding if you wish, however remember to remove that entry and no table will be created by code first. It will keep your database clean.

Forcing EF 6 to create tables with model first

Currently I have a nice model, and I can generate a database based on that, but from what I can tell, the tables are never created (leading to all sorts of fun runtime errors).
My understanding is that there are three options for code first that would force EF to create the tables for me:
DropCreateDatabaseAlways
CreateDatabaseIfNotExists
DropCreateDatabaseIfModelChanges
How can I use these if I am doing things model first?
Additionally, is this an expected error, or when I selected generate database from model the first time is this supposed to happen automatically?
Edit: I tried calling
context.Database.Initialize(true);
context.Database.CreateIfNotExists();
and nothing changes.
also this is good toturial
tutorial
but if you made the model good the first time you access the dbContext the db should be created by the db strategy which you can set: Database.SetInitializer()
set initializer
in short after you create your model you need to create class that inherit from DbContext:
public class CompanyContext : DbContext
{
public CompanyContext() : base("CompanyDatabase") { }
public DbSet<Collaborator> Collaborators { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Manager> Managers { get; set; }
}
and then when you access this context the tables should be generated.
you can also seed the database with data you should inherit from the strategy you want to implement look at this link seeding database

Categories

Resources