Self-referencing relationship in EF Core 5 - entity-framework-core

I'm trying to create a tree structure in the db where items can reference each other. I was considering that to be a simple task, however, EF Core decided to strike. I can't get it to generate the correct foreign keys, it keeps throwing an exception:
Unable to determine the relationship represented by navigation 'Item.Parent' of type 'Item'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
My class looks like this (simplified):
public class Item
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[ForeignKey("Parent")]
public Guid? ParentId { get; set; }
public virtual Item Parent { get; set; }
[InverseProperty("Parent")]
public virtual IEnumerable<Item> Children { get; set; }
}
I have even tried to remove the Children property, but with the same result.
Another thing I have tried was Fluent API:
entity
.HasOne(e => e.Parent)
.WithMany(e => e.Children)
.HasForeignKey(e => e.ParentId);
//or
entity
.HasOne<Item>(e => e.Parent)
.WithMany()
.HasForeignKey(e => e.ParentId);
//or
entity
.HasMany(e => e.Children)
.WithOne(e => e.Parent)
.HasForeignKey(e => e.ParentId);
//or
entity
.HasMany(e => e.Children)
.WithOne(e => e.Parent);
They all keep generating the same error, I'm kind of lost on what am I doing wrong.
I was looking at some other answers too and they look pretty much like what I'm trying to do, however, my EF will not generate the migration.

Disregard this question, I've figured out my own mistake. I will leave it here in case someone faces the same problem as the error message is very misleading.
What has happened was a copy&paste issue where I have created another configuration for an unrelated entity, but forgot to update the type:
{
public void Configure(EntityTypeBuilder<Item> entity)
{
entity
.HasNoKey();
}
}
Notice the Item type in the generics definition. This lead the EF Core to wipe out the key that was originally set on the entity and made EF Core confused about the relationship (as now I'm targeting foreign key to a property that's neither key nor index).

Related

Entity Framework Core code first many-to-many save with only foreign keys [duplicate]

This question already has an answer here:
What is the correct way to do many to many entity relation insert?
(1 answer)
Closed 11 months ago.
I have a many-to-many relationship established code-first that works, with thousands of fake records generated for an API. Now I'm trying to save a new record on one side of that relationship given only the ids of the other side, as the client is passing in an array of int ids.
I've found plenty of questions with problems and answers about saving many-to-many in general, but none specifically about doing so with just a list of foreign keys. Perhaps I'm simply using the wrong terminology?
I could grab all the records for those ids up front, but it seems very heavy to wait for a database query, assign those entities to the new entity, and then go to the database again to save, when all I really need is to establish a relationship with ids I already have.
For single relationships I would just add the foreign key as a separate property and set that instead of the foreign entity itself:
public int? CarId { get; set; }
[ForeignKey("CarId")]
public CarModel? Car { get; set; }
Is there perhaps a similar paradigm for many-to-many?
Entity setup:
public class ClownModel {
public int Id { get; set; }
public List<CarModel> Cars { get; set; }
}
public class CarModel {
public int Id { get; set; }
public List<ClownModel> Clowns { get; set; }
}
DB Context OnModelCreating:
builder.Entity<ClownModel>()
.HasMany(x => x.Cars)
.WithMan(x => x.Clows);
You can use a "stub entity" to add an existing Car to a new or existing Clown without fetching the Car. Eg
var newClown = new Clown();
var car = new Car() { Id = carId };
db.Entry(car).State = EntityState.Unchanged;
newClown.Cars.Add(car);
db.Set<Clown>().Add(newClown);
db.SaveChanges();
Or include the linking entity in your model, which you can do without adding a DbSet property or changing the Many-to-Many navigation properties.
eg
builder.Entity<Clown>()
.HasMany(x => x.Cars)
.WithMany(x => x.Clowns)
.UsingEntity<ClownCar>(
c => c.HasOne(x => x.Car)
.WithMany()
.HasForeignKey(x => x.CarId),
c => c.HasOne(c => c.Clown)
.WithMany()
.HasForeignKey(c => c.ClownId)
);
then
var newClown = new Clown();
var clownCar = new ClownCar();
clownCar.CarId = carId;
clownCar.Clown = newClown;
db.Set<ClownCar>().Add(clownCar);
db.SaveChanges();

Self referencing / parent-child relationship one-to-zero or one in Entity Framework Core

