EF - Many-to-many configuration where both sides have same mapping property - c#

I'm attempting to create a many-to-many mapping between User and Group models. Here are my classes:
public abstract class Entity
{
public Guid Id { get; set; }
public DateTime Created { get; set; }
public DateTime? Modified { get; set; }
}
public class User : Entity
{
public string Email { get; set; }
public string Name { get; set; }
public virtual ICollection<Group> Groups { get; set; }
}
public class Group : Entity
{
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
As you can see, I'm using the Entity abstract class to implement common properties in the classes that inherit from it. In this case, Id will be the key property for all of my EF classes.
Here is my configuration file where I map the many-to-many relationship:
public GroupConfiguration()
{
Property(x => x.Id).IsRequired().HasDatabaseGeneratedOption(databaseGeneratedOption: DatabaseGeneratedOption.Identity);
Property(x => x.Name).HasMaxLength(50).IsRequired();
HasMany(g => g.Users)
.WithMany(u => u.Groups)
.Map(m =>
{
m.MapLeftKey("Id");
m.MapRightKey("Id");
m.ToTable("UserGroups");
});
}
When I attempt to add a migration, I get the following error: Id: Name: Each property name in a type must be unique. Property name 'Id' is already defined. It seemingly doesn't like the fact that the property being mapped on both sides has the same identifier. When I don't inherit from Entity for one of the classes, eg. User, and make the mapping property UserId it is able to successfully create a migration.
Is there any way around this? It would be to be able to use an Id property for all of my entities defined in an abstract class.
Thanks in advance.

You can't have 2 keys with the same name, it will represent the columns for your relationship table (named "UserGroups"). When you call "MapLeftKey" or "MapRightKey", you define the columns name.
So I suggest you to rename your Ids (UserId and GroupId for example) and your mapping should be alright. I think you cannot have another solution on using fluent API.

Related

EF Core unable to determine relationship represented by foreign keys in abstract class

I'm trying to implement an abstract class for CRUD objects which contains two references to the User class; one for the User which created the object and the other for the User which last modified it. Entity framework is unable to determine the relationship represented by the CreatedBy and ModifiedBy navigation properties on the class inheriting them (Department). A potential additional complication is that the User class also has a property of class Department, which is unrelated to the CreatedBy and ModifiedBy properties on Department.
Error message is below:
Unable to determine the relationship represented by navigation property 'Department.CreatedBy' of type 'User'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I'm using dotnet 3.0 and EF Core 3.0
I've attempted various configurations of the ForeignKey data attribute and using Fluent API following the EF documentation, but I was not able to get them to work.
public class User
{
[Key]
public int Id { get; set; }
public Department Department { get; set; } = null!;
}
public class Department : AbstractCrudObject
{
}
public abstract class AbstractCrudObject
{
[Key]
public int Id { get; set; }
[ForeignKey("CreatedByUserId")]
public User CreatedBy { get; set; } = null!;
public int CreatedByUserId { get; set; }
[ForeignKey("ModifiedByUserId")]
public User ModifiedBy { get; set; } = null!;
public int ModifiedByUserId { get; set; }
}
public class AppDbContext : DbContext
{
public virtual DbSet<User> Users { get; set; } = null!;
public virtual DbSet<Department> Departments { get; set; } = null!;
public AppDbContext()
{
}
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
}
}
A potential additional complication is that the User class also has a property of class Department, which is unrelated to the CreatedBy and ModifiedBy properties on Department.
That's the problem - EF Core does not know whether they are unrelated, or it is related to one of them (and which one), so you have to configure that explicitly using the fluent API.
The bare minimum is to specify the multiplicity and navigation properties of the desired relationships - in this case, 3 many-to-one relationships with reference navigation property at the dependent ends and no collection navigation property at the principal ends:
modelBuilder.Entity<User>()
.HasOne(e => e.Department)
.WithMany();
modelBuilder.Entity<Department>()
.HasOne(e => e.CreatedBy)
.WithMany();
modelBuilder.Entity<Department>()
.HasOne(e => e.ModifiedBy)
.WithMany();

Self referencing loop detected for property when inserting into database

I am trying to learn the C# Fluent API, and Im running into issues (I think) with my model setup. I have three tables: OrderFile, Order, LineItem. The error:
Self referencing loop detected for property 'order' with type 'BaseService.WebApi.Order'. Path 'orders[0].lineItems[0]'.
My structure:
OrderFile contains List<Orders>
Order contains List<ListItems> and a Navigation property OrderFile
ListItem contains a Navigation property Order
They are tied together with ForeignKey constraints specified in a Fluent API. Is something wrong with the constraints? I was trying to follow this example for Foreign keys
modelBuilder.Entity<OrderFile>(e =>
{
//many orders within one order file
//the FK relates the OrderFile to the nav key of the Order
e.HasMany(of => of.Orders)
.WithOne(o => o.orderFile)
.HasForeignKey(o => o.FileGuid);
e.HasKey(o => o.FileGuid);
});
modelBuilder.Entity<Order>(e =>
{
//each order has an array of line items
//each line item has one order (navigation property)
//the foreign key of the line item ties it to the Parent (List<Order>)
e.HasMany(o => o.LineItems)
.WithOne(li => li.order)
.HasForeignKey(o => o.OrderGuid);
e.HasKey(o => o.OrderGuid);
});
Models
public class OrderFile
{
public Guid FileGuid { get; set; }
public virtual ICollection<Order> Orders { get; set; } //everything with same FileGuid
}
public class Order
{
....
[JsonIgnore]
public Guid FileGuid { get; set; }
[Key]
public Guid OrderGuid { get; set; }
[JsonIgnore]
public OrderFile orderFile { get; set; }
public virtual ICollection<LineItem> LineItems { get; set; } //everything with same OrderGuid
}
public class LineItem
{
....
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public Guid OrderGuid { get; set; }
public Order order { get; set; }
}
Your LineItem entity has a reference to Order, which doesn't have a JsonIgnore attribute.
Basically your problem stems from trying to serialize an object graph that has circular dependencies (loops), while your design problem is that you use database entity classes in your API. The client facing models should be different classes than the entities you persist in the database.

