Is it possible to inherit a custom DBContext and then modify the entities of the inherited context has without modifying the base tables with discriminator columns?
public class BaseDbContext: DbContext {
public IDbSet<BaseClass> BaseClasses { get; set; }
}
public class BaseClass {
public int Id { get; set; }
etc...
}
public class NewDbContext: BaseDbContext {
public IDbSet<NewBaseClass> NewBaseClasses { get; set; }
}
public class NewBaseClass: BaseClass {
public string SomePropertyName { get; set; }
}
I have a project that is setup for a base database structure use the BaseDbContext. If I created a new database with the same schema and added a column to the BaseClasses table called SomePropertyName. I want to create a new DbContext that derives from the BaseDbContext, and then add the extra property to the new class, NewBaseClass.
Any guidance would be greatly appreciated.
Related
I want to use Table Per Concrete Class as the inheritance strategy for my XAF Code First EF project.
I use the wizard to create the project and then paste in classes from the following
namespace Solution12.Module.BusinessObjects {
public abstract class BaseBO // abstract class helpful in getting TPC
{
[Key]
public int Id { get; set; }
public string Description { get; set; }
}
[NavigationItem("People")]
[Table("People")] // explicit table name is helpful in preventing TPH
public class Person : BaseBO
{
public string PersonName { get; set; }
}
[NavigationItem("Organisation")]
[Table("Organisations")]
public class Organisation : BaseBO
{
public string OrganisationName { get; set; }
}
public class Solution12DbContext : DbContext {
...
public DbSet<Organisation> Organisations{ get; set; }
public DbSet<Person> People { get; set; }
//public DbSet<BaseBO> baseBOs { get; set; } // having this will cause TPT instead of TPC
}
}
This all works as I want, to create the database structure.
However I cant see the abstract class in the model designer at design time.
I can see the abstract class and it's views in the model designer at run time.
How can I get the model designer to show the abstract class BaseBO at design time?
This is a significant issue for us because run-time customizations are stored in the database and hence not part of our source control.
A ticket for this problem can also be found at Dev Express Support here however this is a more concise statement of what we now understand to be the problem.
It seems that if we pop the following into each concrete class then we get the desired behavior
[NotMapped]
public BaseBO BaseBo {
get
{
return (BaseBO)this;
}
}
I've created an abstract class with some base properties:
public abstract class BaseModel
{
public BaseWishModel()
{
}
[Key]
public int Id { get; set; }
public virtual string Title { get; set; }
public bool IsPublished { get; set; }
public bool IsSpam { get; set; }
}
My item class:
public class PrivateItem : BaseModel
{
[NotMapped]
public string PurposesIds { get; set; }
}
My OnModelCreating method:
modelBuilder.Entity<BaseModel>()
.Map<PrivateItem>(r => r.Requires("Discriminator").HasValue((int)Enums.Type.Private))
.ToTable("Items");
When I save the data it's generates next sql:
INSERT [dbo].[Items]([Title], [IsPublished], [ShortDescription1], [ShortDescription2], [Discriminator])
I don't know why it's generates ShortDescription1 and ShortDescription1
As, according to your comment, you have other classes inheriting from BaseModel, and with no other configuration from you, EF uses TPH by default.
Basically this leads to a single table for all the classes hierarchy.
As all classes of the hierarchy are persisted in the same table when an insert, for one class, is done, all columns (of the hierarchy) are populated. The non used by the class columns are populated by null or default value.
This bring the ShortDescription1 and ShortDescription2 in your insert query.
is there any way to share a table in database with multiple context of EF ?
I want to define many models (code first) and bind theme to a single table in a database like this:
public class Context1 : DbContext
{
[Table("Post")]
public DbSet<Post1> Posts { get; set; }
}
public class Context2 : DbContext
{
[Table("Post")]
public DbSet<Post2> Posts { get; set; }
}
public class Context3 : DbContext
{
[Table("Post")]
public DbSet<Post3> Posts { get; set; }
}
all post models are inherited from a basePost for example.
I have an existing model with multiple levels of inheritence with several of the intermediate classes being abstract. The model looks fine, but when trying to create the database I get the error:
Problem in mapping fragments starting at lines 13, 20:Two entities with different keys are mapped to the same row. Ensure these two mapping fragments do not map two groups of entities with different keys to the same group of rows.
Here's the simplest code I could write to reproduce the error. If I make Pet concrete, the problem goes away. What I can I do to allow multiple abstract classes in my hierarchy?
public abstract class Animal
{
public int Id { get; set; }
}
public abstract class Pet : Animal
{
public string Name { get; set; }
}
public class Fish : Pet
{
public bool IsFreshwater { get; set; }
}
public class Dog : Pet
{
public bool IsNeutered { get; set; }
}
public class Person : Animal
{
public Pet MyPet { get; set; }
}
public class PersonContext : DbContext
{
public DbSet<Person> People { get; set; }
}
[TestFixture]
public class AnimalTests
{
[Test]
public void CanCreateDatabase()
{
Database.SetInitializer(new DropCreateDatabaseAlways<PersonContext>());
using (var context = new PersonContext())
{
Assert.AreEqual(0, context.People.Count());//fails here
}
}
}
Update. Here's a KDiff snap of the differences in the generated .edmx file. The file on the left is my original code that fails and on the right is what is generated when I include a DbSet<Pet> in my DbContext.
You need to add Pets to your context:
// Table-per-class (TPC)
public class PersonContext : DbContext
{
public PersonContext()
{
}
public DbSet<Person> People { get; set; }
public DbSet<Pet> Pets { get; set; }
}
Edit: I just saw your comment where you stated you wanted table by hierarchy inheritance. Your problem, then, is that your DbSet is not typed properly - you want a single DbSet using the base type:
// Table-per-hierarchy (TPH)
public class PersonContext : DbContext
{
public PersonContext()
{
}
public DbSet<Animal> Animals { get; set; }
}
From what I understand on several posts the TPT architecure, with EF, does not create the necessary ON DELETE CASCADE when using a shared primary key.... It was also said that the EF context will handle the proper order of deletion of the sub-classed tables (however I do get an error that it breaks the constraint and that I can fix it with adding the ON DELETE CASCADE on the sub-class table)...
more background info...
I have a Section class, which has a number, title, and a list of pages. The page is designed using a super class which holds basic page properties. I have about 10+ sub-classes of the page class. The Section class holds an ICollection of these pages. The DB is created properly with the exception of no ON DELETE CASCADE on the sub-classed tables.
My code will create the entities and adds to the DB fine. However, if I try to delete a section (or all sections) it fails todelete due to the FK constraint on my sub-class page table...
public abstract BaseContent
{
... common properties which are Ignored in the DB ...
}
public class Course : BaseContent
{
public int Id {get;set;}
public string Name {get;set;}
public string Descripiton {get;set;}
public virtual ICollection<Chapter> Chapters{get;set;}
...
}
public class Chapter : BaseContent
{
public int Id {get;set;}
public int Number {get;set;}
public string Title {get;set;}
public virtual Course MyCourse{get;set;}
public virtual ICollection<Section> Sections{get;set;}
...
}
public class Section : BaseContent
{
public int Id {get;set;}
public int Number {get;set;}
public string Title {get;set;}
public virtual Chapter MyChapter {get;set;}
public virtual ICollection<BasePage> Pages {get;set;}
...
}
public abstract class BasePage : BaseContent, IComparable
{
public int Id { get; set; }
public string Title { get; set; }
public string PageImageRef { get; set; }
public ePageImageLocation ImageLocationOnPage { get; set; }
public int PageNumber { get; set; }
public virtual Section MySection { get; set; }
...
}
public class ChapterPage : BasePage
{
public virtual int ChapterNumber { get; set; }
public virtual string ChapterTitle { get; set; }
public virtual string AudioRef { get; set; }
}
public class SectionPage : BasePage
{
public virtual int SectionNumber { get; set; }
public virtual string SectionTitle { get; set; }
public virtual string SectionIntroduction { get; set; }
}
... plus about 8 other BasePage sub-classes...
public class MyContext: DbContext
{
...
public DbSet<Course> Courses { get; set; }
public DbSet<Chapter> Chapters { get; set; }
public DbSet<Section> Sections { get; set; }
public DbSet<BasePage> Pages { get; set; }
...
}
.. Fluent API ... (note Schema is defined to "" for SqlServer, for Oracle its the schema name)
private EntityTypeConfiguration<T> configureTablePerType<T>(string tableName) where T : BaseContent
{
var config = new EntityTypeConfiguration<T>();
config.ToTable(tableName, Schema);
// This adds the appropriate Ignore calls on config for the base class BaseContent
DataAccessUtilityClass.IgnoreAllBaseContentProperties<T>(config);
return config;
}
public virtual EntityTypeConfiguration<BasePage> ConfigurePageContent()
{
var config = configureTablePerType<BasePage>("PageContent");
config.HasKey(pg => pg.Id);
config.HasRequired(pg => pg.Title);
config.HasOptional(pg => pg.PageImageRef);
config.Ignore(pg => pg.ImageLocationOnPage);
return config;
}
public virtual EntityTypeConfiguration<ChapterPage> ConfigureChapterPage()
{
var config = configureTablePerType<ChapterPage>("ChapterPage");
config.HasOptional(pg => pg.AudioRef);
config.Ignore(pg => pg.ChapterNumber);
config.Ignore(pg => pg.ChapterTitle);
return config;
}
public virtual EntityTypeConfiguration<SectionPage> ConfigureSectionPage()
{
var config = configureTablePerType<SectionPage>("SectionPage");
config.HasOptional(pg => pg.AudioRef);
config.Ignore(pg => pg.SectionNumber);
config.Ignore(pg => pg.SectionTitle);
return config;
}
... other code to model other tables...
So the app is able to populate content and the relationships are properly set up. However, when I try to delete the course, I get the error that the delete failed due to the constraint on the ChapterPage to PageContent table..
Here is the code which deletes the Course (actually I delete all courses)...
using (MyContext ctx = new MyContext())
{
ctx.Courses.ToList().ForEach(crs => ctx.Courses.Remove(crs));
AttachLookupEntities(ctx);
ctx.SaveChanges();
}
If I add the 'ON DELETE CASCADE' in the ChapterPage and SectionPage table for its shared primary with PageContent, the delete goes through.
In summary,
The only solution that I have seen is to manually alter the constraints to add the ON DELETE CASCADE for all of my sub-class page tables. I can implement the change, as I have code which generates the DB script for the EF tables I need (a small subset of our whole DB) since we will not use EF to create or instantiate the DB (since it does not properly support migrations as yet...).
I sincerely hope that I have miscoded something, or forgot some setting in the model builder logic. Because if not, the EF designers have defined an architecure (TPT design approach) which cannot be used in any real world situation without a hack workaround. It's a half finished solution. Do not get me wrong, I like the work that has been done, and like most MSFT solutions its works for 70% of most basic application usages. It just is not ready for more complex situations.
I was trying to keep the DB design all within the EF fluent API and self-contained. It's about 98% there for me, just would be nice if they finished the job, maybe in the next release. At least it saves me all the CRUD operations.
Ciao!
Jim Shaw
I have reproduced the problem with a little bit simpler example:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Entity;
namespace EFTPT
{
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<BasePage> Pages { get; set; }
}
public abstract class BasePage
{
public int Id { get; set; }
public string Name { get; set; }
public Parent Parent { get; set; }
}
public class DerivedPage : BasePage
{
public string DerivedName { get; set; }
}
public class MyContext : DbContext
{
public DbSet<Parent> Parents { get; set; }
public DbSet<BasePage> BasePages { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Parent>()
.HasMany(p => p.Pages)
.WithRequired(p => p.Parent); // creates casc. delete in DB
modelBuilder.Entity<BasePage>()
.ToTable("BasePages");
modelBuilder.Entity<DerivedPage>()
.ToTable("DerivedPages");
}
}
class Program
{
static void Main(string[] args)
{
using (var ctx = new MyContext())
{
var parent = new Parent { Pages = new List<BasePage>() };
var derivedPage = new DerivedPage();
parent.Pages.Add(derivedPage);
ctx.Parents.Add(parent);
ctx.SaveChanges();
}
using (var ctx = new MyContext())
{
var parent = ctx.Parents.FirstOrDefault();
ctx.Parents.Remove(parent);
ctx.SaveChanges(); // exception here
}
}
}
}
This gives the same exception that you had too. Only solutions seem to be:
Either setup cascading delete for the TPT constraint in the DB manually, as you already tested (or put an appropriate SQL command into the Seed method).
Or load the entites which are involved in the TPT inheritance into memory. In my example code:
var parent = ctx.Parents.Include(p => p.Pages).FirstOrDefault();
When the entities are loaded into the context, EF creates actually two DELETE statements - one for the base table and one for the derived table. In your case, this is a terrible solution because you had to load a much more complex object graph before you can get the TPT entities.
Even more problematic is if Parent has an ICollection<DerivedPage> (and the inverse Parent property is in DerivedPage then):
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<DerivedPage> Pages { get; set; }
}
public abstract class BasePage
{
public int Id { get; set; }
public string Name { get; set; }
}
public class DerivedPage : BasePage
{
public string DerivedName { get; set; }
public Parent Parent { get; set; }
}
The example code wouldn't throw an exception but instead delete the row from the derived table but not from the base table, leaving a phantom row which cannot represent an entity anymore because BasePage is abstract. This problem is not solvable by a cascading delete but you were actually forced to load the collection into the context before you can delete the parent to avoid such a nonsense in the database.
A similar question and analysis was here: http://social.msdn.microsoft.com/Forums/en-US/adodotnetentityframework/thread/3c27d761-4d0a-4704-85f3-8566fa37d14e/