Entity Framework 6 - inheritance and navigation properties on base class - c#

I have a problem with navigation properties and inheritance.
This is my problem:
I have a base Person class and classes User and Worker which inherit from Person. On the DB level I'm using single table inheritance or table per hierarchy (TPH) inheritance. So there a single table with a discriminator column.
Both User and Worker need to have a Company relation, so I would like to define it on the Person class.
I define my model like this:
[Table("mydb.person")]
public abstract partial class Person
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long ID { get; set; }
public long? CompanyID { get; set; }
[ForeignKey("CompanyID")]
public virtual Company Company { get; set; }
...
}
public partial class User : Person
{
...
}
public partial class Worker : Person
{
....
}
[Table("mydb.company")]
public partial class Company
{
public Company()
{
this.People = new HashSet<Person>();
this.Users = new HashSet<User>();
this.Workers = new HashSet<Worker>();
}
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long ID { get; set; }
public virtual ICollection<Person> People { get; set; }
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<Worker> Workers { get; set; }
...
}
Now, when I try to do a query to get the user and related company, for example:
dbSet.Where(u => u.Username == username).Include(x => x.Company).FirstOrDefault();
The query fails with this exception:
Unknown column 'Extent1.Company_ID' in 'field list
If I examine the result SQL it looks something like this:
SELECT
1 AS `C1`,
#gp2 AS `C2`,
`Extent1`.`ID`,
`Extent1`.`CompanyID`,
`Extent1`.`Username`,
...
`Extent1`.`Company_ID`
FROM `person` AS `Extent1`
WHERE `Extent1`.`Discriminator` = #gp1
It includes the extra Company_ID column, which doesn't exist.
I tried a few thing, nothing worked out:
renaming the column from CompanyID to Company_ID -> it generates a Column_ID1 in SQL and throws the same exception
removing the Users and Workers relations from Company -> it throws an exception saying it doesn't know how to map User and Company entities:
Unable to determine the principal end of an association between the
types 'Models.User' and 'Models.Company'. The principal end of this
association must be explicitly configured using either the
relationship fluent API or data annotations.
If I remove all 3 navigation properties from Company it throws the same mapping exception as above
I'm out of "clean" ideas at the moment. The only thing that could work is to do some dirty hack, define all the relations on child classes, and do separate queries and merging in the base class if both users and workers are required.
Do you have any suggestions?

Remove the Users and Workers collection properties.
public virtual ICollection<User> Users { get; set; }
public virtual ICollection<Worker> Workers { get; set; }
As your Company navigation property is defined on Person the associated back navigation property has to be an ICollection of Person.
The People collection will contain all the associated workers and users. The two extra properties Users and Workers are interpreted as completely new relationships and because you do not have corresponding properties and foreign keys on User or Worker EF generates it virtually.

Answer to the comment. Just for the sake of formatting as a second answer ;-)
With eager loading if you start with the Company
var companies = db.Company.Include(p => p.People);
It will always get the Users and the Workers.
If you use eager loading starting at the people.
var users = db.People.OfType<User>().Include(p => p.Company).ToList();
var companies = users.Select(p => p.Company).Distinct().ToList();
the People navigation property of your companies has just the Users.
Also you could execute two separate statements and the fixup of the database context will automatically fill the Navigation properties.
var company = db.Company.Where(p => p.ID > 100).ToList();
var copanyUsers = db.Company.Where(p => p.ID > 100)
.SelectMany(p => p.People).OfType<User>().ToList();

Related

EF Core - how to model relation of Grandparent - Parent - Child on same model

