Force Entity Framework to recognise a TPT Inheritance Structure - c#

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

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.

Adding attributes to EF6 generated entity classes [duplicate]

This question already has answers here:
Using DataAnnotations with Entity Framework
(2 answers)
Closed 6 years ago.
I am using EF6 in a database first context. In this case I am using the entity classes in my MVC web project and would like to annotate the entity's fields with various validation and display attributes. However, when I refresh the entity classes by doing an update from database in my edmx designer, the classes regenerate and my attributes are lost.
What is the best approach to getting round this?
When working with generated entity classes in a database first Entity Framework project, it is often necessary to apply attributes to the class’s fields. This is especially the case if you are foregoing the use of ViewModels and using your entities directly in an MVC web project.
Of course if you were to apply validation or display name attributes to the fields directly, the next time the data model is generated due to an upgrade from database action, these would all be overwritten.
Luckily the classes generated by Entity Framework are marked as partial. This means that we can create a second class that augments the first. Effectively the two classes are seen as one. For example:
[MetadataType(typeof(AnimalMetaData))]
public partial class Animal
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int NumberOfLegs { get; set; } etc..
public class AnimalMetaData
{
[MaxLength(200)]
[Required]
public string Name { get; set; }
[MaxLength(1000)]
public string Description { get; set; } etc...
But of course we have a problem here. We have put the attribute to associate the metadata class on the entity class and this will be overwritten on an update from the database. So how do we get round this? Simple! We create a third ‘dummy’ class called Animal that sits alongside the metadata class and is also partial:
[MetadataType(typeof(AnimalMetaData))]
public partial class Animal {}
We annotate this class with our metadata class, so in effect we have the three classes acting as one; the entity itself, the dummy class to link the entity with the metadata definition class.
For the sake of keeping things tidy, you could do worse than to place the metadata and dummy classes together in a separate folder adjacent to the entities generated by Entity Framework.

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

EF4 exception with relationship

I have two entities and there are their POCO:
public class DocumentColumn
{
public virtual long Id { get; set; }
public virtual string Name { get; set; }
public virtual long? DocumentTypeId { get; set; }
}
public class DocumentType {
public virtual long Id { get; set; }
public virtual string Name { get; set; }
}
There is a relation between those two entities. In the db the relation called:FK_T_DOCUMENT_COLUMN_T_DOCUMENT_TYPE.
When I do:
DocumentColumns.Where(x => x.DocumentTypeId == documentTypeId).ToList();
I get the exception:
{"Metadata information for the relationship 'MyModel.FK_T_DOCUMENT_COLUMN_T_DOCUMENT_TYPE' could not be retrieved. If mapping attributes are used, make sure that the EdmRelationshipAttribute for the relationship has been defined in the assembly. When using convention-based mapping, metadata information for relationships between detached entities cannot be determined.\r\nParameter name: relationshipName"}
I tryed to remove the relationship and the DocumentColumn table and reload them but the code still throws the exception.
Whet does this exception means and how can I solve it?
EDIT:
The exception happens also If I do DocumentColumns.ToList();
(Presuming you are talking about Code First ....)
There is no information in either class to let CF know that there is a relationship between them. It doesn't matter that the database has the info. Entity Framework needs to have a clue about the relationship. You provide only a property with an integer. CF cannot infer a relationship. You must have something in one class or another that provides type or another. This is not a database. It's a data model. Very different things.
But that's not all. I'm guessing that this is a one to many relationship. You could either put a List property into the Document class or a Document property in the DocumentColumn class. If you only do the latter, CF and EF will NOT know about the 1:. It will presume a 1:1 (that is if you leave DocumentId integer in there, otherwise it will presume a 1:0..1). However, I think you could get away with this and then just configure the multiplicity (1:) in fluent API.
UPDATE...reading your question again, I think you are using an EDMX and designer not code first. What are you using to create your POCO classes? Are you doing code gen from the EDMX or just writing the classes. I still think the lack of a navigation property in at least ONE of the types might be the cause of the problem. The ERROR message does not suggest that...I'm only coming to this conclusion by looking at the classes and inferring my understanding of how EF works with the metadata. I could be barking up the wrong tree. FWIW, I have asked the team if they are familiar with this exception and can provide some idea of what pattern would create it. It's pretty bizarre. :)
It seems odd to me that you are using EF with a defined relationship and you are not using the related property. Can you not do:
DocumentColumns.Where(x=>x.DocumentType.Id == documentTypeId).ToList();
This is what I would expect to see in this instance.

Categories

Resources