EF6 extending an TPH hierarchy (mixing TPH and TPT) - entity-framework

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])
)

Related

Entity Framework - Code First - Update won't change Foreign Key

I just can't find any code that changes the foreign key. How do I tell the context that the foreign key has changed so that it updates the database? I have been attempting to get this to work for 2 months now:
These are the models:
namespace MyApp.WebApi.Models
{
public class Project
{
public int ProjectId { get; set; }
public string Description { get; set; }
public string Name { get; set; }
// Foreign Key - Project Status
public virtual ProjectStatus ProjectStatus { get; set; }
}
public class ProjectStatus
{
public int ProjectStatusId { get; set; }
public string Name { get; set; }
}
public class HerculesWebApiContext : DbContext
{
public HerculesWebApiContext() : base("name=HerculesWebApiContext") { }
public DbSet<Project> Projects { get; set; }
public DbSet<ProjectStatus> ProjectStatuses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder) { }
}
}
This is the controller for the Web API:
namespace MyApp.WebApi.Controllers
{
public class ProjectController : ApiController
{
private HerculesWebApiContext db = new HerculesWebApiContext();
public void PutProject(int id, [FromBody]Project project)
{
if (!ModelState.IsValid) throw new HttpResponseException(HttpStatusCode.BadRequest);
if (id != project.ProjectId) throw new HttpResponseException(HttpStatusCode.BadRequest);
// I need some code here to tell EF that the FK has changed
db.Entry(project).State = EntityState.Modified;
// Try catch around this
db.SaveChanges();
}
The above code will not update the Project Status Foreign Key even if it has changed in the JSON object that is passed to the function. The object is returned in the same format that the Web API provided it but with the Project Status object updated:
[{"ProjectStatus":{"ProjectStatusId":2,"Name":"Started"},"ProjectId":3,"Description":"test description","Name":"test project"}]
I have tried:
db.Projects.Attach(db.Projects.Single(c => c.ProjectId == project.ProjectId));
((IObjectContextAdapter)db).ObjectContext
.ApplyCurrentValues("Projects", project);
I have also tried:
db.Entry(project.ProjectStatus).CurrentValues.SetValues(new ProjectStatus { ProjectStatusId = project.ProjectStatus.ProjectStatusId });
I have also tried this:
var per = new ProjectStatus { ProjectStatusId = project.ProjectStatus.ProjectStatusId }; // create the stub
db.ProjectStatuses.Attach(per);
db.Entry(project.ProjectStatus).CurrentValues.SetValues(per);
These are the table T-SQL files that are automatically created from my models:
Projects Table:
CREATE TABLE [dbo].[Projects] (
[ProjectId] INT IDENTITY (1, 1) NOT NULL,
[Description] NVARCHAR (MAX) NULL,
[Name] NVARCHAR (MAX) NULL,
[ProjectStatus_ProjectStatusId] INT NOT NULL,
CONSTRAINT [PK_dbo.Projects] PRIMARY KEY CLUSTERED ([ProjectId] ASC),
CONSTRAINT [FK_dbo.Projects_dbo.ProjectStatus_ProjectStatusId] FOREIGN KEY ([ProjectStatus_ProjectStatusId]) REFERENCES [dbo].[ProjectStatus] ([ProjectStatusId]) ON DELETE CASCADE
Project Statuses Table
CREATE TABLE [dbo].[ProjectStatus] (
[ProjectStatusId] INT IDENTITY (1, 1) NOT NULL,
[Name] NVARCHAR (MAX) NULL,
CONSTRAINT [PK_dbo.ProjectStatus] PRIMARY KEY CLUSTERED ([ProjectStatusId] ASC)
);
You need to do two things. First explicitly state your primary keys for each class.
Add the [Key] attribute to
Project class, above the line public int ProjectId { get; set; }
ProjectStatus class, above the line public int ProjectStatusId { get; set; }
Secondly add the property for your foreign key to your Project class. Add this line:
public int ProjectStatusId { get; set; }

Entity Framework Code First : how to annotate a foreign key for a "Default" value?

I have 2 classes: Client and Survey.
Each Client can have many surveys - but only one default survey.
I have defined the classes like this:
public class Client
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string ClientName { get; set; }
public Nullable<int> DefaultSurveyID { get; set; }
[ForeignKey("DefaultSurveyID")]
public virtual Survey DefaultSurvey { get; set; }
public virtual ICollection<Survey> Surveys { get; set; }
}
public class Survey
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string SurveyName { get; set; }
[Required]
public int ClientID { get; set; }
[ForeignKey("ClientID")]
public virtual Client Client { get; set; }
}
This creates the Client table as I expect:
[dbo].[Clients]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[ClientName] [nvarchar](max) NULL,
[DefaultSurveyID] [int] NULL
)
But the Survey table has an extra foreign key:
[dbo].[Surveys]
(
[ID] [int] IDENTITY(1,1) NOT NULL,
[SurveyName] [nvarchar](max) NULL,
[ClientID] [int] NOT NULL,
[Client_ID] [int] NULL
)
Why is Code First generating this relationship and how to I tell it not to?
The problem is that when you have multiple relationships between two entities, EF Code First isn't able to find out which navigation properties match up, unless, you tell it how, here is the code:
public class Client
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public string ClientName { get; set; }
/****Change Nullable<int> by int?, looks better****/
public int? DefaultSurveyID { get; set; }
/****You need to add this attribute****/
[InverseProperty("ID")]
[ForeignKey("DefaultSurveyID")]
public virtual Survey DefaultSurvey { get; set; }
public virtual ICollection<Survey> Surveys { get; set; }
}
With your previous version, EF was creating that extra relationship because it didn't know that the DefaultSurvey property was referencing the ID of the Survey class, but you can let it know that, adding the attribute InverseProperty whose parameter is the name of the property in Survey you need DefaultSurvey to match with.
You can do it using code-first, but not being a code first expert I cheated :-)
1) I created the tables and relationships (as above without the extra Client_ID) in the database using SMS
2) I used Reverse Engineer Code First to create the required classes and mappings
3) I dropped the database and recreated it using context.Database.Create()
Original table defs:
CREATE TABLE [dbo].[Client](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
[DefaultSurveyId] [int] NULL,
CONSTRAINT [PK_dbo.Client] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)
)
CREATE TABLE [dbo].[Survey](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [nvarchar](50) NULL,
[ClientId] [int] NULL,
CONSTRAINT [PK_dbo.Survey] PRIMARY KEY NONCLUSTERED
(
[Id] ASC
)
)
Plus foreign keys
ALTER TABLE [dbo].[Survey] WITH CHECK
ADD CONSTRAINT [FK_dbo.Survey_dbo.Client_ClientId] FOREIGN KEY([ClientId])
REFERENCES [dbo].[Client] ([Id])
ALTER TABLE [dbo].[Client] WITH CHECK
ADD CONSTRAINT [FK_dbo.Client_dbo.Survey_DefaultSurveyId]
FOREIGN KEY([DefaultSurveyId]) REFERENCES [dbo].[Survey] ([Id])
Code generated by reverse engineering:
public partial class Client
{
public Client()
{
this.Surveys = new List<Survey>();
}
public int Id { get; set; }
public string Name { get; set; }
public int? DefaultSurveyId { get; set; }
public virtual Survey DefaultSurvey { get; set; }
public virtual ICollection<Survey> Surveys { get; set; }
}
public partial class Survey
{
public Survey()
{
this.Clients = new List<Client>();
}
public int Id { get; set; }
public string Name { get; set; }
public int? ClientId { get; set; }
public virtual ICollection<Client> Clients { get; set; }
public virtual Client Client { get; set; }
}
public class ClientMap : EntityTypeConfiguration<Client>
{
#region Constructors and Destructors
public ClientMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Properties
this.Property(t => t.Name).HasMaxLength(50);
// Table & Column Mappings
this.ToTable("Client");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.Name).HasColumnName("Name");
this.Property(t => t.DefaultSurveyId).HasColumnName("DefaultSurveyId");
// Relationships
this.HasOptional(t => t.DefaultSurvey)
.WithMany(t => t.Clients).HasForeignKey(d => d.DefaultSurveyId);
}
#endregion
}
public class SurveyMap : EntityTypeConfiguration<Survey>
{
#region Constructors and Destructors
public SurveyMap()
{
// Primary Key
this.HasKey(t => t.Id);
// Properties
this.Property(t => t.Name).HasMaxLength(50);
// Table & Column Mappings
this.ToTable("Survey");
this.Property(t => t.Id).HasColumnName("Id");
this.Property(t => t.Name).HasColumnName("Name");
this.Property(t => t.ClientId).HasColumnName("ClientId");
// Relationships
this.HasOptional(t => t.Client)
.WithMany(t => t.Surveys).HasForeignKey(d => d.ClientId);
}
#endregion
}
Entity Framework does exactly what it's told to do. What you've told it is that there is both a one-to-many and a one-to-one relationship between Clients and Surveys. It generated both FKs in the Survey table in order to map both of the relationships that you've requested. It has no idea that you're trying to connect the two relationships together, nor do I think does it have the ability to deal with that.
As an alternative you might want to consider adding a IsDefaultSurvey field on the Survey object so that you can query for the default survey through the Surveys collection that you have on the Client object. You could even go one step further and put it in as a NotMapped property on the Client object so that you could still use Client.DefaultSurvey to get the correct survey, and not have to change any of your other code, as follows:
[NotMapped]
public Survey DefaultSurvey
{
get { return this.Surveys.First(s => s.IsDefaultSurvey); }
}
Please notice that adding the code below you will fix the issue.
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext() : base("DefaultConnection")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Client>()
.HasOptional(x => x.DefaultSurvey)
.WithMany(x => x.Surveys);
.HasForeignKey(p => p.DefaultSurveyID);
{
}