Imagine a model of User that can have Parents and also can have Children.
How would you model such a case in EF Core?
I tried with something like that (pseudo-code)
public class User
{
public ICollection<Relation> Relations {get;set;}
public ICollection<User> Parents => Relation.Where(r => r.Relation == 'Parents')
public ICollection<User> Children => Relation.Where(r => r.Relation == 'Children')
}
public class Relaction
{
public User User1 {get;set;}
public Guid User1Id {get;set;}
public User User2 {get;set;}
public Guid User2Id {get;set;}
public Relation Relation {get;set;} //some enum or sth to indicate relation type
}
But in such modeling, I'm not able to force EF DbContext to fetch into User.Relations data where UserId is in User1Id and in User2Id.
Any idea?
What you are asking for is a classic many-to-many self relationship - (1) user as parent can have many users as children, and (2) user as child can have many users as parents.
Thus it is modelled with one main entity and one join (linking) entity similar to what you have shown. The linking entity does not need special indicator because the two FKs determine the role. i.e. lets change your example with more descriptive names:
public class User
{
public Guid Id { get; set; }
}
public class UserRelation
{
public User Parent { get; set; }
public User Child { get; set; }
public Guid ParentId { get; set; }
public Guid ChildId { get; set; }
}
Now, in pseudo code, given User user, then
user.Parents = db.Users.Where(u => user == u.Child)
user.Children = db.Users.Where(u => user == u.Parent)
EF Core 5.0+ allows you to hide the join entity (it still exists, but is maintained implicitly) and model the relationship with the so called skip navigations, which are the natural OO way of representing such relationship, e.g. the model becomes simply
public class User
{
public Guid Id { get; }
public ICollection<User> Parents { get; set; }
public ICollection<User> Children { get; set; }
}
This is all needed to create such relationship.
However the name of the join table and its columns by convention won't be what normally you would do - in this case, they would be "UserUser" table with "ParentsId" and "ChildrenId" columns.
If you use migrations and don't care about the names, then you are done and can safely skip the rest.
If you do care though, luckily EF Core allows you to change the defaults with fluent configuration (even though in a not so intuitive way):
modelBuilder.Entity<User>()
.HasMany(e => e.Parents)
.WithMany(e => e.Children)
.UsingEntity<Dictionary<string, object>>("UserRelation",
je => je.HasOne<User>().WithMany()
.HasForeignKey("ParentId").IsRequired().OnDelete(DeleteBehavior.Restrict),
je => je.HasOne<User>().WithMany()
.HasForeignKey("ChildId").IsRequired().OnDelete(DeleteBehavior.Restrict),
je => je.ToTable("UserRelations")
.HasKey("ParentId", "ChildId")
);
Here Dictionary<string, object> is the shared type EF Core will use to maintain the join entity in memory (change tracker). And is the most annoying thing in the above configuration since in a future they might change their minds and use different type (there are actually plans to do that in EF Core 6.0), so you'll have to update your mappings. Note that this does not affect the database design, just the memory storage type in EF Core change tracker.
So, because of that and the fact that in some scenarios it is better to work directly with the join entity, you could actually combine both approaches (explicit join entity and skip navigations) and get the best of both worlds.
To do than, you add the explicit entity and (optionally) navigations from/to it. The next is w/o collection navigations from User to UserRelation (with fully defined navigation you would need two ICollection<UserRelation> properties there):
public class User
{
public Guid Id { get; }
public ICollection<User> Parents { get; set; }
public ICollection<User> Children { get; set; }
}
public class UserRelation
{
public User Parent { get; set; }
public User Child { get; set; }
public Guid ParentId { get; set; }
public Guid ChildId { get; set; }
}
and required minimal fluent configuration
modelBuilder.Entity<User>()
.HasMany(e => e.Parents)
.WithMany(e => e.Children)
.UsingEntity<UserRelation>(
je => je.HasOne(e => e.Parent).WithMany(), // <-- here you would specify the corresponding collection nav property when exists
je => je.HasOne(e => e.Child).WithMany(), // <-- here you would specify the corresponding collection nav property when exists
je => je.ToTable("UserRelations")
);
The result is the same database model, but with different in-memory representation of the join entity and ability to query/manipulate it directly. Actually you can do the same with implicit entity, but in type unsafe way using sting names and object values which need to be cast to the appropriate type. This probably will improve in the future if they replace Dictionary<string, object> with some generic type, but for now explicit entity combined with skip navigations looks the best.
You can find (I guess better than mine) explanation of all this in the official EF Core documentation - Many-to-many and the whole Relationships section in general.

