I have seriously spent two work days trying to a TPH setup from Database First to Code first. The Error I get is Something like "Invalid Column Name Entity_EntityId/ Entity_Entity_Id1"
I've drawn up a very basic reproduction of the issue like so:
internal class Program
{
private static void Main(string[] args)
{
using (var context = new Context())
{
var baseClass = new Base {Name = "Test"};
context.BaseClasses.Add(baseClass);
context.SaveChanges();
var baseClasses = context.BaseClasses.ToList();
}
}
}
Context:
public class Context : DbContext
{
public Context() : base("TPH")
{
}
public DbSet<Base> BaseClasses { get; set; }
public DbSet<Derived> DervDerivedClasses { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
}
Mapping:
public class BaseMap : EntityTypeConfiguration<Base>
{
public BaseMap()
{
HasKey(b => b.Id);
Property(b => b.Name);
HasOptional(b => b.AnotherClass)
.WithMany(b => b.Bases)
.HasForeignKey(b => b.AnotherClassId);
Map(b => b.Requires("Disc").HasValue(1));
}
}
public class DerivedMap : EntityTypeConfiguration<Derived>
{
public DerivedMap()
{
HasKey(b => b.Id);
Property(b => b.Name);
HasOptional(b => b.AnotherClass)
.WithMany(b => b.Deriveds)
.HasForeignKey(b => b.AnotherClassId);
Map(b => b.Requires("Disc").HasValue(2));
}
}
public class SecondDerivedMap : EntityTypeConfiguration<SecondDerived>
{
public SecondDerivedMap()
{
HasKey(b => b.Id);
Property(b => b.Name);
HasOptional(b => b.AnotherClass)
.WithMany(b => b.SecondDeriveds)
.HasForeignKey(b => b.AnotherClassId);
Map(b => b.Requires("Disc").HasValue(3));
}
}
Entities:
public class Base
{
public int Id { get; set; }
public string Name { get; set; }
public int? AnotherClassId { get; set; }
public AnotherClass AnotherClass { get; set; }
}
public class Derived : Base
{
}
public class SecondDerived : Base
{
}
public class AnotherClass
{
public int Id { get; set; }
public ICollection<Base> Bases { get; set; }
public ICollection<Derived> Deriveds { get; set; }
public ICollection<SecondDerived> SecondDeriveds { get; set; }
}
How can I get the table to just have a single "AnotherClassId?"
You're only supposed to have a single navigation property per entity per relationship -- and you have three (Bases, Deriveds, and SecondDeriveds). EF sees those properties and thinks there are three different one-to-many associations between AnotherClass and the various classes in the Base hierarchy.
If you want to get a collection of the related Derived entities from AnotherClass, you're supposed to use something like anotherClassEntity.Bases.OfType<Derived>().
Related
I have created a simple EF Core to join two tables by using relationship (HasOne). But when I run it, the query only queries the master table (Employees) without joining to the second table (Contact) and it causes the model to not bind the data.
Could someone point out what I am missing in this code shown below? Thanks
public class Employees
{
public int EmployeeId { get; set; }
public string EmployeeName { get; set; }
public Contact Contact { get; set; }
}
public class Contact
{
public int Id { get; set; }
public string ContactNumber { get; set; }
public Employees Employee { get; set; }
public int EmployeeId { get; set; }
}
internal class EmployeeMap : IEntityTypeConfiguration<Employees>
{
public void Configure(EntityTypeBuilder<Employees> builder)
{
builder.HasKey(x => x.EmployeeId);
builder.Property(p => p.EmployeeId).ValueGeneratedOnAdd();
builder.HasOne(x => x.Contact).WithOne(y => y.Employee).HasForeignKey<Contact>(k => k.EmployeeId);
}
}
public class ContactMap : IEntityTypeConfiguration<Contact>
{
public void Configure(EntityTypeBuilder<Contact> builder)
{
builder.HasKey(x => x.Id);
builder.Property(p => p.Id).ValueGeneratedOnAdd();
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(GetType().Assembly);
}
private EmployeeResponse GetEmployeeResponse()
{
var emp = _context.Employees.FirstOrDefault();
return new EmployeeResponse
{
ContactNumber = emp!.Contact.ContactNumber,
EmployeeId = emp.EmployeeId,
};
}
Solutions:
1. Enable lazy loading:
DbContext.Configuration.LazyLoadingEnabled = true;
2. Or load it manually with .Include:
_context.Employees.Include(x => x.Contact).FirstOrDefault();
More information about navigation propertys in ef.
I am trying to configure a one to many relationship using EF Core via fluent api and i keep getting the following error :
The expression 'x => x.parent' is not a valid property expression. The
expression should represent a simple property access: 't =>
t.MyProperty'. (Parameter 'propertyAccessExpression')'
Model(s)
public class Parent {
public int ID { get; set; }
public string Name { get; set; }
public ICollection<Child> Children { get; set; }
}
public class Child {
public int ID { get; set; }
public string Name { get; set; }
public Parent parent;
public int ParentId { get; set; }
}
Context
public class MyContext : DbContext {
public DbSet<Parent> Parents { get; set; }
public DbSet<Child> Children { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Child>().HasKey(x => x.ID);
modelBuilder.Entity<Parent>().HasKey(x => x.ID);
modelBuilder.Entity<Child>()
.HasOne(x => x.parent)
.WithMany(y => y.Children)
.HasForeignKey(t => t.ParentId);
}
public MyContext(DbContextOptions options):base(options) { }
}
Usage
static async Task Main(string[] args)
{
string connectionString = "[someconnectionstring]"
var optionsBuilder = new DbContextOptionsBuilder<MyContext>();
optionsBuilder.UseSqlServer(connectionString);
MyContext context = new MyContext(optionsBuilder.Options);
await context.Parents.AddAsync(new Parent {
Name = "myparent",
Children = new List<Child>() {
new Child { Name = "Child1" },
new Child { Name = "Child2" } }
}); //i am getting the error here
await context.SaveChangesAsync();
}
parent in Child class is a field. It should be public property. Please see for more information https://learn.microsoft.com/en-us/ef/ef6/modeling/code-first/fluent/types-and-properties#property-mapping
EF migrations fails and tells me that the derived types have no key defined.
I'm trying to setup TPH, which should be fairly easy with attributes, but I can't get it to work with separate (fluent-)configuration files. It always reports that the key of the derived types is not defined.
If I define it on the derived types, then it says the key must be defined for the base type.
Entity type configurations:
public class CoreEntityConfiguration<TBaseModel> : IEntityTypeConfiguration<TBaseModel> where TBaseModel : class, ICoreEntity
{
public virtual void Configure(EntityTypeBuilder<TBaseModel> builder)
{
builder.Property(p => p.Created).IsDateColumn().HasDefaultValueSql("GETDATE()").ValueGeneratedOnAdd();
builder.Property(p => p.Modified).IsDateColumn().HasDefaultValueSql("GETDATE()").ValueGeneratedOnAddOrUpdate();
builder.Property(p => p.Rowversion).IsRowVersion();
}
}
public class BaseEntityConfiguration<TBaseEntity, TKey> : CoreEntityConfiguration<TBaseEntity> where TBaseEntity : BaseEntity<TKey>
{
public override void Configure(EntityTypeBuilder<TBaseEntity> builder)
{
base.Configure(builder);
builder.HasKey(p => p.ID);
}
}
public class EventConfiguration : BaseEntityConfiguration<Event, int>
{
public override void Configure(EntityTypeBuilder<Event> builder)
{
base.Configure(builder);
builder.ApplyConfiguration(new SearchableConfiguration<Event, int>());
builder.Property(p => p.Time).IsDateTimeOffsetColumn();
builder.HasOne(p => p.Player).WithMany(p => p.Events).HasForeignKey(p => p.PlayerID).OnDelete(Microsoft.EntityFrameworkCore.DeleteBehavior.Restrict);
builder.HasDiscriminator(p => p.EventType)
.HasValue<Goal>("Goal")
.HasValue<Card>("Card")
.HasValue<Substitution>("Substitution");
builder.ToTableWithSchema(Constants.SCHEME_OPERATIONS);
}
}
public class EventGoalConfiguration : IEntityTypeConfiguration<Goal>
{
public void Configure(EntityTypeBuilder<Goal> builder)
{
builder.HasBaseType<Event>();
builder.HasOne(p => p.GoalType).WithMany().OnDelete(DeleteBehavior.Restrict);
builder.HasOne(p => p.AssistedByPlayer).WithMany(p => p.Assists).HasForeignKey(p => p.AssistedByPlayerID).OnDelete(DeleteBehavior.SetNull);
}
}
Model classes:
public abstract class CoreEntity : ICoreEntity
{
public DateTime Created { get; set; }
public int CreatedByID { get; set; }
public DateTime? Modified { get; set; }
public int? ModifiedByID { get; set; }
public byte[] Rowversion { get; set; }
}
public abstract class BaseEntity<TKey> : CoreEntity, IBaseEntity<TKey>
{
public TKey ID { get; set; }
}
public abstract class Event : BaseEntity<int>
{
...
}
public class Goal : Event
{
public int GoalTypeID { get; set; }
public virtual GoalType GoalType { get; set; }
public int? AssistedByPlayerID { get; set; }
public virtual Player AssistedByPlayer { get; set; }
}
I can't get rid of this error:
Please help me and tell me what I am doing wrong. I can't seem to figure it out...
How to configure EF mapping for the following relations:
public class OrderBook : BaseEntity
{
public OrderBook()
{
BuyOrders = new List<OrderBookItem>();
SellOrders = new List<OrderBookItem>();
}
public virtual ICollection<OrderBookItem> BuyOrders { get; set; }
public virtual ICollection<OrderBookItem> SellOrders { get; set; }
}
public class OrderBookItem : BaseEntity
{
public OrderType Type { get; set; }
public int OrderBookId { get; set; }
public virtual OrderBook OrderBook { get; set; }
}
I have tried a few variations
public class OrderBookMap : DynamicLoadEntityTypeConfiguration<OrderBook>
{
public OrderBookMap()
{
ToTable(nameof(OrderBook));
this.HasKey(p => p.Id);
//this does not work
//this.HasMany<OrderBookItem>(p=>p.BuyOrders).WithRequired(i=>i.OrderBook).HasForeignKey<int>(s => s.OrderBookId);
//this.HasMany<OrderBookItem>(p=>p.SellOrders).WithRequired(i=>i.OrderBook).HasForeignKey<int>(s => s.OrderBookId);
}
}
public class OrderBookItemMap : EntityTypeConfiguration<OrderBookItem>
{
public OrderBookItemMap()
{
ToTable(nameof(OrderBookItem));
this.HasKey(p => p.Id);
//this leads to extra columns created
this.HasRequired(i => i.OrderBook).WithMany(d => d.BuyOrders).HasForeignKey(i => i.OrderBookId).WillCascadeOnDelete(false);
this.HasRequired(i => i.OrderBook).WithMany(d => d.SellOrders).HasForeignKey(i => i.OrderBookId).WillCascadeOnDelete(false);
}
}
But the mapping result is incorrect, i think it is enough to have one FK between OrderBook and OrderBookItem tables:
the Type property might be used somehow as it helps to distinct which item is Buy or Sell.
In my case I decided it would be better to split it on 3 tables rather than having OrderBook_id1 and OrderBook_id2:
OrderBook
BuyOrder
SellOrder
So the entities and mapping end up as the following:
public class OrderBook : BaseEntity
{
public OrderBook()
{
BuyOrders = new List<BuyOrder>();
SellOrders = new List<SellOrder>();
}
public virtual ICollection<BuyOrder> BuyOrders { get; set; }
public virtual ICollection<SellOrder> SellOrders { get; set; }
}
public abstract class OrderBookItem : BaseEntity
{
public int OrderBookId { get; set; }
public virtual OrderBook OrderBook { get; set; }
}
public class BuyOrder : OrderBookItem
{
}
public class SellOrder : OrderBookItem
{
}
public class OrderBookMap : EntityTypeConfiguration<OrderBook>
{
public OrderBookMap()
{
ToTable(nameof(OrderBook));
this.HasKey(p => p.Id);
}
}
public class BuyOrderMap : EntityTypeConfiguration<BuyOrder>
{
public BuyOrderMap()
{
Map(m =>
{
m.MapInheritedProperties();
m.ToTable(nameof(BuyOrder));
});
this.HasKey(p => p.Id);
this.HasRequired(i => i.OrderBook).WithMany(d => d.BuyOrders).HasForeignKey(i => i.OrderBookId).WillCascadeOnDelete(false);
}
}
public class SellOrderMap : EntityTypeConfiguration<SellOrder>
{
public SellOrderMap()
{
Map(m =>
{
m.MapInheritedProperties();
m.ToTable(nameof(SellOrder));
});
this.HasKey(p => p.Id);
this.HasRequired(i => i.OrderBook).WithMany(d => d.SellOrders).HasForeignKey(i => i.OrderBookId).WillCascadeOnDelete(false);
}
}
Today I got a question about how to create a many to many mapping using Entity Framework Code First fluent api.
The problem is that the entity create an additional table beyond that was set for me.
public class Person
{
public Person()
{
courses = new HashSet<Course>();
}
public int PersonID { get; set; }
public String Name { get; set; }
public ICollection<Course> courses { get; set; }
}
public class Course
{
public Course()
{
people = new HashSet<Person>();
}
public int CourseID { get; set; }
public String Name { get; set; }
public ICollection<Person> people { get; set; }
}
public class PersonCourse
{
public int fk_CourseID { get; set; }
public virtual Course course { get; set; }
public int fk_PersonID { get; set; }
public virtual Person person { get; set; }
public String AnotherInformation { get; set; }
}
public class PersonDataConfiguration : EntityTypeConfiguration<Person>
{
public PersonDataConfiguration()
{
ToTable("Person");
Property(c => c.Name).IsRequired();
this.HasMany(c => c.courses).WithMany(t => t.people).Map(m => { m.MapLeftKey("CourseID"); m.MapRightKey("PersonID"); });
}
}
public class CourseDataConfiguration : EntityTypeConfiguration<Course>
{
public CourseDataConfiguration()
{
ToTable("Course");
Property(c => c.Name).IsRequired();
this.HasMany(c => c.people).WithMany(t => t.courses).Map(m => { m.MapLeftKey("PersonID"); m.MapRightKey("CourseID"); });
}
}
public class PersonCourseDataConfiguration : EntityTypeConfiguration<PersonCourse>
{
public PersonCourseDataConfiguration()
{
ToTable("PersonCourseX");
HasKey(c => new { c.fk_CourseID, c.fk_PersonID });
Property(c => c.AnotherInformation).IsRequired();
this.HasRequired(c => c.person).WithMany().HasForeignKey(t => t.fk_PersonID);
this.HasRequired(c => c.course).WithMany().HasForeignKey(t => t.fk_CourseID);
}
}
public class ProgramTesteContext : DbContext
{
public ProgramTesteContext()
: base("MyConnectionString")
{
}
public DbSet<Person> Person { get; set; }
public DbSet<Course> Course { get; set; }
public DbSet<PersonCourse> PersonCourse { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<IncludeMetadataConvention>();
modelBuilder.Properties<String>()
.Configure(p => p.HasColumnType("varchar"));
modelBuilder.Properties<String>()
.Configure(p => p.HasMaxLength(100));
modelBuilder.Configurations.Add(new PersonDataConfiguration());
modelBuilder.Configurations.Add(new CourseDataConfiguration());
modelBuilder.Configurations.Add(new PersonCourseDataConfiguration());
}
}
The entity set up two tables for mapping:
PersonCourseX created by me and another CoursePerson table containing only foreign keys without anotherinformation field.
How to make this second table is not created?
Change PersonCourseDataConfiguration as follows:
public class PersonCourseDataConfiguration : EntityTypeConfiguration<PersonCourse>
{
public PersonCourseDataConfiguration()
{
ToTable("PersonCourseX");
HasKey(c => new { c.fk_CourseID, c.fk_PersonID });
Property(c => c.AnotherInformation).IsRequired();
this.HasRequired(c => c.person).WithMany(c => c.courses).HasForeignKey(t => t.fk_PersonID);
this.HasRequired(c => c.course).WithMany(c => c.people).HasForeignKey(t => t.fk_CourseID);
}
}
Remove the commented lines:
public class PersonDataConfiguration : EntityTypeConfiguration<Person>
{
public PersonDataConfiguration()
{
ToTable("Person");
Property(c => c.Name).IsRequired();
//this.HasMany(c => c.courses).WithMany(t => t.people).Map(m => { m.MapLeftKey("CourseID"); m.MapRightKey("PersonID"); });
}
}
public class CourseDataConfiguration : EntityTypeConfiguration<Course>
{
public CourseDataConfiguration()
{
ToTable("Course");
Property(c => c.Name).IsRequired();
//this.HasMany(c => c.people).WithMany(t => t.courses).Map(m => { m.MapLeftKey("PersonID"); m.MapRightKey("CourseID"); });
}
}
Change Person and Course as follows:
public class Person
{
//.. other properties
public ICollection<PersonCourse> courses { get; set; }
}
public class Course
{
//.. other properties
public ICollection<PersonCourse> people { get; set; }
}