Entity Framework Code First Base Class Not Mapped To DB

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.

T4 relationship generation

suppose I have the following tables and relations:
CREATE TABLE [dbo].[Customers] (
[CustomerID] [int] IDENTITY(1,1) NOT NULL,
[Description] [nvarchar](255) NOT NULL
CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED ([CustomerID] ASC)
CREATE TABLE [dbo].[Orders](
[OrderID] [int] IDENTITY(1,1) NOT NULL,
[CustomerID] [int] NOT NULL,
...
CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED ([OrderID] ASC)
ALTER TABLE [dbo].[Orders] WITH CHECK ADD CONSTRAINT [FK_Orders_Customers]
FOREIGN KEY([CustomerID]) REFERENCES [dbo].[Customers] ([CustomerID])
I am using some T4 templates to generate simple POCO classes with a property for each database column. This is very easy and really funny. In fact, the code I have is able to generate a single class for each table like in the following sample
public class Customer {
public int CustomerID { get; set; }
public string Description { get; set; }
}
I would like now to also create code that maps relationship between tables (1-N, 1-1) so to obtain, for example a class like this one
public class Customer {
public int CustomerID { get; set; }
public string Description { get; set; }
public IList<Order> Orders { get; set; }
}
Unfortunately my code does not work. Here it is:
foreach ( NavigationProperty navProperty in entity.NavigationProperties.Where( p => p.DeclaringType == entity ) ) {
string elementType = ((EntityType)( (RefType)navProperty.ToEndMember.TypeUsage.EdmType ).ElementType).Name;
string relationshipName = navProperty.ToEndMember.DeclaringType.FullName;
string targetRoleName = navProperty.ToEndMember.Name;
if ( navProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ) {
<#=Accessibility.ForProperty( navProperty ) #> IList<<#=navProperty.Name#>> <#=navProperty.Name#> { get; set; }
}
}
Unfortunately this generate code like this one:
public class Customer {
public int CustomerID { get; set; }
public string Description { get; set; }
public IList<Orders> Orders { get; set; } // NOTE THE PLURAL IN THE TYPE NAME!!!
}
which is the entity set name not the entity name. What I have to do to solve this problem?
NOTE: I am using EF for the .NET 3.5 version. Also I am including the EF.Utility.CS.ttinclude available in Visual Studio 10.
Found the solution. This is the correct code:
foreach ( NavigationProperty navProperty in entity.NavigationProperties.Where( p => p.DeclaringType == entity ) ) {
string elementType = ((EntityType)( (RefType)navProperty.ToEndMember.TypeUsage.EdmType ).ElementType).Name;
if ( navProperty.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many ) {
<#=Accessibility.ForProperty( navProperty ) #> IList<<#= elementType #>> <#=navProperty.Name#> { get; set; }
}
}
Instead of using
navProperty.name
inside of your list declaration
Can you use
navProperty.ToEndMember
Through that property I think you should be able to find the real non-pluralized class name

Code First and Parent-Child references

I'm using Code First CTP 5. I have a fairly simple setup between a parent table and child tables
Create table testA (
id int not null identity(1,1),
stuff varchar(200),
primary key (id)
);
go
create table testB (
id int not null
foreign key references testA(id),
morestuff varchar(200),
primary key (id)
);
go
To refer to these table using Code First, we have the following construct:
namespace Test.Models
{
public class TestEntities : DbContext
{
public DbSet<testa> testa { get; set; }
public DbSet<testb> testb { get; set; }
protected override void OnModelCreating(System.Data.Entity.ModelConfiguration.ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<testa>().ToTable("testa");
modelBuilder.Entity<testa>()
.Property(p => p.id)
.HasDatabaseGenerationOption(DatabaseGenerationOption.Identity);
modelBuilder.Entity<testb>().ToTable("testb");
}
}
public class testa
{
public int id { get; set; }
public String stuff { get; set; }
public virtual testb testb { get; set; }
}
public class testb
{
public int id { get; set; }
public string morestuff { get; set; }
public virtual testa testa { get; set; }
}
}
When I try to add a record to testa, I get the error "Cannot insert explicit value for identity column in table 'testA' when IDENTITY_INSERT is set to OFF."
Ok. Strike 1 to Code First for not recognizing that Id is an identity column. We can fix this, so we tell CodeFirst that testa.id is an identity:
modelBuilder.Entity<testa>()
.Property(p => p.id)
.HasDatabaseGenerationOption(DatabaseGenerationOption.Identity);
That done we run it again and get another error: "A dependent property in a ReferentialConstraint is mapped to a store-generated column. Column: 'id'". So - what's wrong with this picture?
What am I doing wrong and how do I fix it???
In a 1:1 association, Code First recognize one of the entities as principal and the other one as dependent. Then it makes the principal PK as identity and you need to take care of a valid unique PK when inserting into the dependent table. In your case it picks testb as the principal but it looks like that you want testa to be the principal end in this association. This could be achieved by using fluent API and basically giving hint to Code First about which one is principal and which one is dependent:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<testb>()
.HasRequired(b => b.testa)
.WithOptional(a => a.testb);
}
For more information, take a look at this article:
Associations in EF Code First CTP5: Part 2 – Shared Primary Key Associations