Mapping To a Single Lookup Table For Multiple Tables in EF Core

I'm attempting to map to a lookup table that contains values for multiple tables. I cannot modify the database. The table has a one-to-many relationship with multiple tables, but no foreign key constraints. The lookup table has three columns code, category and description. A sample set of the data would look like this:
Lookup table:
code
category
description
1
Role
Admin
2
Role
User
1
Job
Mechanic
2
Job
Plumber
3
Job
Electrician
1
Activity
Work
2
Activity
Overtime
3
Activity
Training
I'm interested in joining the Activity table that looks like this (some columns omitted for clarity):
Activity table:
id
code
hours
1
1
8.0
2
1
8.0
3
2
1.0
4
2
5.3
I want to join the Activity table to the Lookup table using EF Core. If I was writing a SQL statement I'd just have something like this:
SELECT *
FROM Activity
JOIN Lookup ON Lookup.code = Activity.code
AND Lookup.category = 'Activity'
So in EF Core I created my classes to represent my tables:
public class Lookup
{
[Key]
public string Code { get; set; }
public string Category { get; set; }
public string Description { get; set; }
}
public class Activity
{
[Key]
public string Id { get; set; }
public string Code { get; set; }
public double Hours { get; set; }
[NotMapped]
public string LookupCategory { get; set; } = "Activity";
public Lookup ActivityType { get; set; }
}
I also have the following fluent relationship setup:
modelBuilder.Entity<Activity>()
.HasOne<Lookup>(x => x.ActivityType)
.WithMany()
.HasForeignKey(x => x.Code);
But this doesn't work since there are multiple rows with the same code value. How do I get the key constraint to also take in to account the LookupCategory value since it is not mapped?
Following pjs's suggestions I was able to implement the following successfully:
I created a new class that extended Lookup. This class has the attribute NotMapped, which was an important part in getting everything to finally work.
[NotMapped]
public class ActivityType : StaffLookup{ }
In Activity I modified the class to use ActivityType instead of StaffLookup. I also removed the LookupCategory property
public class Activity
{
[Key]
public string Id { get; set; }
public string Code { get; set; }
public double Hours { get; set; }
// This is mapped to our subclass so that we can get the filtered values
public ActivityType ActivityType { get; set; }
}
In my OnModelCreating I removed the existing fluent relationship and added the following new ones:
// This tells EF what column is used as a the filter
modelBuilder.Entity<StaffLookup>()
.HasDiscriminator(x => x.Category);
// This is what filters the Lookup table for us
modelBuilder.Entity<ActivityType>()
.HasDiscriminator()
.HasValue("Activity");
// Since the foreign key isn't mapped in the database
// we need to add this relationship
modelBuilder.Entity<ActivityMain>()
.HasOne(x => x.ActivityType)
.WithMany()
.HasForeignKey(x => x.ActivityCode);
Look at setting up a Table-Per-Hierarchy, TPH, with Category as the discriminator. Then your EF model can have Lookup as the base table, with RoleType, ActivityType, etc. as child entities in your EF model. The relationship would be from ActivityType to Activity, and EF would already know that the only applicable values in ActivityType are the Lookup rows with Category = 'Activity'. You would want to verify all existing data meets those constraints since they aren't enforced in the database.

Many-to-many relationship in EF user instance is NULL [duplicate]

