EF stores subset of records into another table - entity-framework

I have a header-child tables with the child having different types but stored in the same table (TPH).
On top of this, user can snapshot a copy of a header and its children records and I would like to store the snapped copy into a different table since these snapshot records would be less frequent to view/modify.
To achieve this, I am mixing TPC to my existing TPH.
The new structure is as follows:
public class Header
{
private IList<Child> _childs = new List<Child>();
private IList<ChildSnapshot> _childSnapshots = new List<ChildSnapshot>();
[Key]
public int Id { get; set; }
public string Name { get; set; }
public IList<Child> Childs { get { return _childs; } }
public IList<ChildSnapshot> ChildSnapshots { get { return _childSnapshots; } }
}
public abstract class ChildBase
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("Header")]
public int HeaderId { get; set; }
[ForeignKey("HeaderId")]
public virtual Header Header { get; set; }
}
public abstract class Child : ChildBase
{
}
public class Child1 : Child
{
}
public class Child2 : Child
{
}
public abstract class ChildSnapshot : ChildBase
{
}
public class ChildSnapshot1 : ChildSnapshot
{
}
public class ChildSnapshot2 : ChildSnapshot
{
}
And the database context:
public class TestContext : DbContext
{
public DbSet<Header> Headers { get; set; }
public DbSet<Child> Childs { get; set; }
public DbSet<ChildSnapshot> ChildSnapshots { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Header>().Map(m => m.ToTable("Header"));
modelBuilder.Entity<Child>()
.Map<Child>(m =>
{
m.MapInheritedProperties();
m.ToTable("Child");
})
.Map<Child1>(m =>
{
m.Requires("Discriminator").HasValue("Child1");
})
.Map<Child2>(m =>
{
m.Requires("Discriminator").HasValue("Child2");
});
modelBuilder.Entity<ChildSnapshot>()
.Map<ChildSnapshot>(m =>
{
m.MapInheritedProperties();
m.ToTable("ChildSnapshot");
})
.Map<ChildSnapshot1>(m =>
{
m.Requires("Discriminator").HasValue("Child1");
})
.Map<ChildSnapshot2>(m =>
{
m.Requires("Discriminator").HasValue("Child2");
});
}
}
It works perfectly after many trials and errors. However, I've got to create 2 list properties in Header class. Is it possible to have only 1 list property of ChildBase type? I got the following error when I do so.
The type 'Child' cannot be mapped as defined because it maps inherited
properties from types that use entity splitting or another form of
inheritance. Either choose a different inheritance mapping strategy so
as to not map inherited properties, or change all types in the
hierarchy to map inherited properties and to not use splitting.
Why is the behaviour determined by the type of the container list? Can't EF infer from the type of the object in the list instead?
FYI I am using EF 4.3.

Related

EF6 Use Existing Db Column as Discriminator

I am using Entity Framework 6 Code First for my project.
Entities have Inheritance so I am following TPH(Table per Hierarchy).
I read following Article and many others.
None of them explain a way in which I can use an existing DB Column mapped to a property in Base Entity as Discriminator.
Based on the sample below I get following Exception
One or more validation errors were detected during model generation:
TaskType: Name: Each property name in a type must be unique. Property name 'TaskType' is already defined.
I think EF's auto generated Discriminator and my Entities Mapping is Conflicting.
Is there a possible way to instruct EF to not auto generate column and use Entity mapped Column.
If not, is there any explanation of this can not be avoided.
Peace.
I have Entities in following format
public enum TaskType
{
Random = 0,
Polished = 1,
Dropping = 2
}
public interface ITask
{
int Id { get; set; }
string Name { get; set; }
TaskType typeofTask { get; set; }
}
public abstract class BaseTask : ITask
{
public BaseTask(string name, TaskType type)
{
this.Name = Name;
this.typeofTask = type;
}
public int Id { get; set; }
public string Name { get; set; }
public TaskType typeofTask { get; set; }
}
public class RandomTask : BaseTask
{
public RandomTask() : base("My Random", TaskType.Random)
{
}
public int Owner { get; set; }
}
public class PolishedTask : BaseTask
{
public PolishedTask() : base("My Polished", TaskType.Polished)
{
}
}
public class DBContextTest : DbContext
{
public DBContextTest(string connection) : base(connection)
{
}
public DbSet<BaseTask> Task { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<BaseTask>().Map<RandomTask>(m => m.Requires("TaskType").HasValue(1))
.Map<PolishedTask>(m => m.Requires("TaskType").HasValue(1));
modelBuilder.Entity<BaseTask>().Property(p => p.typeofTask).HasColumnName("TaskType");
}
}
class Program
{
static void Main(string[] args)
{
try
{
DBContextTest dataContext = new DBContextTest("Server = (localdb)\\mssqllocaldb;DataBase = LOC2;Trusted_Connection = True;");
RandomTask randomtask = new RandomTask();
PolishedTask polishedTask = new PolishedTask();
dataContext.Task.Add(randomtask);
dataContext.Task.Add(polishedTask);
dataContext.SaveChanges();
}
catch (System.Exception ex)
{
}
}
}
Remove TaskType from your entity and let EF manage that as part of the TPH mapping. To differentiate types if you're dealing with a base-class collection, use .OfType<PolishedTask>() rather than .Where(x => x.TaskType == TaskType.Polished) EF should take care of the rest. If you do want it on the entity, create a non-mapped property in your sub-classes.
I.e.
public abstract class BaseTask
{
[NotMapped]
public abstract TaskType TaskType { get; }
}
public class PolishedTask
{
[NotMapped]
public override TaskType TaskType => TaskType.Polished
// or
//public override TaskType TaskType
//{
// get { return TaskType.Polished; }
//}
}

