Entity Framework Code First Base Class Not Mapped To DB - sql-server-2008-r2
I am using the Entity Framework Code First in C# and I have many entities that have the same columns used for tracking. The columns I am using are Active, IsDeleted, CreatedBy, ModifiedBy, DateCreated, and DateUpdated. It seems tedious to have to add these properties to every entity that I want to track. I would like to have a base class that my entities can inherit from such as the one below.
public abstract class TrackableEntity
{
public bool Active { get; set; }
public bool IsDeleted { get; set; }
public virtual User CreatedBy { get; set; }
public virtual User ModifiedBy { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateModified { get; set; }
}
Then I could inherit from this class in my entities to have these properties and when the database gets generated it would have these columns for each entity.
public class UserProfile : TrackableEntity, IValidatableObject
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string Phone { get; set; }
public bool IsValid { get { return this.Validate(null).Count() == 0; } }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (String.IsNullOrEmpty(FirstName))
yield return new ValidationResult("First name cannot be blank", new[] { "Username" });
if (String.IsNullOrEmpty(LastName))
yield return new ValidationResult("Last cannot be blank", new[] { "Password" });
//Finish Validation Rules
}
}
I really want to cut back on code duplication and use some type of method like this but I can't get this to work. I keep receiving the error below:
Unable to determine the principal end of an association between the types 'Namespace.Models.User' and 'Namespace.Models.User'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
I have been searching around for a few days now and can't find an answer for what I am wanting to do. I really don't want to create a separate table that everything links to. I have read about TPT, TPH, and TPC. TPC seemed pretty close to what I want but sharing PKs across pretty much all my tables is some I definitely don't want to do.
Here is an example of what I would like my tables to look like. These tables would be created by entities that inherit from the TrackableEntity.
[UserProfile]
[Id] [int] IDENTITY(1,1) NOT NULL
[FirstName] [varchar](50) NOT NULL
[LastName] [varchar](50) NOT NULL
[Email] [varchar](255) NOT NULL
[Phone] [varchar](20) NOT NULL
[CreatedBy] [int] NOT NULL
[ModifiedBy] [int] NOT NULL
[DateCreated] [datetime] NOT NULL
[DateModified] [datetime] NOT NULL
[Active] [bit] NOT NULL
[IsDeleted] [bit] NOT NULL
[CaseType]
[Id] [int] IDENTITY(1,1) NOT NULL
[Name] [varchar](50) NOT NULL
[CreatedBy] [int] NOT NULL
[ModifiedBy] [int] NOT NULL
[DateCreated] [datetime] NOT NULL
[DateModified] [datetime] NOT NULL
[Active] [bit] NOT NULL
[IsDeleted] [bit] NOT NULL
Most likely you are having this exception because you have your User class derived from TrackableEntity as well:
public class User : TrackableEntity
The consequence is that the User entity now contains the two inherited properties
public virtual User CreatedBy { get; set; }
public virtual User ModifiedBy { get; set; }
and Entity Framework by convention will assume a relationship between the two properties, i.e. one relationship that has those two navigation properties as its two ends. Because the navigation properties are references and not collections EF infers a one-to-one relationship. Because in the model isn't specified which of the two navigation properties has the related foreign key for the relationship EF cannot determine what's the principal and what's the dependent of the relationship - which causes the exception.
Now, this problem could be solved - as the exception tells - by defining principal and dependent explicitly. But in your model the convention - namely to assume a one-to-one relationship - is incorrect. You actually need two relationships, one from CreatedBy (with its own foreign key) and one from ModifiedBy (with another foreign key). Both relationships are one-to-many (because a User can be the creator or modifier of many other users) and don't have a navigation collection at the other end of the relationship. You must supply a mapping with Fluent API to override the convention and define those two one-to-many relationships:
public class UnicornsContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<UserProfile> UserProfiles { get; set; }
public DbSet<CaseType> CaseTypes { get; set; }
// ... etc.
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<User>()
.HasOptional(u => u.CreatedBy)
.WithMany()
.Map(m => m.MapKey("CreatedBy")); // FK column name in DB table
modelBuilder.Entity<User>()
.HasOptional(u => u.ModifiedBy)
.WithMany()
.Map(m => m.MapKey("ModifiedBy")); // FK column name in DB table
}
}
Note, that I have used HasOptional instead of HasRequired (which means that the FK in the database will be nullable), because - at least with autoincremented Ids - you can't create the very first user with a CreatedBy or ModifiedBy being set - it could only refer to itself but there isn't any valid FK value to be used because the very first PK hasn't been created yet.
(You could also consider to avoid the relationship and referential constraint of CreatedBy or ModifiedBy altogether and store only the user's name as creator and modifier instead, since for auditing and tracking purposes it might be enough to save a unique person's identity. Do you really need to navigate from every entity to its creator and modifier? Even if you need it in exceptional cases, you could still manually join to the Users table. And would it be a problem if the user gets deleted from the Users table as long as the name is still stored in CreatedBy or ModifiedBy?)
You don't have to deal with any inheritance mapping as long as you don't introduce a DbSet for the abstract base class...
public DbSet<TrackableEntity> TrackableEntities { get; set; } // NO!
...or a mapping in Fluent API for that class...
modelBuilder.Entity<TrackableEntity>()... // NO!
...or use the class as a navigation property in any other class:
public class SomeEntity
{
//...
public virtual TrackableEntity Something { get; set; } // NO!
public virtual ICollection<TrackableEntity> Somethings { get; set; } // NO!
}
With any of these EF will infer TrackableEntity as an entity and introduce an inheritance mapping between model and database tables (TPH by default). Otherwise TrackableEntity is just a base class and every property within the base class will be considered as if it were a property in the derived entity and mapped as such to a database table.
Related
Immitating an ADO.NET design in Entity Framework Core
I've been learning ADO.NET, and then EF Core. My assignment was to create a database application in C#, first in ADO.NET and then convert that app so it would use Entity Framework instead. This was my design in ADO.NET, and it seemed to work fine. CREATE TABLE Categories ( Id INT IDENTITY, Name NVARCHAR(50) NOT NULL, CONSTRAINT PK_Categories PRIMARY KEY (Id) ) CREATE TABLE CategoryCategories ( ParentCategoryId INT, ChildCategoryId INT, CONSTRAINT PK_CategoryCategories PRIMARY KEY (ParentCategoryId, ChildCategoryId), CONSTRAINT FK_CategoryCategories_ParentCategoryId FOREIGN KEY (ParentCategoryId) REFERENCES Categories (Id), CONSTRAINT FK_CategoryCategories_ChildCategoryId FOREIGN KEY (ChildCategoryId) REFERENCES Categories (Id) ON DELETE CASCADE ) CREATE TABLE Products ( Id INT IDENTITY, ArticleNumber NVARCHAR(50) NOT NULL UNIQUE, Name NVARCHAR(50) NOT NULL, Description NVARCHAR(500) NOT NULL, Price DECIMAL(18,2) NOT NULL, CONSTRAINT PK_Products PRIMARY KEY (Id) ); CREATE TABLE Products_Categories ( ProductId INT, CategoryId INT NOT NULL, CONSTRAINT PK_Products_Categories PRIMARY KEY (ProductId, CategoryId), CONSTRAINT FK_ProdCats_Prods FOREIGN KEY (ProductId) REFERENCES Products (Id), CONSTRAINT FK_ProdCats_Cats FOREIGN KEY (CategoryId) REFERENCES Categories (Id) ON DELETE CASCADE ); The connection between Products and Categories - no problem, just an ICollection property in each of the two responding classes. Entity Framework immediately created a CategoryProducts junction table. When creating a connection between an entity and itself, that's where I ran into a wall. I tried simply copying all the model building code for CategoryProducts generated by the migrations into an OnModelCreating method for CategoryCategories, and inserting the right names of the columns and the tables. Didn't work, got complaints that CategoryCategories was in shadow mode. I've vacuum-cleaned the internet trying to find a solution, but I can't find any clear instructions. Is it really so much harder to do this in Entity Framework than in regular SQL, or ADO.NET? I thought Entity Framework was supposed to make things easier. Any suggestions? If you need more information, please let me know. Edit Thought I might add the classes, for clarity's sake. class Category { public int Id { get; protected set; } [Required] public string Name { get; protected set; } public ICollection<Product> Products { get; protected set; } // public ICollection<Category> ChildCategories { get; protected set; } public Category(string name) { Name = name; Products = new List<Product>(); // ChildCategories = new List<Category>(); } } class CategoryCategory { // EFC forced me to have an Id. Could not run Add-migr Initial without it public int Id { get; protected set; } [Required] public int ParentCategoryId { get; protected set; } [Required] public int ChildCategoryId { get; protected set; } public CategoryCategory(int parentCategoryId, int childCategoryId) { ParentCategoryId = parentCategoryId; ChildCategoryId = childCategoryId; } } class Product { public int Id { get; protected set; } [Required] public string ArticleNumber { get; protected set; } [Required] // EFC forcing me to have unprotected set // Without it, I can not update articles :( public string Name { get; set; } [Required] public string Description { get; set; } [Required] public decimal Price { get; set; } public ICollection<Category> Categories { get; protected set; } public Product(string articleNumber, string name, string description, decimal price) { ArticleNumber = articleNumber; Name = name; Description = description; Price = price; Categories = new List<Category>(); } public Product(int id, string articleNumber, string name, string description, decimal price) : this(articleNumber, name, description, price) { Id = id; } } With these classes, EF says The entity type 'CategoryCategories' is in shadow state. A valid model requires all entity types to have corresponding CLR type. If I uncomment the ICollection ChildCategories part in Category, then EF creates the third column, CategoryId in CategoryCategories, which is very undesirable in my opinion. Why should I want the third ID referring to Categories, when I already have two of them?
The connection between Products and Categories - no problem, just an ICollection property in each of the two responding classes. Entity Framework immediately created a CategoryProducts junction table. If this works, then you are using EF Core 5.0+ which added support for many-to-many relationship with implicit join entity, as can be seen, creating such relationship is quite easy. But your CategoryCategories represents exactly the same type of relationship, with the only difference that both related ends are one and the same entity. So how you create such relationship between two entities? You do that by adding 2(!) collection navigation properties in each related entity: class Product { public ICollection<Category> Categories { get; set; } } class Category { public ICollection<Product> Products { get; set; } } (side note: the type of the property setter (public, internal, protected, private) doesn't matter for EF Core). Which leads us to the answer of question how do you define such relationship between the same entity? Well, exactly the same - by adding 2(!) collection navigation properties in each related entity, which in this case is one and the same: class Category { public ICollection<Category> ParentCategories { get; set; } public ICollection<Category> ChildCategories { get; set; } } And that's it. No need of explicit CategoryCategory entity. EF Core creates automatically something like this migrationBuilder.CreateTable( name: "CategoryCategory", columns: table => new { ChildCategoriesId = table.Column<int>(type: "int", nullable: false), ParentCategoriesId = table.Column<int>(type: "int", nullable: false) }, constraints: table => { table.PrimaryKey("PK_CategoryCategory", x => new { x.ChildCategoriesId, x.ParentCategoriesId }); table.ForeignKey( name: "FK_CategoryCategory_Categories_ChildCategoriesId", column: x => x.ChildCategoriesId, principalTable: "Categories", principalColumn: "Id", onDelete: ReferentialAction.Cascade); table.ForeignKey( name: "FK_CategoryCategory_Categories_ParentCategoriesId", column: x => x.ParentCategoriesId, principalSchema: "SO14", principalTable: "Categories", principalColumn: "Id", onDelete: ReferentialAction.Restrict); }); If you don't like the generated table/column names, they are all configurable through fluent API. You can even create and map explicit join entity if you like, and still use the benefits of the so called "skip navigations". But the two collection navigation properties are the bare minimum, and are the thing which makes the EF Core approach much easier.
Fluent mapping for EF when foreign key not suffixed with ID
How to implement fluent mapping for the below scenario, I tried but it ends in vain. I have two table Product and State, Product have column name State which hold StateCode like "WA", "NJ" etc which of string type. So i need to populate the State column into StateCode and the related State object into State property of the product entity. Below is the classes i am using. I don't want to change the columns of table public class Product { public int ID { get; set; } public State State { get; set; } public string StateCode { get; set; } public string Description { get; set; } } public class State { public int Id { get; set; } public string Code{get;set;} public string Description{get;set;} } I tried the below mapping for Product this.Property(t => t.StateCode).HasColumnName("State"); HasRequired(t => t.State).WithMany().HasForeignKey(t => t.StateCode);
No, currently it's not possible to have a Foreign Key column that refers to non PK in entity framework. Check this feature suggestion. If you really want to have that feature, you need to have custom Seed that execute. alter table Products add constraint FK_Products_States foreign key(State) references States(Code) But you will not be able to populate State object. Putting public State State { get; set; } property will automatically create a Foreign Key column State_ID that refers to States::ID. Otherwise you need to change the StateCode to be StateId (integer) that refers to State::Id.
The type of ForeignKey and PrimaryKey of referenced table must be the same. So you need to set State class Id property type to string. In EF you can only use foreig keys pointing to primary keys.
EF6 extending an TPH hierarchy (mixing TPH and TPT)
I have a table used to store several inherited entities in TPH configuration. That works well and there are no issues over that. The issue I'm facing is that I need to extend some of those entities with additional fields and want those new fields stored in its own table using TPT. To put some context I will give you an example: The TPH stores a root PERIOD class and several inherited ones like QUARTER, MONTH, WEEK, etc, using a discriminator field. So now I need to create a special QUARTER with some additional fields and want to store those additional field in its own table. Is that possible in EF? I'm using EF 6.1 and haven't found a working sample or explanation on how to accomplish this specific scenario. Thanks in advance, Andrés.
public abstract class Period { public int Id { get; set; } public string DisplayName { get; set; } } public class Month : Period { public byte MonthValue { get; set; } } public class Quarter : Period { public byte QuarterValue { get; set; } } public class SpecialQuarter : Quarter { public int SpecialQuarterValue { get; set; } } public class TestContext : DbContext { public DbSet<Period> Periods { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<Period>().ToTable("Period"); // TPH modelBuilder.Entity<Month>().Map(p => p.Requires("PeriodType").HasValue("M")); modelBuilder.Entity<Quarter>().Map(p => p.Requires("PeriodType").HasValue("Q")); //TPT modelBuilder.Entity<SpecialQuarter>().Map(p => p.ToTable("SpecialQuarter")); } } This context maps to these tables. CREATE TABLE [dbo].[Period] ( [Id] [int] NOT NULL IDENTITY, [DisplayName] [nvarchar](max), [MonthValue] [tinyint], [QuarterValue] [tinyint], [PeriodType] [nvarchar](128) NOT NULL, CONSTRAINT [PK_dbo.Period] PRIMARY KEY ([Id]) ) CREATE TABLE [dbo].[SpecialQuarter] ( [Id] [int] NOT NULL, [SpecialQuarterValue] [int] NOT NULL, CONSTRAINT [PK_dbo.SpecialQuarter] PRIMARY KEY ([Id]) )
One to one OPTIONAL relationship
Traditional EF questions starts with: My models are public class Ingredient { public int IngredientID { get; set; } public virtual RequestedIngredient RequestedIngredient { get; set; } // other stuff } public class RequestedIngredient { [Key] string BlahBlahBlah { get; set; } public int? IngredientID { get; set; } public virtual Ingredient Ingredient { get; set; } } Somewhere in dbContext... modelBuilder.Entity<Ingredient>() .HasOptional<RequestedIngredient>(e => e.RequestedIngredient) .WithOptionalPrincipal(e => e.Ingredient) .Map(e => e.MapKey("IngredientID")) .WillCascadeOnDelete(false); But I get Schema specified is not valid. Errors: (195,6) : error 0019: Each property name in a type must be unique. Property name 'IngredientID' was already defined. If I remove IngredientID from RequestedIngredient, the db will be created just as I want to. But I have no access to IngredientID. How can I set this up to have access to foreign key?
This is not one-to-one relationship. In one-to-one relationship the foreign key must be a primary key. Which is not the case in this example. This is one-to-many, but I assumed that my app will take care of making sure there's only one association. EF can deal with that using Independent Association. It will create foreign key, hidden from your POCO class. One can specify the name of the column using MapKey as I did. However, because I also created a property called IngredientID, just as the column used with MapKey, the EF has a problem as two properties are mapped to the same column. So things like that are possible in EF, but you can't use foreign key anymore.
EF Code First: Primary Key same as Foreign Key
I have two classes public class Product { public Guid Id { get; set; } public string ProductDetails { get; set; } } public class SpecialProductDetails { public Guid Product_Id { get; set; } // PK and FK to Product class public string SpecialName { get; set; } public string SpecialDescription { get; set; } public virtual Product Product { get; set; } } SpecialProductDetails is mapped 1-1 with Product class and is optional. It shares the same PrimaryKey and ForeignKey. In Fluent API i am mapping this relationship like this (inside SpecialProductDetails) public SpecialProductDetails() { HasKey(p => p.Product_Id); HasRequired(p => p.Product).WithMany().HasForeignKey(p => p.Product_Id).WillCascadeDelete(true); } This gives me this error when trying to generate the Database \tSystem.Data.Entity.Edm.EdmAssociationEnd: : Multiplicity is not valid in Role 'SpecialProductDetails_Product_Source' in relationship 'SpecialProductDetails_Product_Source'. Because the Dependent Role refers to the key properties, the upper bound of the multiplicity of the Dependent Role must be '1'. How can i have a column set as PK and FK on EF Code First?
I'm quite sure you have already solved that, but I hit the same problem and the solution I found was: public SpecialProductDetails() { HasKey(p => p.Product_Id); HasRequired(p => p.Product).WithOptional(); } "it worth noting that when we are mapping a one-to-one association with fluent API, we don't need to specify the foreign key as we would do when mapping a one-to-many association with HasForeignKey method. Since EF only supports one-to-one associations on primary keys, it will automatically create the relationship in the database on the primary keys." after http://weblogs.asp.net/manavi/associations-in-ef-4-1-code-first-part-3-shared-primary-key-associations