EF Core - Shared Primary Key Associations via Fluent API - c#

I have an 'User'class contains two 'Address' properties reference to 'Address' entity, and there is another class - 'Shipment' also associate with 'Address'.
How i can use fluent api on ef core to build a correct relation between entities.
public class Address
{
public int AddressId { get; set; }
public string Street { get; set; }
...
}
public class User
{
public int UserId { get; set; }
public string Name { get; set; }
public virtual Address DefaultAddress { get; set; }
public virtual Address BillingAddress { get; set; }
}
public class Shipment
{
public int ShipmentId { get; set; }
public virtual Address DeliveryAddress { get; set; }
}

There is not need to declare explicit configuration, the EF will do everything without any help.
I prepared a working example with and without fluent configuration, you can check it out here. Just switch between commits to see the difference.
As you can notice, there is no differences in generated migration.

Related

EF Core 2.2.6: Unable to map 2 foreign keys to the same table

I am having issues trying to map two fields that are foreign keys into the same table. The use case is for a modifier and creator. My class already has the Ids, and then I wanted to add the full User object as virtual.
I am using a base class so that each of my tables have the same audit fields:
public class Entity
{
public long? ModifiedById { get; set; }
public long CreatedById { get; set; } = 1;
[ForeignKey("CreatedById")]
public virtual User CreatedByUser { get; set; }
[ForeignKey("ModifiedById")]
public virtual User ModifiedByUser { get; set; }
}
The child class is very simple:
public class CircleUserSubscription : Entity
{
[Required]
public long Id { get; set; }
public long SponsorUserId { get; set; }
[ForeignKey("SponsorUserId")]
public virtual User User { get; set; }
public long TestId { get; set; }
[ForeignKey("TestId")]
public virtual User Test { get; set; }
}
This is a standard junction table.
When I try to generate the migration, I am getting errors that I don't understand fully.
Unable to determine the relationship represented by navigation property 'CircleUserSubscription.User' of type 'User'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I tried what this answer had, but the code is basically the same: https://entityframeworkcore.com/knowledge-base/54418186/ef-core-2-2---two-foreign-keys-to-same-table
An inverse property doesn't make sense since every table will have a reference to the user table.
For reference, here is the User entity:
public class User : Entity
{
public long Id { get; set; }
public string Username { get; set; }
public string Email { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
I am hoping you all can help me out, TIA :)
EDIT: One thing to note, all of this worked fine when the entity class was as follows:
public class Entity
{
public long? ModifiedById { get; set; }
public long CreatedById { get; set; } = 1;
}
It was only after I added the entity that things went awry.

Entity Framework Code First One to One Cascade Deletes

Have and "Address" model used by several other models ("Employee" & "Client").
I would call this a one to one relationship, I could be wrong. The address is required by both of the other models. Remove, deletes only the parent object.
Tried in both EF Core and EF6. Remove deletes the parent object, but not the "Address" object.
public class Address
{
public int AddressID { get; set; }
public string Street { get; set; }
public string CityStateZip { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
[Required]
public virtual Address EmployeeAddress { get; set; }
}
public class Client
{
public int Id { get; set; }
public string Name { get; set; }
[Required]
public virtual Address ClientAddress { get; set; }
}
No error messages - context.remove simply won't delete the child object. Complete noob here when it comes to EF. Sorry, this is probably a very basic question, but please believe that I have searched extensively. Most solutions suggest a foreign key back to the parent - but, in this case, the child object can be used (but not shared) in several different models.
The same Address object can be used in multiple Employee and/or Client instances as currently implemented.
The suggestion you received
Most solutions suggest a foreign key back to the parent - but, in this case, the child object can be used (but not shared) in several different models.
informs Entity Framework that a given Address can only appear in one specific Employee/Client.
You should be able to resolve this by having Employee and Client inherit from a common base class, e.g.
public class Person
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
[Required]
public virtual Address PersonAddress { get; set; }
}
public class Employee : Person
{
// Other properties that make Employee unique go here
}
public class Client : Person
{
// Other properties that make Client unique go here
}
Then add the backreference to the base class
public class Address
{
public virtual int AddressID { get; set; }
public virtual string Street { get; set; }
public virtual string CityStateZip { get; set; }
[Required]
public virtual Person AddressOf { get; set; }
}

Different behavior across domain objects for same properties in Entity Framework Core 2

I am new to .NET Core and using EF Core 2
My domain objects are all derived from a base class with some audit fields on it that get set on SaveChanges as needed:
(Simplified below)
public abstract class AuditableEntity
{
public DateTime CreatedOn { get; set; }
[ForeignKey("CreatedBy")]
public Guid? CreatedByWebUserId { get; set; }
public WebUser CreatedBy { get; set; }
public DateTime? UpdatedOn { get; set; }
[ForeignKey("UpdatedBy")]
public Guid? UpdatedByWebUserId { get; set; }
public WebUser UpdatedBy { get; set; }
public DateTime? DeletedOn { get; set; }
[ForeignKey("DeletedBy")]
public Guid? DeletedByWebUserId { get; set; }
public WebUser DeletedBy { get; set; }
}
On add-migration, I get the error:
Unable to determine the relationship represented by navigation property
'Address.CreatedBy' of type 'WebUser'. Either manually configure the
relationship, or ignore this property using the '[NotMapped]' attribute or by
using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
Address is one of the classes derived from AuditableEntity:
(Simplified below)
public class Address : AuditableEntity
{
public Guid Id { get; set; }
public string Nickname { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string Address3 { get; set; }
public string City { get; set; }
public string StateProvince { get; set; }
public string PostalCode { get; set; }
public string CountryCode { get; set; }
public decimal Latitude { get; set; }
public decimal Longitude { get; set; }
}
However, I have several objects that use the same "agent and timestamp" pair pattern similar to the above that work just fine such as:
public DateTime? VerifiedOn { get; set; }
[ForeignKey("VerifiedBy")]
public Guid? VerifiedByWebUserId { get; set; }
public WebUser VerifiedBy { get; set; }
The error always comes from Address, and if I remove the base class from Address everything works fine (meaning, these fields get successfully applied to my 15+ other domain objects).
The issue seemingly stems from WebUser having a reference to Address:
(Simplified below)
public class WebUser : AuditableEntity
{
public Guid Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public string Phone1 { get; set; }
public string Phone1Type { get; set; }
public string Phone2 { get; set; }
public string Phone2Type { get; set; }
[ForeignKey("AddressId")]
public Address Address { get; set; }
public Guid? AddressId { get; set; }
}
What is the correct way of creating these references prioritizing keeping the FK constraints (over keeping the ability to navigate)?
The problem is unrelated to the usage of a base class (the same will happen if you remove the base class, but copy its properties to Address class), but the multiple cross references between the two classes.
By convention EF Core tries to automatically "pair" navigation properties of the two entities in order to form a single relationship, which succeeds in most of the cases. However in this case the WebUser has Address type navigation property and Address class has WebUser type navigation property (actually 3).
Since all they have associated FK property via ForeignKey data annotation, EF Core should be able to correctly identify them as different one-to-many relationships, but it doesn't. Not only it fails with the exception in question, but also doesn't create FK relationships for the WebUser.
Everything works correctly if the base class contains only 1 WebUser type of navigation property, so I'm assuming thet unfortunately you are hitting some current EF Core bug.
As a workaround until they fixed it, I would suggest explicitly configuring the problematic relationships using fluent API, by overriding the OnModelCreating and adding the following code:
var auditableEntityTypes = modelBuilder.Model.GetEntityTypes().Where(t => t.ClrType.IsSubclassOf(typeof(AuditableEntity)));
var webUserNavigations = new[] { nameof(AuditableEntity.CreatedBy), nameof(AuditableEntity.DeletedBy), nameof(AuditableEntity.UpdatedBy) };
foreach (var entityType in auditableEntityTypes)
{
modelBuilder.Entity(entityType.ClrType, builder =>
{
foreach (var webUserNavigation in webUserNavigations)
builder.HasOne(typeof(WebUser), webUserNavigation).WithMany();
});
}
i.e. for each entity class that derives from AuditableEntity we explicitly configure the 3 WebUser reference navigation properties to be mapped to 3 separate one-to-many relationships with no inverse collection navigation properties. Once we do that, EF Core has no problem to correctly map the WebUser.Address FK association.

Entity Framework relations questions

I have three classes
public class SPR
{
public int ID { get; set; }
public string SubmittedBy { get; set; }
public virtual ICollection<SPRItem> AllItems { get; set; }
}
public class SPRItem
{
[Key]
public int ID { get; set; }
public string manufacturer { get; set; }
[ForeignKey("SPRItemDetails")]
public virtual SPRItemDetails ItemDetails { get; set; }
public string requestedMinimumQuantity { get; set; }
public virtual SPR SPR { get; set; }
}
public class SPRItemDetails
{
public int ID { get; set; }
public string ItemNumber { get; set; }
public virtual SPRItem SPRItem { get; set; }
}
So the SPR class has a collection of SPRItem and which has the ItemDetails object.
I have a web API method which maps the data to the SPR object and fills in the SPRItem list and ItemDetails object. But whenever I am trying to save it using Entity Framework code first I am getting this error
{"Message":"An error has occurred.","ExceptionMessage":"Unable to determine the principal end of an association between the types 'SharePoint.MultiSPR.Service.Models.SPRItemDetails' and 'SharePoint.MultiSPR.Service.Models.SPRItem'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
This is my Context
public System.Data.Entity.DbSet<SharePoint.MultiSPR.Service.Models.SPR> SPRs { get; set; }
public System.Data.Entity.DbSet<SharePoint.MultiSPR.Service.Models.SPRItem> SPRItem { get; set; }
public System.Data.Entity.DbSet<SharePoint.MultiSPR.Service.Models.SPRItemDetails> SPRItemDetails { get; set; }
Can someone please tell me how to configure the relations correctly.
Thanks
In a 1:1 relation you always have to indicate the principal and the dependent entity. The principal entity is the one that is most independent of the other, in this case SPRItem, presumably.
Next thing to decide is whether the relationship should be optional or required. I think, judging by the entity names, an SPRItemDetails will never exist without an SPRItem, so the relationship is 1:0..1 (not 0..1:0..1). Here's how to configure that:
modelBuilder.Entity<SPRItem>()
.HasOptional(si => si.ItemDetails)
.WithRequired(id => id.SPRItem);
This creates (or requires) an SPRItemDetails table having a primary key that's also a foreign key to SPRItem.

Entity Framework --Unable to determine the principal end of an association between the types?

I am creating entities using code first schema but when run the application its generating exception
Unable to determine the principal end of an association between the types 'WebApplication1.Models.DateOfProject' and 'WebApplication1.Models.Projects'. The principal end of this association must be explicitly configured using either the relationship fluent API or data annotations.
My scenario is to implement 1.1 relation between Projects and DateOfProjects such that 1 project has 1 dateOfProject.
My code is
public class Projects
{
[Key()]
[DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
public int ProjectId { get; set; }
public string ProjectTitle { get; set; }
public string ProjectDescriptions { get; set; }
public DateOfProject DateOfProject { get; set; }
public virtual ICollection<ApplicationUser> ApplicationUser { get; set; }
public virtual ICollection<TaskSheetManagement> TaskSheetManagement { get; set; }
}
public class DateOfProject
{
public int DateOfProjectId { get; set; }
[ForeignKey("ProjectId")]
public Projects Projects { get; set; }
public DateTime DateOfProjectCreation { get; set; }
public Nullable<DateTime> ExpectedCompletionDate { get; set; }
public Nullable<DateTime> ProjectCompletionDate { get; set; }
}
and inside DbContextClass inOnModelCreating function
modelBuilder.Entity<Projects>().HasKey(pk => pk.ProjectId).ToTable("Projects");
modelBuilder.Entity<DateOfProject>().HasKey(pk => pk.DateOfProjectId).ToTable("DateOfProject");
modelBuilder.Entity<Projects>().HasRequired(p => p.DateOfProject).WithRequiredPrincipal(c => c.Projects);
I could not just resolve that problem.
If you want a 1 : 1 relationship you have to remove the [ForeignKey("ProjectId")] Attribute. For 1: 1 relationships the Primary Key is used. If you want a separate foreign Key column it is a 1 : * relationship.

Categories