How do I link two properties to one class but only being able to reference to one in EFC Code First?

I have two Classes:
public class User
{
public Guid UserId { get; set; }
[Required]
public string Username { get; set; }
[Required]
public string Password { get; set; }
public UserProfile UserProfile { get; set; }
}
public class UserProfile
{
public int UserProfileId { get; set; }
public string Avatar { get; set; }
public User User { get; set; }
public User Wingman { get; set; }
}
Trying to add a migration understandably i get the following error:
Unable to determine the relationship represented by navigation
property 'User.UserProfile' of type 'UserProfile'. Either manually
configure the relationship, or ignore this property using the
'[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in
'OnModelCreating'.
UserProfile.User is the user itself and UserProfile.Wingman is another user representig the wingman.
Do i need another Table for that like a bridge or is there another way to resolve that problem?
And I don't need to reference Wingman from the User.
Thanks in advance!
You can use the InversePropertyAttribute or the Fluent API config methods HasOne/WithOne or HasOne/WithMany with appropriate property selectors.
If you’re looking for a 1:1 relationship, use a shard primary key - make the PK for UserProfile the FK to User.
modelBuilder.Entity<UserProfile>()
.HasOne( up => up.User )
.WithOne( u => u.UserProfile )
.HasForeignKey( up => up.UserProfileId ); // I suggest renaming PK to UserId for clarity
If 1:N relationship, two ways; first, w/ InversePropertyAttribute:
public class User
{
...
[InverseProperty( "User" )]
public ICollection<UserProfile> UserProfiles { get; set; }
}
Or via Fluent API:
modelBuilder.Entity<UserProfile>()
.HasOne( up => up.User )
.WithMany( u => u.UserProfiles );

Many-to-one relationship

I have a schema Definitions which I would like to be able to reference itself. As I need meta data about the reference, there's a coupling schema named Associations. I'm using Entity Framework's fluent API in conjunction with data annotation attributes.
Definitions:
public class Definition
{
[Key]
public int Id { get; set; }
// ...
public virtual ICollection<Association> Associations { get; set; }
}
Associations:
public class Association
{
[Key]
public int Id { get; set; }
public int TypeId { get; set; }
public int AssociatedDefinitionId { get; set; }
public int RootDefinitionId { get; set; }
public virtual AssociationType Type { get; set; }
public virtual Definition AssociatedDefinition { get; set; }
public virtual Definition RootDefinition { get; set; }
}
OnModelCreating:
modelBuilder.Entity<Association>()
.HasRequired(p => p.AssociatedDefinition)
.WithRequiredPrincipal();
modelBuilder.Entity<Association>()
.HasRequired(p => p.RootDefinition)
.WithRequiredPrincipal();
I use MySQL as the database engine.
When I try to save a definition entity with an empty association collection, I get a constraint violation:
Cannot add or update a child row: a foreign key constraint fails
("u0228621_8"."Definitions", CONSTRAINT
"FK_Definitions_Associations_Id" FOREIGN KEY ("Id") REFERENCES
"Associations" ("Id"))
What am I doing wrong?
You have defined your association class with all relationships being "required:required" because of the WithRequiredPrincipal which doesn't seem to be what you want. Since the Associations collection appears (from the comments) to be the relation from the Root definitions, the mapping should come from definition, like so:
// Foreign key mappings included.
modelBuilder.Entity<Definition>().HasMany(d => d.Assocations)
.WithRequired(a => a.RootDefinition).HasForeignKey(a => a.RootDefinitionId);
modelBuilder.Entity<Association>().HasRequired(a => a.AssociatedDefinition)
.HasForeignKey(a => a.AssociatedDefinitionId);
So the Associations collection may be empty, but every Association requires a RootDefinition and AssociatedDefinition.

EF4 Code-First CTP5 Many-to-one

I've been trying to get EF4 CTP5 to play nice with an existing database, but struggling with some basic mapping issues.
I have two model classes so far:
public class Job
{
[Key, Column(Order = 0)]
public int JobNumber { get; set; }
[Key, Column(Order = 1)]
public int VersionNumber { get; set; }
public virtual User OwnedBy { get; set; }
}
and
[Table("Usernames")]
public class User
{
[Key]
public string Username { get; set; }
public string EmailAddress { get; set; }
public bool IsAdministrator { get; set; }
}
And I have my DbContext class exposing those as IDbSet
I can query my users, but as soon as I added the OwnedBy field to the Job class I began getting this error in all my tests for the Jobs:
Invalid column name 'UserUsername'.
I want this to behave like NHibernate's many-to-one, whereas I think EF4 is treating it as a complex type. How should this be done?
UserUsername is the default name that EF Code First chooses for the foreign key in the Jobs table. Obviously, it's not the same name that your existing database has for the FK column in Jobs table. You need to override this conventions and change the FK's default name so that it matches your database. Here is how it's done with fluent API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Job>()
.HasOptional(j => j.OwnedBy)
.WithMany()
.IsIndependent()
.Map(m => m.MapKey(u => u.Username, "TheFKNameInYourDB"));
}
Try let it create new database from your schema and look what columname it expect.
I think its foreing key for OwnedBy. It tries to name it according to its internal convention, that is incompatible with how you named your foreing key column. And no, its really treating it as many-to-one.

Categories

Resources