TPC mappint generate the base class table - entity-framework

I want to achieve TPC mapping by code-first, and I have read this article:
Inheritance with EF Code First: Part 3 – Table per Concrete Type (TPC)
I've wrote the code as below.
namespace TPCTest
{
class Program
{
static void Main(string[] args)
{
using (TestContext context = new TestContext())
{
Manager m = new Manager();
m.AnnualSalary = 100000;
m.Name = "Allen";
m.Sex = true;
m.Id = 1;
Worker w = new Worker();
w.Id = 2;
w.Name = "John";
w.Sex = true;
w.MonthlyPay = 5000;
context.empSet.Add(m);
context.empSet.Add(w);
context.SaveChanges();
}
}
}
abstract class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public bool Sex { get; set; }
}
class Manager : Employee
{
public decimal AnnualSalary { get; set; }
public string Department { get; set; }
}
class Worker : Employee
{
public decimal MonthlyPay { get; set; }
}
class TestContext : DbContext
{
public DbSet<Employee> empSet { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Manager>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Manager");
});
modelBuilder.Entity<Worker>().Map(m =>
{
m.MapInheritedProperties();
m.ToTable("Worker");
});
modelBuilder.Entity<Employee>().Property(e => e.Id).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None);
}
}
}
But after running the code, I found the Employee table also exist in database, anyone can help?
Thanks in advance!

Use EF 5.0 or higher!
The issue seems to be fixed since that version. With EF 4.3.1 I could reproduce the behavior. So just update.

Related

Entity Framework Core null relationship

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.

EF Core 6 Seeding Data Gives Foreign Key error Despite Having FK Value

So, I have struggled with this for a while now and can't figure out what I'm missing. I have a table that holds an entity called Skill and the DataModel looks like this:
public class SkillModel
{
public SkillModel()
{
}
public SkillModel(int skillId)
{
SkillId = skillId;
}
public int SkillId { get; set; } = 0;
public string Name { get; set; } = "";
public Guid DescriptionId { get; set; } = new();
public int SkillGroupId { get; set; } = 0;
public SkillGroupModel SkillGroup { get; set; } = new();
}
It references the SkillGroup which is it's own table and it looks like this:
public class SkillGroupModel
{
public SkillGroupModel()
{
}
public SkillGroupModel(int skillGroupId)
{
SkillGroupId = skillGroupId;
}
public int SkillGroupId { get; set; } = 0;
public string Name { get; set; } = "";
public Guid DescriptionId { get; set; } = new();
public List<SkillModel> Skills { get; set; } = new();
}
They each have their own configuration files and the look like this:
SkillModel
public class SkillConfiguration : IEntityTypeConfiguration<SkillModel>
{
public void Configure(EntityTypeBuilder<SkillModel> builder)
{
var dataSeeds = new DataSeeds();
builder.ToTable("Skills", "Skills");
builder.HasKey(k => k.SkillId);
builder.HasOne(s => s.SkillGroup)
.WithMany(s => s.Skills);
builder.HasData(dataSeeds.Skills);
}
}
SkillGroupModel
var dataSeeds = new DataSeeds();
builder.ToTable("SkillGroups", "Skills")
.HasKey(k => k.SkillGroupId);
builder.HasData(dataSeeds.SkillGroups);
Data seeds looks like this:
SkillGroupModel Seed
public List<SkillGroupModel> GetSkillGroups()
{
return new List<SkillGroupModel>()
{
new()
{
SkillGroupId = 1, Name = "Artisan", DescriptionId = SkillGroupDescriptions["Artisan"].Id
},
...
}
SkillModel Seeds
return new List<SkillModel>()
{
new()
{
SkillId = 1,
Name = "Aesthetics",
DescriptionId = SkillDescriptions["Aesthetics"].Id,
SkillGroupId = 1
},
...
}
So, I was obviously missing something. Ivan Stoev had a great point of not initializing my navigation properties like that, and that was a great help.
I went about it by not using my navigation properties and only setting the FK Properties. I think in the past I was trying to do both and that was causing issues that took me down this path. I'm not sure what I was doing wrong before but the way the documentation for seeding data on MSDN worked fine for me after going back and trying it again.

entity framework seed in OnModelCreating with many-to-many relatioship

i have 2 class with a many to many relationship
public class Actor
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Movie> Movies { get; set; }
}
public class Movie
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Actor> Actors { get; set; }
}
I would like to add data in the generated tables via the OnModelCreating.
I have always un error because actormovie don't exist at this time.
Might you help me ?
I found the solution on Join entity type configuration
Use this to seed data OnModelCreating for the joining table:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder
.Entity<Actor>()
.HasData(new Actor { Id = 1, Name = "Keanu Reeves" });
modelBuilder
.Entity<Movie>()
.HasData(
new Movie { Id = 1, Name = "Matrix" },
new Movie { Id = 2, Name = "John Wick" });
modelBuilder
.Entity<Actor>()
.HasMany(m => m.Movies)
.WithMany(a => a.Actors)
.UsingEntity(j => j
.HasData(
new { ActorsId = 1, MoviesId = 1 },
new { ActorsId = 1, MoviesId = 2 } ));
}
This worked for me.

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; }
}

