Home > Net >  C# EntityFramework relation
C# EntityFramework relation

Time:04-12

I have the following problem with Entity Framework:

Unable to determine the relationship represented by navigation 'Booking.Participants' of type 'List'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.

These are my two classes:

 public class Booking
{
    [Key]
    public int Id { get; set; }

    [Required]
    public Event? Event { get; set; }

    public Invoice? Invoice { get; set; }

    [Required]
    public Participant? TravelApplicant { get; set; }

    public List<Participant>? Participants { get; set; }

    [Required]
    public int NumberOfParticipants
    {
        get
        {
            if (Participants != null)
            {
                if (TravelApplicant != null)
                {
                    if (TravelApplicant.IsTravelApplicantParticipant)
                        return Participants.Count   1;
                    else
                        return Participants.Count;
                }
            }
            return 0;
        }
        set
        {
            if (Participants != null)
            {
                if (TravelApplicant != null)
                {
                    if (TravelApplicant.IsTravelApplicantParticipant)
                        NumberOfParticipants = Participants.Count   1;
                    else
                        NumberOfParticipants = Participants.Count;
                }
            }
        }
    }

    [Required]
    public bool CoronaEntryCheckBox { get; set; }

    [Required]
    public bool AgbCheckBox { get; set; }

    [Required]
    public bool NewsletterCheckBox { get; set; }
}

And

 public class Participant
{
    [Key]
    public int Id { get; set; }

    [Required]
    public Accommodation? Accommodation { get; set; }

    [Required]
    public Booking? Booking { get; set; }

    [Required]
    public Gender Gender { get; set; }

    public string? Title { get; set; }

    [Required]
    public string? FirstName { get; set; }

    [Required]
    public string? LastName { get; set; }

    [Required]
    public DateTime? Birthday { get; set; }

    [Required]
    public string? Street { get; set; }

    [Required]
    public string? HouseNumber { get; set; }

    public string? AdditionalAddress { get; set; }

    [Required]
    public string? ZipCode { get; set; }

    [Required]
    public string? City { get; set; }

    [Required]
    public string? Country { get; set; }

    [Required]
    public string? EMail { get; set; }

    [Required]
    public string? Phone { get; set; }

    [Required]
    public bool IsTravelApplicant { get; set; }

    [Required]
    public bool IsTravelApplicantParticipant { get; set; }

    [Required]
    public bool Vegetarian { get; set; }

    public string? Note { get; set; }
}

I hope one of you knows the solution, thank you in advance :)

CodePudding user response:

How have you defined the relationship when configuring the model?

From the looks of it you still need to let Entity Framework know how the relationship is linked. Ideally you would have the BookingId stored for a Participant, which if you have it marked as Required should not be nullable.

[Required]
public BookingId int {get; set; }

public virtual Booking Booking { get; set; }

I would suspect you actually want it to be defined in the Booking class like so:

public virtual List<Participant> Participants { get; set; } = new List<Participant>();

Then in your database context, you would have something along the lines of this in your OnModelCreating override:

modelBuilder.Entity<Booking>(entity => 
{
    entity.HasMany(x => x.Participants)
});

modelBuilder.Entity<Participant>(entity => 
{
    entity.HasOne(x => x.Booking)
    .HasForeignKey(k => k.BookingId);

});

You can view the full documentation over at https://docs.microsoft.com/en-us/ef/core/modeling/

CodePudding user response:

Try create the relationship in code first mode.

Follow the tutorial: [Configure one to one - Code First][1]

CodePudding user response:

EF auto-linking relationships will not necessarily resolve relationships especially when they double-up. The issue you are seeing is likely because Booking has both a many-to-one relationship with Particilant (booking.TravelApplicant) and a one-to-many (booking.Participants).

Mike's answer should get you sorted to tell EF to use Participant.BookingId to link back to resolve booking.Participants, while you should also configure something like a TravelApplicantId FK on Booking to service the TravelApplicant. EF's convention-based approach tries to find/use FKs based on the Type of the navigation entity rather than the Name of the property. This causes the majority of issues where multiple references seem to fail when relying on convention.

I don't generally recommend structures like this because while it is implied that TravelApplicant would be part of the Participants collection, there is no way to enforce this at the data level. For example if I have participants of PersonA, PersonB, and PersonC where PersonA is the Applicant, there is nothing stopping me from either removing PersonA from participants (leaving them as applicant) or changing the applicant to PersonD which was never added to Participants. You can of course validate this at the application level but there is no guard available at the data level.

Alternatively you can structure it as having an Applicant and the collection is additional participants. This would mean you would always be appending the applicant to the participants when needing all people, and doesn't guard against the applicant being added to the participants relationship.

Instead, the approach I would use would be to use a many-to-many relationship table (BookingParticipants) using something like a SortOrder or an IsApplicant flag to identify the main applicant. This ensures that the main applicant is always an associated participant.

Using a sort-order where the main applicant is the lowest index, and the sort order has a unique constraint so this satisfies protecting the relationships and applicant at the DB level. The flag is more read-able, but lacks the ability to guard at a data level as you could have multiple participants flagged as Applicant, or no participant flagged as applicant. The "Applicant" can be exposed by an entity as an unmapped property the fetches the correct Participant, though I recommend using a naming convention when working with unmapped properties to make it easy to recognize them and ensure they don't end up in EF Linq expressions.

  • Related