Entity framework replaces delete+insert with an update. How to turn it off

I want to remove a row in database and insert it again with the same Id, It sounds ridiculous, but here is the scenario:
The domain classes are as follows:
public class SomeClass
{
public int SomeClassId { get; set; }
public string Name { get; set; }
public virtual Behavior Behavior { get; set; }
}
public abstract class Behavior
{
public int BehaviorId { get; set; }
}
public class BehaviorA : Behavior
{
public string BehaviorASpecific { get; set; }
}
public class BehaviorB : Behavior
{
public string BehaviorBSpecific { get; set; }
}
The entity context is
public class TestContext : DbContext
{
public DbSet<SomeClass> SomeClasses { get; set; }
public DbSet<Behavior> Behaviors { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Entity<SomeClass>()
.HasOptional(s => s.Behavior)
.WithRequired()
.WillCascadeOnDelete(true);
}
}
Now this code can be executed to demonstrate the point
(described with comments in the code below)
using(TestContext db = new TestContext())
{
var someClass = new SomeClass() { Name = "A" };
someClass.Behavior = new BehaviorA() { BehaviorASpecific = "Behavior A" };
db.SomeClasses.Add(someClass);
// Here I have two classes with the state of added which make sense
var modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// They save with no problem
db.SaveChanges();
// Now I want to change the behavior and it causes entity to try to remove the behavior and add it again
someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" };
// Here it can be seen that we have a behavior A with the state of deleted and
// behavior B with the state of added
modifiedEntities = db.ChangeTracker.Entries()
.Where(entity => entity.State != System.Data.Entity.EntityState.Unchanged).ToList();
// But in reality when entity sends the query to the database it replaces the
// remove and insert with an update query (this can be seen in the SQL Profiler)
// which causes the discrimenator to remain the same where it should change.
db.SaveChanges();
}
How to change this entity behavior so that delete and insert happens instead of the update?
A possible solution is to make the changes in 2 different steps: before someClass.Behavior = new BehaviorB() { BehaviorBSpecific = "Behavior B" }; insert
someClass.Behaviour = null;
db.SaveChanges();
The behaviour is related to the database model. BehaviourA and B in EF are related to the same EntityRecordInfo and has the same EntitySet (Behaviors).
You have the same behaviour also if you create 2 different DbSets on the context because the DB model remains the same.
EDIT
Another way to achieve a similar result of 1-1 relationship is using ComplexType. They works also with inheritance.
Here an example
public class TestContext : DbContext
{
public TestContext(DbConnection connection) : base(connection, true) { }
public DbSet<Friend> Friends { get; set; }
public DbSet<LessThanFriend> LessThanFriends { get; set; }
}
public class Friend
{
public Friend()
{Address = new FullAddress();}
public int Id { get; set; }
public string Name { get; set; }
public FullAddress Address { get; set; }
}
public class LessThanFriend
{
public LessThanFriend()
{Address = new CityAddress();}
public int Id { get; set; }
public string Name { get; set; }
public CityAddress Address { get; set; }
}
[ComplexType]
public class CityAddress
{
public string Cap { get; set; }
public string City { get; set; }
}
[ComplexType]
public class FullAddress : CityAddress
{
public string Street { get; set; }
}