I want to create a referencing / parent-child relationship one-to-zero or one in Entity Framework Core. I mean that my entity could have a parent:
public class MyEntity
{
public Guid Id { get; set; }
public Guid? ParentEntityId { get; set; }
public MyEntity ParentEntity { get; set; }
public MyEntity ChildEntity { get; set; }
}
I am trying to configure it via fluent api:
entity.HasOne(x => x.ParentEntity)
.WithOne(x => x.ChildEntity)
.HasForeignKey( .... )
I do not understand what I do have to write in the last line. I am not either sure my entity is correct.
Can anyone help me please?
EDIT: This question does not resolve my problem: Self referencing / parent-child relationship in Entity Framework
My problem is about create the foreign key. This line does not work:
.HasForeignKey(x => x.ParentEntityId)
HasForeignKey expects a string in input.
In a one-to-one relationship you always have to specify the dependent entity type in the HasForeignKey call, i.e. the entity that will contain the foreign key. For a one-to-one relationship between two different classes that makes sense, see the standard EF example. For a self-reference it looks obvious that EF should figure out there's no option. Still, you have to specify the type:
modelBuilder.Entity<MyEntity>()
.HasOne(x => x.ParentEntity)
.WithOne(x => x.ChildEntity)
.HasForeignKey<MyEntity>(c => c.ParentEntityId);

Entity Framework Core : invalid column name 'UserId1'

I am trying to use Entity Framework Core / .NET 5 to interact with my databases.
When I try to query DbContent.UserClaims I get the following error:
Microsoft.Data.SqlClient.SqlException (0x80131904): Invalid column name 'UserId1'.
I am not sure where UserId1 us coming from. I have a property called UserId which is the foreign key. Here are the relation mapping
Here is what I tried to do in the DbContext class
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<User>(user =>
{
user.HasKey(r => r.Id);
user.HasMany(x => x.UserRoles).WithOne().HasForeignKey(x => x.UserId);
user.HasMany(x => x.UserClaims).WithOne().HasForeignKey(x => x.UserId);
user.HasMany(x => x.UserTokens).WithOne().HasForeignKey(x => x.UserId);
});
builder.Entity<UserClaim>(userClaim =>
{
userClaim.HasKey(r => r.Id);
userClaim.HasOne(r => r.User).WithOne().HasForeignKey<UserClaim>(x => x.UserId);
});
}
Here is the UserClaim class which is derived from IdentityUserClaim
public class UserClaim : IdentityUserClaim<string>
{
public virtual User User { get; set; }
}
Here is the User class which is derived from IdentityUser
public class User : IdentityUser<string>
{
public virtual ICollection<UserToken> UserTokens { get; set; }
public virtual ICollection<UserRole> UserRoles { get; set; }
public virtual ICollection<UserClaim> UserClaims { get; set; }
}
Here is the query that EF5 is generating
SELECT [u].[Id], [u].[ClaimType], [u].[ClaimValue],[u].[UserId], [u].[UserId1]
FROM [UserClaims] AS [u]
How can I fix this issue in Entity Framework Core?
You're using shadow properties here, and on top of that, trying to add UserId foreign key to the User itself. Since UserId is an already defined property in that class, it's adding a suffix to the property name every time you're trying to add a foreign key in the user table by the same name.
It should be something like this:
modelBuilder.Entity<UserClaim>()
.Property<int>("UserForeignKey");
modelBuilder.Entity<UserClaim>()
.HasOne(a => a.User)
.WithMany(b => b.UserClaims)
.HasForeignKey("UserForeignKey")
Read the documentation on how to configure Fluent API for shadow properties, and some other ways to use the Fluent API.

Navigation Properties not re-wired correctly after importEntities()

