I am using EF 5 Beta 2 Code-First. I have created an edmx file which has 2 entities among others named Brand and Vehicle.
One Brand can have zero or more (many) Vehicles and every Vehicle should have a Brand (required).
Vehicle has a foreign key named BrandID which is non-nullable.
(relationship)
Brand +-------------------> Vehicle
(1) (*)
Also I did use EF 5 DbContext Generator to create POCO classes.
The Problem
When I try to either Read or Write records, I get the following error:
error 3023: Problem in mapping fragments starting at line 155:Column Vehicle.BrandID in table Vehicle must be mapped: It has no default value and is not nullable.
Notice: I am using TPC inheritance mapping where Vehicle is an abstract base class from which 2 classes (Car & Motorbike) are being derived.
Here is class definition plus related fluent API code:
//------------ Class definiions ---------------
public abstract partial class Vehicle
{
public int VehicleID { get; set; }
public short BrandID { get; set; }
public virtual Brand Brand { get; set;
}
public partial class Car : Vehicle
{
public string BodyType { get; set; }
}
public partial class Motorbike : Vehicle
{
}
public partial class Brand
{
public Brand()
{
this.Vehicles = new HashSet<Vehicle>();
}
public short BrandID { get; set; }
public string Name { get; set; }
public virtual ICollection<Vehicle> Vehicles { get; set; }
}
//--------------- Fluent API code ---------------
modelBuilder.Entity<Vehicle>()
.Property(p => p.VehicleID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<Car>()
.Map(m =>
{
m.ToTable("Car");
m.MapInheritedProperties();
});
modelBuilder.Entity<Motorbike>()
.Map(m =>
{
m.ToTable("Motorbike");
m.MapInheritedProperties();
});
modelBuilder.Entity<Brand>()
.HasMany(b=>b.Vehicles)
.WithRequired(v=>v.Brand)
.HasForeignKey(p => p.BrandID);
modelBuilder.Entity<Brand>()
.Property(p => p.BrandID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
This is so strange since everything seems OK and has been checked several times.
Any thoughts would be greatly appreciated.
TPC inheritance just doesn't work for this kind of model where you have a navigation property on the base type:
...using TPC in the EF forces you to avoid associations in your base
type...
(Quote from here: http://blogs.msdn.com/b/alexj/archive/2009/04/15/tip-12-choosing-an-inheritance-strategy.aspx)
You must use TPT or TPH inheritance for your model.
Related
This question already has an answer here:
One-to-Zero relationship with Entity Framework Core 2.0
(1 answer)
Closed 4 years ago.
Updating entity Persons I get the following error:
System.InvalidOperationException: The property 'ID' on entity type 'Person' has a temporary value. Either set a permanent value explicitly or ensure that the database is configured to generate values for this property.
at Microsoft.EntityFrameworkCore....
This has been discussed several places, and a similar issue was reported fixed by the EF Core team. However, one of those posts is about multiple updates to an entity and the one-to-many solution does not work here; additionally, I cannot make one ID column nullable and prefer to use the fluent API configuration. The documentation example does not work either, so I am asking here.
The scenario is that I am upgrading a legacy ASP.NET MVC 4 project to ASP.NET MVC Core, and as a result I am upgrading from EF 6.1 to EF Core 2.1. I will happily move to 2.2 instead if it solves this problem; I think it was still in prerelease when I started.
Here is a (ridiculously) simplified version of my entities:
public class Person
{
public int ID { get; set; }
public string name { get; set; }
public virtual Worker Worker { get; set; }
}
public class Worker
{
public int ID { get; set; }
public string somePersonalDetails { get; set; }
public virtual Person Person { get; set; }
// other relationships exist
}
I am using fluent API configuration:
public class PersonBuilder
{
public PersonBuilder(EntityTypeBuilder<Person> entity)
{
entity.HasKey(k => k.ID);
entity.HasOne(p => p.Worker)
.WithOne(p => p.Person)
.HasForeignKey<Person>(p => p.ID)
//.IsRequired(false) //?
.OnDelete(DeleteBehavior.Cascade);
}
}
public class WorkerBuilder
{
public WorkerBuilder(EntityTypeBuilder<Worker> entity)
{
entity.HasKey(k => k.ID);
// other relationships are defined
}
}
public override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Configurations<Person>().Add(typeof(PersonBuilder));
builder.Configurations<Worker>().Add(typeof(WorkerBuilder));
}
The reason that it's split apart like that is because I adapted it from our leftover EF 4/5/6 configuration. Yay legacy code. Nevertheless it works (for other defined types). The way I am reading that, it says "define a foreign key on the related Worker object pointing to the ID of this object." It does just the opposite.
I have tried:
Defining the key relationship on the WorkerBuilder type instead. This yields SQLite Error 19: 'FOREIGN KEY constraint failed'. Amazingly, however, it still attempts to define the key on the Person entity, which is wrong.
Removing some of the specific expressions in hopes that EF will just figure out the relationship itself. It doesn't; if I provide too little information, it tries to use columns|fields that don't exist (e.g., "PersonID", or is unable to figure out the relationship altogether.
So, I am stumped. Has anyone done this successfully? In plain English,
"A person may or may not have a worker record" (1:0); and,
"if they have a worker record, both records have the same ID." (FK_W_ID__P_ID)
Write your model classes as follows:
public class Person
{
public int PersonId { get; set; }
public string Name { get; set; }
public virtual Worker Worker { get; set; }
}
public class Worker
{
public int PersonId { get; set; }
public string somePersonalDetails { get; set; }
public virtual Person Person { get; set; }
// other relationships exist
}
Then in the PersonConfiguration and WorkerConfiguration:
public class PersonConfiguration : IEntityTypeConfiguration<Person>
{
public void Configure(EntityTypeBuilder<Person> builder)
{
builder.HasKey(u => u.PersonId);
}
}
public class WorkerConfiguration : IEntityTypeConfiguration<Worker>
{
public void Configure(EntityTypeBuilder<Worker> builder)
{
builder.HasKey(u => u.PersonId);
builder.HasOne(u => u.Person)
.WithOne(b => b.Worker)
.HasForeignKey<Worker>(b => b.PersonId);
}
}
Then in the OnModelCreating of DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfiguration(new PersonConfiguration());
modelBuilder.ApplyConfiguration(new WorkerConfiguration());
}
If I defined one-to-many relation foreign-key constraints in Sql Server, Is it possible just define and use it in code-first class definitions in one side?
For example suppose I have Library and Book classes. Each Library can has many books but each book belong to one library.
public class Book
{
public Book() { }
public int BookId { get; set; }
public string BookName { get; set; }
public virtual Library Library { get; set; }
}
public class Library
{
public Library() { }
public int LibraryId { get; set; }
public virtual ICollection<Book> Books { get; set; }
}
If I want always just use this relation from Book side,Can I don't define below line in Library class definition?
public virtual ICollection<Book> Books { get; set; }
Sure, you can do this:
modelBuilder.Entity<Book>()
.HasRequired(b => b.Library) // Or: HasOptional
.WithMany()
.Map(m => m.MapKey("LibraryId"))
In this case, the database table Book has foreign key field LibraryId that's either required or optional (nullable). The foreign key is not mapped to a property in class Book. This is called an independent association.
You can also map the key to a property in Book if you want:
modelBuilder.Entity<Book>()
.HasRequired(b => b.Library)
.WithMany()
.HasForeignKey(b => b.LibraryId))
This turns the association into a foreign key association. In many cases this can be beneficial.
I'm new in Entity Framework and my English is not very good, sorry if i write something wrong. I want to make an unidirectional association in One-to-Many relationship with Entity Framework 6 using the following example:
public class Person
{
public int personId;
public string Name;
.
.
.
//public ICollection<Phone> Phones { get; set; } //I don't want this nav property
}
public class Phone
{
public int phoneId;
public string number;
public Person myPerson { get; set; }
}
In this classes, a Person has many Phone, so a Phone has only a Person (1 to 1...*) but I want to create the navigation property in Phone, not in Person.
How to create this association with Fluent API for mappings?
Use following mapping
modelBuilder.Entity<Phone>().HasRequired(p => p.myPerson).WithMany();
HasRequired configures required relationship for phone (i.e. it's required to have person id)
WithMany() configures relationship to be required:many without navigation property on many side
Consider reading Configuring Relationships with the Fluent API article.
First of all, you need to be more clear on what you want, considering you changed your question in Sergey's answer.
Considering it, this is what your classes will look like (yes, navigation properties inheritance is supported):
public class Person
{
public int personId { get; set; }
public string Name { get; set; }
}
public abstract class Phone
{
public int phoneId { get; set; }
public int personId { get; set; }
public Person myPerson { get; set; }
}
public class PhoneFixedLine : Phone
{ }
public class PhoneCellPhone : Phone
{ }
Obs.: Mind the getters and setters in the Person and Phone classes.
You want an unidirectional mapping with the navigation property in the Phone class.
Since you are mapping derivated classes you need to follow an Inheritance Strategy, see this linkfor more information.
I will be following the TPC strategy where only the concrete classes are mapped.
In the OnModelCreatingMethod do the following:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<PhoneFixedLine>().HasRequired(p => p.myPerson).WithMany()
.Map(m =>
{
m.MapInheritedProperties();
m.ToTable("FixedPhones");
});
modelBuilder.Entity<PhoneCellPhones>().HasRequired(p => p.myPerson).WithMany()
.Map(m =>
{
m.MapInheritedProperties();
m.ToTable("CellPhones");
});
}
You will face an identity problem with this strategy because these two table share the same primary key phoneId, in the link of the Inheritance Strategies there are two ways of dealing with it.
Using a table-per-type inheritance model and Entity Framework Code First, I am trying to eager load a list of derived class. Please note that I can't change the model.
I have the following model (overly simplified)
public class Training
{
public string Name { get; set; }
public IList<Person> Persons { get; set; }
}
public abstract class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
[Table("Students")]
public class Student : Person
{
public string StudentNumber { get; set; }
public IList<Training> Trainings { get; set; }
}
[Table("Instructors")]
public class Instructor : Person
{
public DateTime StartingDate { get; set; }
public IList<Training> Trainings { get; set; }
}
I want to query Training by name and eager load all the persons including the derived class (Student and Instructor). Back in April 2011, Tom Dykstra seemed to claim it wasn't possible.
The current version of the Entity Framework doesn't support eager loading for one-to-zero-or-one relationships when the navigation property is on the derived class of a TPH inheritance structure.
Has this changed? I am using EF5.
I don't see why ...
var list = context.Trainings.Include(t => t.Persons)
.Where(t => t.Name == someName)
.ToList();
... shouldn't work. EF should populate the Persons list with concrete Student and Instructor entities.
You neither have a "one-to-zero-or-one relationship" nor is your navigation property (Training.Persons) "on the derived class". So, I think the mentioned limitation does not apply to your model and query.
I tried to map some classes using Entity Framework in TPC style and got the following error:
Error: The type 'A' 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.
This error occurs when I use the following classes:
public abstract class BaseEntityTest
public abstract class BaseEntityTest2 : BaseEntityTest
public abstract class BaseEntityTest3 : BaseEntityTest2
public class A: BaseEntityTest3 // this class is the only one with a table in the db
In the OnModelCreating method I added the following code to get the TPC mapping
modelBuilder.Entity<A>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("A");
});
When I exclude BaseEntityTest2 from the structure (so that A inherits only from BaseEntityTest instead of BaseEntityTest2) the error goes away. Does that mean that it is not possible to create this mapping or do I just miss something?
EDIT:
Properties of classes:
public abstract class BaseEntityTest
{
[Key]
public Guid Id { get; set; }
public String Info { get; set; }
[Required]
public DateTime CreationDate { get; set; }
[Required]
public String CreationUser { get; set; }
[Required]
public DateTime ModificationDate { get; set; }
[Required]
public String ModificationUser { get; set; }
[ConcurrencyCheck]
[Required]
public int LockVersion { get; internal set; }
}
public abstract class BaseEntityTest2 : BaseEntityTest
{
[Required]
public string Name { get; set; }
public string Description { get; set; }
}
public abstract class BaseEntityTest3: BaseEntityTest2
{
[Required]
public DateTime FromDate { get; set; }
public DateTime ThruDate { get; set; }
}
public class A: BaseEntityTest3{
public String Test { get; set; }
}
The error occurs for EF 4.3.1 and earlier versions, but not for EF 4.4 and EF 5.0. (EF 4.4 is actually EF 5.0, but with .NET 4.0 as target platform.)
BUT: The error occurs only if you are using your abstract classes as entities in your model, that means
you either have DbSets for them in your context class, like
public DbSet<BaseEntityTestX> BaseEntityTestXs { get; set; }
or you have some Fluent mapping for BaseEntityTestX, some modelBuilder.Entity<BaseEntityTestX>()... stuff
or you are using one of the BaseEntityTestX as a navigation property in another (concrete) entity type
Do you need any of this?
Having a DbSet<BaseEntityTestX> in your context would only make sense if you really want to query for one of the abstract entities, like:
List<BaseEntityTest> list = context.BaseEntityTests
.Where(b => b.Info == "abc").ToList();
The result is of course a list of concrete entities that inherit from BaseEntityTest, but it can be a mix of different types, like some As and some Bs. Do you need such queries? Or do you only want to query for some of the concrete objects:
List<A> list = context.As
.Where(b => b.Info == "abc").ToList();
In the latter case you don't need a DbSet for the abstract base classes and you don't need any inheritance mapping. You can just remove the DbSet<BaseEntityTestX> from your context class and remove the TPC mapping and your error will go away.
The last point - having a navigation property to one of the abstract entities in another entity - doesn't make sense with TPC mapping. It is just not mappable to a relational database because with TPC mapping there is no table for the abstract entity, hence there is no target the foreign key relationship could refer to from the table of the concrete class that has the navigation property.
The error will also disappear if you extend your TPC mapping to the base classes:
modelBuilder.Entity<BaseEntityTestX>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("BaseEntityTestX");
});
But it will create tables for those abstract entities that don't seem to make sense to me.
in EF6.0 its happed when
EntityTypeConfiguration'<'YourBaseClass'>'
did not detailed ALL your derived class with
this.Map<DerivedClass1>(m =>
{
m.MapInheritedProperties();
m.ToTable("..");
});
if just one dervied class in the assembley not configured like so
you get this exception