why the explicit load does not work,and the navigation property always null?

All my code is here,quite simple,and I don't konw where it goes wrong.
Person and Task has an many-to-many relationship.
I want to load someone's task using the explicit way.
I follow the way this post shows,and i can't make it work.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Task> Tasks { get; set; }
}
public class Task
{
public int Id { get; set; }
public string Subject { get; set; }
public ICollection<Person> Persons { get; set; }
}
public class Ctx : DbContext
{
public Ctx()
: base("test")
{
this.Configuration.LazyLoadingEnabled = false;
this.Configuration.ProxyCreationEnabled = false;
}
public DbSet<Person> Persons { get; set; }
public DbSet<Task> Task { get; set; }
}
class Program
{
static void Main(string[] args)
{
//add some data as follows
//using (var ctx = new Ctx())
//{
//ctx.Persons.Add(new Person { Name = "haha" });
//ctx.Persons.Add(new Person { Name = "eeee" });
//ctx.Task.Add(new Task { Subject = "t1" });
//ctx.Task.Add(new Task { Subject = "t2" });
//ctx.SaveChanges();
//var p11 = ctx.Persons.FirstOrDefault();
//ctx.Task.Include(p2 => p2.Persons).FirstOrDefault().Persons.Add(p11);
//ctx.SaveChanges();
//}
var context = new Ctx();
var p = context.Persons.FirstOrDefault();
context.Entry(p)
.Collection(p1 => p1.Tasks)
.Query()
//.Where(t => t.Subject.StartsWith("t"))
.Load();
//the tasks should have been loaded,isn't it?but no...
Console.WriteLine(p.Tasks != null);//False
Console.Read();
}
}
Is there anything wrong with my code?I'm really new to EF,so please, someone help me.
The problem is your .Query() call. Instead of loading the collection, you are getting a copy of the IQueryable that would be used to load, then executing the query.
Remove your .Query() line and it will work.
If what you are looking for is getting a filtered list of collection elements, you can do this:
var filteredTasks = context.Entry(p)
.Collection(p1 => p1.Tasks)
.Query()
.Where(t => t.Subject.StartsWith("t"))
.ToList();
This will not set p.Tasks, nor is it a good idea to do so, because you'd be corrupting the domain model.
If you really, really want to do that... this might do the trick (untested):
var collectionEntry = context.Entry(p).Collection(p1 => p1.Tasks);
collectionEntry.CurrentValue =
collectionEntry.Query()
.Where(t => t.Subject.StartsWith("t"))
.ToList();
This solution worked for me :
For some reasons EF requires virtual keyword on navigation property, so the entities should be like this :
public class Person
{
//...
public virtual ICollection<Task> Tasks { get; set; }
}
public class Task
{
//...
public virtual ICollection<Person> Persons { get; set; }
}