Background:
I'm using the SandboxManager format described in the documentation to export my entities to a second manager for changes, and then the entity is imported back into the main manager. I'm creating the Sandbox on page initialization from a copy of the main manger using createEmptyCopy(), giving the Sandbox the same metadata and no entities.
During the export, I pass the entity over this way:
function exportEntityToSandbox(entity) {
var exportData = STEP.EntityManager.exportEntities([entity], false);
var result = STEP.SandboxManager.importEntities(exportData,
{ mergeStrategy: breeze.MergeStrategy.OverwriteChanges });
// ImportEntities changes the service name so revert it back
STEP.SandboxManager.setProperties({
dataService: new breeze.DataService({
serviceName: 'api/Sandbox',
hasServerMetadata: false
})
});
return result.entities[0];
};
I'm currently working with this entity:
public class License
{
public int ID { get; set; }
public int LicenseTypeID { get; set; }
public virtual LicenseType LicenseType { get; set; }
public int ExternalProductID { get; set; }
public virtual ExternalProduct ExternalProduct { get; set; }
public int? LicensesPurchased { get; set; }
public int? LicensesAllocated { get; set; }
public string AllocationDescription { get; set; }
public bool DeletedFlag { get; set; }
}
And the map for this entity:
public LicenseMap()
{
this.HasKey(t => t.ID);
this.Property(t => t.ID)
.HasColumnName("ID")
.HasColumnType("int")
.IsRequired();
this.Property(t => t.LicenseTypeID)
.HasColumnName("LICENSE_TYPE_ID")
.HasColumnType("int")
.IsRequired();
this.Property(t => t.ExternalProductID)
.HasColumnName("PRODUCT_ID")
.HasColumnType("int")
.IsRequired();
this.Property(t => t.LicensesPurchased)
.HasColumnName("LICENSES_PURCHASED")
.HasColumnType("int")
.IsOptional();
this.Property(t => t.LicensesAllocated)
.HasColumnName("LICENSES_ALLOCATED")
.HasColumnType("int")
.IsOptional();
this.Property(t => t.AllocationDescription)
.HasColumnName("ALLOCATION_DESCRIPTION")
.HasColumnType("varchar")
.HasMaxLength(Int32.MaxValue)
.IsOptional();
this.Property(t => t.DeletedFlag)
.HasColumnName("DELETED_FLAG")
.HasColumnType("bit")
.IsRequired();
this.ToTable("LICENSE");
}
}
I'm only passing in the License entity during export--the ExternalProduct and LicenseType navigation entities are not passed to the Sandbox. However, ALL ExternalProducts & LicenseTypes are loaded into the Main manager on page initialization. So in the workflow, I select from a list of dropdowns the ExternalProduct and update ONLY the ExternalProductId (as the ExternalProduct itself is not on the Sandbox).
Issue:
The problem I'm facing is when the entity is exported back into the Main Manager:
function save(id, manager, tag) {
manager = manager || STEP.SandboxManager;
return manager.saveChanges()
.then(saveSucceeded)
.fail(saveFailed);
function saveSucceeded(data) {
var exportData = STEP.SandboxManager.exportEntities(data.entities, false);
STEP.EntityManager.importEntities(exportData,
{ mergeStrategy: breeze.MergeStrategy.OverwriteChanges });
// ImportEntities changes the service name
// Revert it back
STEP.EntityManager.setProperties({
dataService: new breeze.DataService({
serviceName: 'api/Datamart',
hasServerMetadata: false
})
});
// Get a reference to the same entity in the Sandbox and update observable
var entityKey = data.entities[0].entityAspect.getKey();
var type = entityKey.entityType.shortName;
var entityManagerEntity = STEP.EntityManager.getEntityByKey(type, id);
};
function saveFailed(msg) {
// Do stuff
};
};
The entity has a new ExternalProductId (changed during edit), but still has the old ExternalProduct navigation entity. The new navigation entity is not wired up to License even though I know it's in the cache. The navigation property still points to the old entity (property License.ExternalProductId does not equal License.ExternalProduct.ID).
So am I expecting too much of Breeze to do this rewiring upon import and I will need to do this manually every time?
I thought this may a problem with my EF definition and have tried adding each of these to the LicenseMap with no success:
this.HasRequired(m => m.ExternalProduct)
.WithOptional()
.Map(m => m.MapKey("ExternalProductID"));
this.HasRequired(t => t.ExternalProduct
.WithMany()
.HasForeignKey(t => t.ExternalProductID)
.WillCascadeOnDelete(false);
This is a required relationship with the navigation property on the License entity only.
I'm using Breeze v1.4.11
Edit:
I just made sure this was in the entity map:
this.HasRequired(t => t.ExternalProduct)
.WithMany()
.HasForeignKey(t => t.ExternalProductID);
And tested this simple coding snippet:
license.ExternalProductID(816);
var test = license.ExternalProduct();
And the test variable of navigation entity ExternalProduct still does not change after setting the ID directly. According to Breeze documentation the navigation entity should be updated unless I'm doing something wrong?
You have found a bug. Setting the FK to a value should cause the corresponding navigation property to be refreshed. That works properly if the corresponding entity is in cache. Unfortunately, if the corresponding entity is NOT in cache, Breeze v.1.5.1 does not null-out the navigation property as it should.
I will be back when that is fixed. We will jump on that. Stay tuned.
I got this figured out.
The problem was I didn't have all of the ExternalProducts in cache so when changing the navigation property ID, the associated ExternalProduct entity wasn't available to wire up from the new ID. When I make sure the new ExternalProduct is in cache, changing the ID on License updates the ExternalProduct properly.
I could argue that Breeze should set the ExternalProduct to null when License.ExternalProductID is changed to a value for an entity not in cache but I digress.

Modeling a composite key of foreign keys (modelled as entity references) in EF6

I am coming from nHibernate and am trying to create an entity that has a 2 column composite key where both columns are also foreign keys.
For example I have a UserRole table that is (UserId Guid, RoleId Guid). I want to model this as
public class UserRole
{
public User User { get; set; }
public Role Role { get; set; }
}
EF doesn't seem to like this idea though. It seems to want me to also add Guid UserId {get;set;} and Guid RoleId { get; set; }. I managed to resolve this for the handling the FK part by defining the model in the dbcontext like so:
modelBuilder.Entity<UserRole>()
.HasRequired(x => x.Role)
.WithRequiredPrincipal()
.Map(x => x.MapKey("RoleId"));
modelBuilder.Entity<UserRole>()
.HasRequired(x => x.User)
.WithRequiredPrincipal()
.Map(x => x.MapKey("UserId"));
Which I hope I can turn into a convention. However when I tried to do this too:
modelBuilder.Entity<UserRole>().HasKey(x => new { x.User, x.Role });
it craps out at runtime with:
The property 'User' cannot be used as a key property on the entity 'Paroxysm.Domain.UserRole' because the property type is not a valid key type. Only scalar types, string and byte[] are supported key types.
FYI this is done in nHibernate byCode mapping like this (slightly different example):
public class ProjectUserProfileMap : ClassMap<ProjectUserProfile>
{
public ProjectUserProfileMap()
{
CompositeId()
.KeyReference(x => x.User, "UserId")
.KeyReference(x => x.Project, "ProjectId");
ReadOnly();
References(x => x.User, "UserId");
References(x => x.Project, "ProjectId");
Map(x => x.IsActive);
Map(x => x.ActivatedUtcDate).Not.Nullable().CustomType<NHibernate.Type.UtcDateTimeType>();
Map(x => x.InvitedUtcDate).Not.Nullable().CustomType<NHibernate.Type.UtcDateTimeType>();
Table("ProjectUserProfile");
}
}
So easy! Incidentally that little CustomType UTC behaviour doesn't seem to be supported by EF either.
Problem is not actually related to the fact that I have a composite key but having a single column PK which is also an FK would be a weird case (1:1 rel).
So I guess I want to know definitely if this can or cannot be done in EF6. The error message certainly indicates its not doable. Can someone confirm?
You could achieve this but only if you add to UserRole 2 foreign key properties: RoleId and UserId. This is because HasKey do not offer any mapping functionality - it can be defined only on properties existing in entity classes. It seems EF enforces that all Primary Key columns are always defined as concrete properties in classes wheres foreign key columns may not have corresponding properies defined. So to achieve what you want you need to define UserRole like this:
public class UserRole
{
public User User { get; set; }
public int UserId { get; set; }
public Role Role { get; set; }
public int RoleId { get; set; }
}
modelBuilder.Entity<UserRole>()
.HasRequired(x => x.Role)
.WithRequiredPrincipal()
.HasForeignKey(x => x.RoleId);
modelBuilder.Entity<UserRole>()
.HasRequired(x => x.User)
.WithRequiredPrincipal()
.HasForeignKey(x => x.UserId);
modelBuilder.Entity<UserRole>().HasKey(x => new { x.UserId, x.RoleId });
The exact situation as you posted you might alternatively achieve by many-to-many relationship:
modelBuilder.Entity<User>()
.HasMany(x => x.Roles)
.WithMany()
.Map(m =>
{
m.ToTable("UserRole");
m.MapLeftKey("UserId");
m.MapRightKey("RoleId");
});
With this you would achieve UserRole table with primary key defined on UserId and RoleId.