I have 2 subclasses inheriting from the same base class. They all map back to the same table. If field1 in the db table is null and field2 is not null, it's one subclass. If field1 is not null and field 2 is null, it's the other subclass.
I keep getting an error. The actual message is: "Invalid column name 'Discriminator'." It actually says Discriminator... I didn't throw that in as a generic term.
Here is a sample of my code:
DatabaseTableA
TableAId ( PK , int )
FooId ( FK , int , null )
BarId ( FK , int , null )
Prop1 ( int )
Prop2 ( int )
Prop3 ( int )
public abstract class BaseClass
{
public int Prop1{ get; set; }
public int Prop2{ get; set; }
public int Prop3{ get; set; }
}
public class Foo : BaseClass
{
public int FooId{get;set;}
}
public class Bar : BaseClass
{
public int BarId{get;set;}
}
internal class BaseClassMap : EntityTypeConfiguration<BaseClass>
{
public BaseClassMap()
{
ToTable("DatabaseTableA");
HasKey( e => e.TableAId);
Map<Foo>( m => m.Requires("BarId").IsDBNull());
Map<Bar>( m => m.Requires("FooId").IsDBNull());
}
}
How do I map this correctly?
Entity Framework will assume that any class that inherits from a POCO class that is mapped to a table on the database requires a Discriminator column, even if the derived class will not be saved to the DB.
See the solution here EF Code First "Invalid column name 'Discriminator'" but no inheritance
Related
I have the following classes generated from an edmx model:
public partial class A
{
public int Id { get; set; }
public virtual B B { get; set; }
}
public partial class B
{
public int Id { get; set; }
public virtual A A { get; set; }
}
The existing db doesn't use the EF default which expects A.Id to be the primary key of table B:
CREATE TABLE [dbo].[B] (
[Id] INT IDENTITY (1, 1) NOT NULL,
PRIMARY KEY CLUSTERED ([Id] ASC)
);
CREATE TABLE [dbo].[A] (
[Id] INT IDENTITY (1, 1) NOT NULL,
[BId] INT NULL,
CONSTRAINT [fk] FOREIGN KEY ([BId]) REFERENCES [dbo].[B] ([Id])
);
With an edmx model, I can explicitly configure the multiplicity of each end, but I haven't found how to get the equivalent model using the fluent-api. When I do something like the following and generate a new db, the foreign key gets placed in table A instead of table B.
modelBuilder.Entity<A>().HasOptional(a => a.B).WithRequired(b => b.A);
I'm guessing I need to use a convention, but so far I've been unable to get the desired output.
UPDATE:
The closest solution I've found so far is to use the following which generates the correct SQL in the db:
modelBuilder.Entity<A>()
.HasOptional(a => a.B)
.WithOptionalDependent(b => b.A)
.Map(c => c.MapKey("BId"));
However, it's conceptually modeled as a 0..1:0..1 relationship and I haven't found how to set a CASCADE delete rule that deletes B when A is deleted.
I wasn't able to find a direct solution, but using the following code seems to meet my requirements of preserving the existing schema and creating a conceptual model that has the same multiplicities & delete behaviors as my original edmx model.
I'd still be interested in any solutions that don't require updating the conceptual model during the post-processing IStoreModelConvention.
{
var overridesConvention = new OverrideAssociationsConvention();
modelBuilder.Conventions.Add(overridesConvention);
modelBuilder.Conventions.Add(new OverrideMultiplictyConvention(overridesConvention));
}
private class OverrideAssociationsConvention : IConceptualModelConvention<AssociationType>
{
...
public List<AssociationEndMember> MultiplicityOverrides { get; } = new List<AssociationEndMember>();
public void Apply(AssociationType item, DbModel model)
{
if (multiplicityOverrides.Contains(item.Name))
{
// Defer actually updating the multiplicity until the store model is generated
// so that foreign keys are placed in the desired tables.
MultiplicityOverrides.Add(item.AssociationEndMembers.Last());
}
if (cascadeOverrides.Contains(item.Name))
{
item.AssociationEndMembers.Last().DeleteBehavior = OperationAction.Cascade;
}
}
}
private class OverrideMultiplictyConvention : IStoreModelConvention<EdmModel>
{
private readonly OverrideAssociationsConvention overrides;
public OverrideMultiplictyConvention(OverrideAssociationsConvention overrides)
{
this.overrides = overrides;
}
public void Apply(EdmModel item, DbModel model)
{
overrides.MultiplicityOverrides.ForEach(o => o.RelationshipMultiplicity = RelationshipMultiplicity.One);
}
}
I am strugging to see why entity framework does not allow 1 to 1 relationships in the following context;
ClassA {
public int ID;
[ForeignKey("ClassB")]
public int ClassBID;
public ClassB classB;
}
ClassB {
public int ID;
[ForeignKey("ClassA")]
public int ClassAID;
public ClassA classa;
}
i.e. a one to one relationship whereby I could navigate to either from linq.
The context I have is that I have a Vehicle. Each Vehicle can have a device (which is null if it doesnt). An each device will have an optional vehicle.
If someone could explain why he above is not valid (or supported) and explain how I would get around my issue I would really apreciate it.
Thanks in advance.
You have to decide that one end is non-optional (i.e., 1-to-0..1 is allowed ... 0..1-to-0..1 is not). Once you do, EF supports 1-to-0..1 by forcing the dependent side to not have it's own Key, but to instead define the dependent class' Key to be the same as its ForeignKey (which, if you think about it, makes sense for a relation that's supposed to be 1-to-1):
class A
{
public int Id { get; set; }
public int? BId { get; set; }
public virtual B B { get; set; }
}
class B
{
[Key, ForeignKey("A")]
public int Id { get; set; }
public virtual A A { get; set; }
}
Note, too, the detail that BId is int?: NULL in SQL corresponds to Nullable<> in C#.
If I understand a device can have zero or one vehicles and vice-versa.
In an old DB model one of the two tables (device or vehicles) should have a nullable field that references the other table.
To configure it in EF you have to use data annotation or fluent interface.
Here the code of the model and of the context
public class ClassA
{
public int Id { get; set; }
public string Description { get; set; }
public virtual ClassB ClassB { get; set; }
}
public class ClassB
{
public int Id { get; set; }
public string Description { get; set; }
public virtual ClassA ClassA { get; set; }
}
class Context : DbContext
{
public Context(DbConnection connection)
: base(connection, false)
{ }
public DbSet<ClassA> As { get; set; }
public DbSet<ClassB> Bs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ClassB>().HasOptional(c => c.ClassA).WithOptionalDependent(c => c.ClassB);
}
}
As you can immagine there are some restrictions on using this model. I.e. the model (POCO model) allow you to have a classA1 referencing a classB1 that references classA2 (same type but different instance of classA1). The DB and EF does not. Here an example with a query dump on how EF works in this case (I think is very interesting!!!)
using (Context context = new Context(connection))
{
ClassA classA;
ClassB classB;
// Very simple behaviour (as expected). You can see the queries after SaveChanges()
classA = new ClassA {Description = "B empty"};
context.As.Add(classA);
classA = new ClassA { Description = "B full", ClassB = new ClassB(){Description = "ClassB full"}};
context.As.Add(classA);
classB = new ClassB { Description = "B empty"};
context.Bs.Add(classB);
context.SaveChanges();
/*
insert into [ClassAs]([Description])
values (#p0);
#p0 = B full
insert into [ClassAs]([Description])
values (#p0);
#p0 = B empty
insert into [ClassBs]([Description], [ClassA_Id])
values (#p0, #p1);
#p0 = ClassB full
#p1 = 1
insert into [ClassBs]([Description], [ClassA_Id])
values (#p0, null);
#p0 = B empty
*/
// Here a new classB references an already referenced classA. But we don't want this!!!
// EF works like we want, the classA is detached from the old classB then attached to the
// new classB. Below you can see the queries
classB = new ClassB { Description = "B full with the wrong A", ClassA = classA};
context.Bs.Add(classB);
/*
update [ClassBs]
set [ClassA_Id] = null
where (([Id] = #p0) and ([ClassA_Id] = #p1))
#p0 = 1
#p1 = 1
insert into [ClassBs]([Description], [ClassA_Id])
values (#p0, #p1);
#p0 = B full with the wrong A
#p1 = 1
*/
context.SaveChanges();
}
Now last step...
Looking at the structure of the database this POCO model is
ClassBs(Id, Description, ClassA_Id : ClassAs)
ClassAs(Id, Description)
In the DB model we could have 2 different instances of ClassB that has the same ClassA instance (EF does not allow us to do this but we can do this from SQL).
After the hack using SQL you can run this test
using (Context context = new Context(connection))
{
foreach (var classB in context.Bs.ToList())
{
if (classB.ClassA == null)
continue;
Console.WriteLine("{0} {1} {2}", classB.Id, classB.ClassA.Id, classB.ClassA.ClassB.Id);
}
}
This test raises an exception
===
An unhandled exception of type 'System.InvalidOperationException' occurred in EntityFramework.dll
Additional information: A relationship multiplicity constraint violation occurred: An EntityReference can have no more than one related object, but the query returned more than one related object. This is a non-recoverable error.
===
Can we avoid that someone from SQL does it? Yes, inserting a unique constraint on ClassA_Id field.
I'm having an issue with discriminators in TPH inheritance with Entity Framework v6.1.1
I would expect that the discriminator should fall on the table that is representative of the base class. However, it appears EF is trying to map the discriminator column to the table mapped to the derived class
E.g.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Staff : Person
{
public decimal? Salary { get; set; }
}
public class MyContext : DbContext
{
public MyContext()
: base("MyConnectionString") {}
public virtual IDbSet<Person> Persons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.Map(x => x.ToTable("Person"))
.Map<Staff>(x => x.ToTable("Staff").Requires("PersonTypeId").HasValue(1));
}
}
I am also using an existing schema too - i.e:
CREATE TABLE Person
(
Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
Name VARCHAR(50) NULL,
PersonTypeId INT NOT NULL
)
CREATE TABLE Staff
(
Id INT NOT NULL REFERENCES Person(Id),
Salary DECIMAL NULL
)
GO
However, when I try to add a new Staff, I encounter the following exception:
'System.Data.SqlClient.SqlException : Cannot insert the value NULL into column 'PersonTypeId', table 'MyDb.dbo.Person'; column does not allow nulls. INSERT fails.
The statement has been terminated'
It appears that it is trying to insert the discriminator (incorrectly) to the derived table. Hope someone can help.
So it appears that I've mis-understood the TPH setup for Entity Framework.
In my case, I'm wanting to map derived types to separate tables, which is an example of Table per Type - http://weblogs.asp.net/manavi/inheritance-mapping-strategies-with-entity-framework-code-first-ctp5-part-2-table-per-type-tpt
Discriminators are redundant in this context.
I've posted my problem on codeplex http://entityframework.codeplex.com/workitem/2087.
There are also some questions posted here but they are not successfully answered.
See
Mapping TPT in EF Code First 4.1 w/ Different Primary Keys
Entity Framework 4 - TPT Inheritance in Features CTP5 (code first): rename foreign key column on inherited table
How can I use TPT inheritance models when primary keys have different names?
Is it now possible to have different column names for the primary keys when using TPT?
May be with 6.1.0
In TPT you're essentially do not want to declare the key in the subclasses, you'd miss the point otherwise.
If you must have a different Id name, just make proxy properties in the subclasses mapping to the base Id one.
public class BaseEntity
{
public int Id { get; set; }
}
public abstract class SubEntity : BaseEntity
{
public BaseId
{
get => Id;
set => Id = value;
}
}
Consider marking the sub fields as NotMapped, which in case you shouldn't include them in your LINQ queries.
With EF 6.4 I was able to use the ColumnAttribute to rename the Primary Key column in the dependent class
[Table("Person")]
public class Person
{
[Key]
public virtual int PersonId { get; set; }
// Person atributes...
}
[Table("Employee")]
public class Employee : Person
{
[Column("EmployeeId")] // <- Name of the primary Key column in the Database
public override int PersonId { get; set }
// Employee Attributes
}
Look at this code snip. Its work correct for me:
public partial class Person
{
// Any other PK name can thrown an exception
public int ID { get; set; }
}
public partial class Employee : Person
{
// Hide base class ID
private new int ID { get; set }
// Define derived class ID (that wrapped inherited ID)
[NotMapped]
public int EmployeeID
{
get { return base.PersonID; }
set { base.PersonID = value; }
}
}
Now, we must rename the inherited ID (with fluent API) for database table:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.Property(e => e.ID)
.HasColumnName("EmployeeID");
}
(This looks like a long question, but it's not really, honest!)
I am trying to get a simple proof of concept working with Entity Framework 4 and the CTP 3 version of Code Only. It feels like I'm missing something really obvious and simple.
I have this following test which is failing:
[TestFixture]
public class ParentChildTests
{
[Test]
public void ChildRead_DatabaseContainsRelatedObjects_ParentIsNotNull()
{
var ctx = GetMyObjectContext();
var child = ctx.Children.Where(c => c.Id == 1).Single();
var parent = child.ParentTable;
Assert.That(parent, Is.Not.Null);
}
// GetMyObjectContext etc...
}
The read of child works fine and I get back a ChildTable whose ParentTableId value is '1' as I would expect, but the ParentTable property is NULL. I do not expect this because my POCOs have all virtual properties (see below) and EF4 has lazy loading enabled by default.
What am I missing?
Database
create table parent_table
(
parent_table_id int identity(1,1) primary key,
parent_table_name varchar(50) not null,
display_name varchar(50)
)
create table child_table
(
child_table_id int identity(1,1) primary key,
child_table_name varchar(50) not null,
parent_table_id int not null
)
alter table child_table add constraint FK_child_table__parent_table
foreign key (parent_table_id) references parent_table(parent_table_id)
POCO Entities
public class ParentTable
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual string DisplayName { get; set; }
}
public class ChildTable
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual int ParentTableId { get; set; }
public virtual ParentTable ParentTable { get; set; }
}
Entity Configurations
public class ParentTableConfiguration : EntityConfiguration<ParentTable>
{
public ParentTableConfiguration()
{
MapSingleType(pt => new
{
parent_table_id = pt.Id,
parent_table_name = pt.Name,
display_name = pt.DisplayName,
})
.ToTable("dbo.parent_table");
Property( pt => pt.Id ).IsIdentity();
Property( pt => pt.Name ).IsRequired();
}
}
public class ChildTableConfiguration : EntityConfiguration<ChildTable>
{
public ChildTableConfiguration()
{
MapSingleType(ct => new
{
child_table_id = ct.Id,
child_table_name = ct.Name,
parent_table_id = ct.ParentTableId,
})
.ToTable("dbo.child_table");
Property( ct => ct.Id ).IsIdentity();
Property( ct => ct.Name ).IsRequired();
Relationship(ct => ct.ParentTable)
.HasConstraint((ct, pt) => ct.ParentTableId == pt.Id);
}
}
(Thanks for reading this far!)
As far as understand you just do not load this navigation property.
This will result in eager loading.
var child = ctx.Children.Include("ParentTable").Where(c => c.Id == 1).Single();
Or you could enable lazy loading by setting ctx.ContextOptions.LazyLoadingEnabled = true;