This question already has answers here:
EF Core returns null relations until direct access
(2 answers)
Closed 5 years ago.
I'm using .net core 2 mvc, I tried to build many-to-many relationship between Users and Steps.
the relationship is doen but when I query for the record I get user = null.
Hier is my code:
(applicationUser model):
public class ApplicationUser : IdentityUser
{
public string Name { get; set; }
public List<StepsUsers> StepUser { get; set; }
}
(Steps model):
public class Steps
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public List<StepsUsers> StepUser { get; set; }
}
StepsUsers model:
public class StepsUsers : IAuditable
{
public int StepId { get; set; }
public Steps Step { get; set; }
public string UserId { get; set; }
public ApplicationUser User { get; set; }
}
In DbContext I did this :
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<StepsUsers>()
.HasKey(s => new { s.StepId, s.UserId });
builder.Entity<StepsUsers>()
.HasOne(su => su.Step)
.WithMany(s => s.StepUser)
.HasForeignKey(su => su.StepId);
builder.Entity<StepsUsers>()
.HasOne(su => su.User)
.WithMany(s => s.StepUser)
.HasForeignKey(su => su.UserId);
}
public DbSet<MyApp.Models.StepsUsers> StepsUsers { get; set; }
Now, when I query for an instance of StepsUsers with specific StepId I get all de fields correct except the User field is null
var stepUsers = await _context.StepsUsers.Where(s => s.StepId == id).ToListAsync();
I did the same code for another two tables and it works fine, I don't know why it is like this, any suggestion 1?
The cause of your problems is that your forgot to declare your To-many relations as virtual. Another improvement would be to declare them as virtual ICollection instead of List. After all, what would ApplicationUser.StepUser[4] mean?
If you configure a many-to-many relationship according to the entity framework conventions for many-to-many, you don't need to mention the junction table (StepsUsers). Entity framework will recognize the many-to-many and will create the junction table for you. If you stick to the code first conventions you won't even need the fluent API to configure the many-to-many.
In your design every ApplicationUser has zero or more Steps and every Step is done by zero or more ApplicationUsers.
class ApplicationUser
{
public int Id {get; set;}
// every ApplicationUser has zero or more Steps:
public virtual ICollection<Step> Steps {get; set;}
public string Name {get; set;}
...
}
class Step
{
public int Id {get; set;}
// every Step is performed by zero or more ApplicationUsers:
public virtual ICollection<ApplicationUser> ApplicationUsers {get; set;}
public string Name {get; set;}
...
}
public MyDbContext : DbContext
{
public DbSet<ApplicationUser ApplictionUsers {get; set;}
public DbSet<Step> Steps {get; set;}
}
This is all entity framework needs to know to recognize that you configured a many-to-many relationship. Entity framework will create the junction table for you and the foreign keys to the junction table. You don't need to declare the junction table.
But how am I suppose to do a join if I don't have the junction table?
The answer is: Don't do the join. Use the collections instead.
If you want all ApplicationUsers that ... with all their Steps that ... you would normally do an inner join with the junction table, and do some group by to get the Application users. Ever tried method syntax to join three tables? They look hideous, difficult to understand, error prone and difficult to maintain.
Using the collections in entity framework your query would be much simpler:
var result = myDbContext.ApplicationUsers
.Where(applicationUser => applicationUser.Name == ...)
.Select(applicationUser => new
{
// select only the properties you plan to use:
Name = applicationUser.Name,
Steps = applicationUser.Steps
.Where(step => step.Name == ...)
.Select(step => new
{
// again fetch only Step properties you plan to use
Name = step.Name,
...
})
.ToList(),
});
Entity framework will recognize that joins with the junction table is needed and perform them for you.
If you want Steps that ... with their ApplicationUsers who ... you'll do something similar:
var result = myDbContext.Steps
.Where(step => ...)
.Select(step => new
{
Name = step.Name,
... // other properties
ApplicationUsers = step.ApplicationUsers
.Where(applicationUser => ...)
.Select(applicationUser => new
{
...
})
.ToList(),
});
In my experience, whenever I think of performing a query with a of DbSets using entity framework, whether it is in a many-to-many, a one-to-many or a one-to-one relation, the query can almost always be created using the collections instead of a join. They look simpler, they are better to understand and thus better to maintain.

Creating copy of entities with many to many relationship without duplicating one of the type