How to have an entity inherit from another base entity and map to db using TPC with EF 4.2?

Say I have an entity model aggregate for Activity, like so:
public class Activity : Entity
{
public int PersonId { get; set; }
public virtual Person Person { get; set; }
public int Number { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public virtual ICollection<ActivityTag> Tags { get; set; }
}
public class ActivityTag : Entity
{
public int ActivityPersonId { get; set; }
public int ActivityNumber { get; set; }
public virtual Activity Activity { get; set; }
public int Number { get; set; }
public string Text { get; set; }
}
Forget about the relation between Activity and Person, but note the 1..* relation between Activity and ActivityTag. The fluent mapping looks more or less like this:
public class ActivityOrm : EntityTypeConfiguration<Activity>
{
public ActivityOrm()
{
ToTable("Activity", "Activities");
HasKey(p => new { p.PersonId, p.Number });
HasRequired(d => d.Person)
.WithMany()
.HasForeignKey(d => d.PersonId)
.WillCascadeOnDelete(true);
HasMany(p => p.Tags)
.WithRequired(d => d.Activity)
.HasForeignKey(d => new { d.ActivityPersonId, d.ActivityNumber })
.WillCascadeOnDelete(true);
Property(p => p.Content).HasColumnType("ntext");
}
}
public class ActivityTagOrm : EntityTypeConfiguration<ActivityTag>
{
public ActivityTagOrm()
{
ToTable("ActivityTag", "Activities");
HasKey(p => new { p.ActivityPersonId, p.ActivityNumber, p.Number });
Property(p => p.Text).IsRequired().HasMaxLength(500);
}
}
Given this, I want to introduce a new collection property to the Activity entity:
public ICollection<DraftedTag> DraftedTags { get; set; }
The DraftedTag entity should have the same exact properties and primary key as ActivityTag. The only thing that should be different is the table it is mapped to. I tried creating a class that derived from ActivityTag, like so:
public class DraftedTag : ActivityTag
{
}
public class DraftedTagOrm : EntityTypeConfiguration<DraftedTag>
{
public DraftedTagOrm()
{
Map(m =>
{
m.MapInheritedProperties();
m.ToTable("DraftedTag", "Activities");
});
HasKey(p => new { p.ActivityPersonId, p.ActivityNumber, p.Number });
}
}
The DraftedTagOrm has been added to the modelBuilder.Configurations collection, but without even adding the foreign key association to Activity, I get the following exception:
The property 'ActivityPersonId' is not a declared property on type
'DraftedTag'. Verify that the property has not been explicitly
excluded from the model by using the Ignore method or
NotMappedAttribute data annotation. Make sure that it is a valid
primitive property.
When I completely duplicate the code from the ActivityTag class and the ActivityTagOrm constructor into the respective DraftTag class / configuration constructor, then it works as expected -- I get two different tables with identical schemas, but different names. However each time I want to make a change to the ActivityTag class, I must make a corresponding change in the DraftTag class.
Is it possible to make this code DRYer by having DraftTag extend ActivityTag? If so, what would the EntityTypeConfiguration look like for DraftTag?

EF 4.1 Mapping Inheritence on a Many-to-Many relationship

Confusing Situation
I have a situation where I have 2 entities where 1 inherits from the other, that need to map to 2 separate tables, but code use should be around the base of the 2 entities.
Details
public class Team
{
public virtual int Id { get; set; }
public virtual ICollection<Employee> Members { get; set; }
}
public class Employee
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Team> Teams { get; set; }
}
public class EmployeeInfo : Employee
{
public virtual int Id { get; set; }
public virtual decimal Amount { get; set; }
}
We have an existing database schema where Employee and EmployeeInfo are separate tables with a FK between EmployeeInfo_Id and Employee_Id.
In our system "managers" will be adding Employee's to the system, with a set of private information (more properties than listed above) like pay, and add them to a Team. Other areas of the system will be using the Team or Employee objects for various other things. We would like to have to code super simple if the mapping can be done.
When a manager creates a new employee we would like the code to look something like this:
public void Foo(string name, decimal pay)
{
// create the employee
var employee = new EmployeeInfo();
employee.Name = name;
employee.Pay = pay;
// add him/her to the team
_team.Employees.Add(employee); // the idea being that consumers of the Team entity would not get the separate employee info properties
// save the context
_context.SaveChanges();
}
The end result would be that the EmployeeInfo properties entered into the EmployeeInfo table and the base Employee data is entered into the Employee table and added to the Team via the association table TeamEmployees.
So far I'm trying the current mappings, and I get an invalid column named "Discriminator." When just adding an employee to a team.
public class TeamConfiguration : EntityTypeConfiguration<Team>
{
public TeamConfiguration()
{
ToTable("Team");
HasKey(t => t.Id);
HasMany(t => t.Members).WithMany(m => m.Teams)
.Map(m =>
{
m.MapLeftKey("Team_Id");
m.MapRightKey("Employee_Id");
m.ToTable("TeamEmployees");
});
}
}
public class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguration()
{
ToTable("Employee");
ToTable("EmployeeInfo");
HasKey(t => t.Id);
Property(p => p.Name);
HasMany(m => m.Teams)
.WithMany(t => t.Members)
.Map(m =>
{
m.MapLeftKey("Employee_Id");
m.MapRightKey("Team_Id");
m.ToTable("TeamEmployees");
});
}
}
Also, if I take the many-to-many between team and employee out of the mix I get a FK exception on Employee_Id to EmployeeInfo_Id.
Thanks, JR.
Discriminator is a column that's being added to your table when you use Table Per Hierarchy approach.
I think what you're looking for is "Table per Type (TPT)". Decorate your EmployeeInfo class as follows:
[Table("EmployeeInfo")]
public class EmployeeInfo : Employee
Or add below to your OnModelCreating event:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
...
modelBuilder.Entity<EmployeeInfo>().ToTable("EmployeeInfo");
...
}
Or, create the following class and use it like modelBuilder.Configurations.Add(new EmployeeInfoConfiguration()); in OnModelCreating method:
public class EmployeeInfoConfiguration : EntityTypeConfiguration<EmployeeInfo>
{
public EmployeeInfoConfiguration()
{
ToTable("EmployeeInfo");
}
}
This will cause EF to create EmployeeInfo table with necessary constraints.
Also, it's good to initialize your collections in your objects' constructors to prevent null exception. For example in Team class:
public Team()
{
this.Employees = new HashSet<Employee>();
}
I copied your code exactly, and changed the following parts:
public class Team
{
public Team()
{
this.Members = new HashSet<Employee>();
}
public virtual int Id { get; set; }
public virtual ICollection<Employee> Members { get; set; }
}
public class Employee
{
public Employee()
{
this.Teams = new HashSet<Team>();
}
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual ICollection<Team> Teams { get; set; }
}
[Table("EmployeeInfo")]
public class EmployeeInfo : Employee
{
public virtual int Id { get; set; }
public virtual decimal Amount { get; set; }
}
In the DbContext, no changes:
public partial class TestEntities : DbContext
{
public DbSet<Employee> Employees { get; set; }
public DbSet<EmployeeInfo> Employee_Info { get; set; }
public DbSet<Team> Teams { get; set; }
}
and your working Foo method:
public static void Foo(string name, decimal pay)
{
var _team = new Team();
var context = new TestEntities();
context.Teams.Add(_team);
// create the employee
var employee = new EmployeeInfo();
employee.Name = name;
employee.Amount = pay;
context.Employees.Add(employee);
context.SaveChanges();
// add him/her to the team
_team.Members.Add(employee);
// save the context
context.SaveChanges();
}
Finally, remove ToTable("EmployeeInfo"); part from EmployeeConfiguration since you have mentioned this correctly in your mode creating event.
For more info about Table Per Type approach, check out this great article.

Mapping entities' fields to the same column

Using Model-first and Table-per-heirachy, I can create two classes that inherit from the same base class, and map a column in each of the two derived classes to the same table column so that I can 're-use' columns.
If I try that with Code-first, I get the following error: "Each property name in a type must be unique. Property name 'XXX' was already defined."
I assume that this is a bug in code-first?
Here is some example code:
public class Parent
{
public Guid Id { get; set; }
}
public class ChildA : Parent
{
public Int32 ChildAProperty { get; set; }
}
public class ChildB : Parent
{
public Int32 ChildBProperty { get; set; }
}
public class TestContext : DbContext
{
public DbSet<Parent> Entities { get { return this.Set<Parent>(); } }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var childAConfig = modelBuilder.Entity<ChildA>();
childAConfig.Property(p => p.ChildAProperty).HasColumnName("Property");
var childBConfig = modelBuilder.Entity<ChildB>();
childBConfig.Property(p => p.ChildBProperty).HasColumnName("Property");
}
}