I have problem with copying entities with many to many relationship.
I have three entities Company, Role and User defined like this:
Company:
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public virtual IList<User> Users { get; set; }
}
User:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public virtual IList<Role> Roles { get; set; }
}
Role:
public class Role
{
public int Id { get; set; }
public string Name { get; set; }
public virtual IList<User> Users { get; set; }
}
Also, I defined many to many relationship between users and roles:
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
ToTable("TUser");
HasKey(x => x.Id);
HasMany(x => x.Roles).WithMany(x => x.Users).Map(m =>
{
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
m.ToTable("TUserRole");
});
}
}
I used migrations to create tables in db and obviously EF created table TUserRole (so far everything good).
And now, I would like to create copy of company and users but without copying roles (so I want to create new records at the tables TCompany, TUser and TUserRole, but no new records at the TRole).
I thought that something like this would work but I'm getting exception:
Context context = new Context();
var company = context.Companies.Include(x => x.Users.Select(u => u.Roles)).AsNoTracking().SingleOrDefault();
context.Companies.Add(company);
foreach (var user in company.Users)
{
foreach (var role in user.Roles)
{
context.Entry(role).State = EntityState.Unchanged;
}
}
context.SaveChanges();
And the exception is Saving or accepting changes failed because more than one entity of type 'Mackas.EF.Model.Role' have the same primary key value.
I understand why I'm getting this (because there is more than one role with the same ID), but I don't know what should be my approach.
Any suggestions?
I'm using EF 6.1.3.
Using AsNoTracking generally is a good idea to obtain a graph of entities that aren't attached to a context. As you know, adding the root entity (company) to a new context will mark all entities in the graph as Added and copying entities is a piece of cake.
But there's one bummer. AsNoTracking causes EF to materialize a new object for each entity in the result set, because it has no way to track that an entity has already been materialized. So you're OK as long as the object graph only diverges off the root entity. I.e. as long as all associations are 1 - 0..n. It that is true, all entities in the graph will represent exactly one "real" entity.
However, in your case, there's a m - n association between User and Roles. The graph converges. If some users have the same roles, EF will create duplicate Role objects when using AsNoTracking.
[By the way, contrary to EF6, EF-core manages to create unique entities even with AsNoTracking]
The way to go here is to query the object graph by one context, as POCOs, not proxies, and then add/attach it to a second context:
Company company;
using (Context context = new Context())
{
context.Configuration.ProxyCreationEnabled = false;
company = context.Companies.Include(x => x.Users.Select(u => u.Roles))
.SingleOrDefault();
}
using (Context context = new Context())
{
context.Companies.Add(company);
foreach (var user in company.Users.SelectMany(u => u.Roles)
.Distinct())
{
context.Entry(role).State = EntityState.Unchanged;
}
context.SaveChanges();
}
Proxies have a reference to the context they were created by, so you can't attach them to a second context.

EF won't update tables based on my model

At first I created a Person model which only contained the basic properties:
[Table("SGDB_Persons")]
public class Person {
public int Id { get; set; }
[Required]
public string Firstname { get; set; }
[Required]
public string Lastname { get; set; }
[Required]
public Department Department { get; set; }
[Required]
public SourceType SourceType { get; set; }
After I noticed I'm missing something I've added a new PersonData Property:
[Required]
public PersonData PersonData { get; set; }
Unfortunately EF won't update the Database at all - PersonData which at first contained an object of type Person got updated so there is no Person property anymore. On the other hand, EF does not create a new Column for PersonData_Id.
Additionally the ID column is not auto-incrementing (all other table's Id Column do). What's confusing me is the following Constraing which gets created inside my Person table:
CONSTRAINT [FK_dbo.SGDB_Persons_dbo.SGDB_PersonData_Id] FOREIGN KEY ([Id]) REFERENCES [dbo].[SGDB_PersonData] ([Id])
I tried everything (at least I think so). I dropped all tables / the whole database manually, reinstalled EF, executed manual migrations but nothing seems to work.
I think it's this problem which causes me to not be able to seed my database with the following code:
protected override void Seed(PersonContext context) {
base.Seed(context);
var dep = new DepartmentContext().Departments.First();
var status = new Status("Test");
var persondata = new PersonData(status);
context.Status.Add(status);
context.PersonData.Add(persondata);
for (int i = 0; i < 10; i++) {
var person = new Person {
Firstname = $"TestPersonFirstname{i}",
Lastname = $"TestPersonLastname{i}",
SourceType = COM.SourceType.Manual,
Department = dep,
PersonData = persondata
};
context.Persons.Add(person);
}
context.SaveChanges();
}
Everytime this code get's executed I'm getting an Exception:
The member with identity 'SGDB.DAL.Contexts.Person_Department' does not exist in the metadata collection. Parameter name: identity.
I don't know if both problems are related to the same problem but both need to be resolved :)
Thanks in advance!
Update 1
My Solution is divided into a few different Projects:
BLL, DAL, COM, UI
DataContexts are located inside the DAL project, Models inside the COM project.
Department Model:
[Table("SGDB_Departments")]
public class Department {
public int Id { get; set; }
[Required]
public string Costcenter { get; set; }
[Required]
public string Abbreviation { get; set; }
public string Description { get; set; }
public string FullDepartmentName {
get {
return $#"{Division.Abbreviation}\{Abbreviation}";
}
}
[Required]
public virtual Division Division { get; set; }
}
PersonData Model:
[Table("SGDB_PersonData")]
public class PersonData {
public PersonData(Status status) {
Status = status;
}
public int Id { get; set; }
public DateTime Limit { get; set; }
public Person Responsible { get; set; }
[Required]
public Status Status { get; set; }
}
The Person table (as you can see) has got a Department_Id column (EF inserted automatically).
Clarification
A Person object contains a PersonData object as additional Information for this Person. A Person may / may not has a Responsible Person (so PersonData.Responsible is not a Navigation Property to the Parent Person).
Additionaly if possible I don't want to have a foreign Key inside the PersonData table.
As I figured out I'd have to modify
modelBuilder.Entity<Person>()
.HasRequired(e => e.PersonData)
.WithRequiredPrincipal(e => e.Responsible)
.WillCascadeOnDelete(true);
to
modelBuilder.Entity<Person>()
.HasRequired(e => e.PersonData)
.WithMany()
.WillCascadeOnDelete(true);
I'll try this and report in if it solved my problem.
Update 2
The member with identity 'SGDB.DAL.Contexts.Person_Department' does not exist in the metadata collection.
Your model defines one-to-one relationship between Person and PersonData with later being required and the former - optional. EF always uses the required side of the one-to-one relationship as principal and optional part as dependent. Hence it thinks PersonaData is the principal and Person - dependent and reflects that in database table design.
You need the opposite and also both sides being required. When both sides are required or optional, EF cannot automatically derive the principal/dependent side and there is no way to specify that via data annotations (attributes), so you need a fluent API setup.
Override your DbContext OnModelCreating and add something like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasRequired(e => e.PersonData)
.WithRequiredPrincipal(e => e.Responsible)
.WillCascadeOnDelete(true);
base.OnModelCreating(modelBuilder);
}
What it does is to tell EF that both sides of the Person->PersonData relationship are required and Person is the principal. This should make again your Person.Id column auto-increment and should resolve the person-data part of the problem.
The other thing I've noticed is this line:
var dep = new DepartmentContext().Departments.First();
while all other parts of the same procedure are using a variable called context. This might/might not be a problem, just check it out.
UPDATE: From the clarification in the updated question it turns out you have two relationships between Person and PersonData, so you need separate configuration for each of them like this:
modelBuilder.Entity<Person>()
.HasRequired(e => e.PersonData)
.WithRequiredPrincipal()
.WillCascadeOnDelete(true);
modelBuilder.Entity<PersonData>()
.HasOptional(e => e.Responsible)
.WithOptionalDependent() // or WithMany()
.WillCascadeOnDelete(false);
Please note that there is no way to not introduce additional FK column in the PersonData table. It's needed to represent the Responsible relation, so you'll end up with a table column called Responsible_Id.

Categories