简体   繁体   中英

Configuring EF Core one to many mapping of different properties correctly

I'm using AutoMapper to map classes from Camille to my own domain classes, which I plan on storing using EF Core in my .NET 5 REST API. One particularly nasty class has been causing me a sea of troubles though. The ParticipantTimeline class contains several dictionaries with stats, as you can see below:

namespace MingweiSamuel.Camille.MatchV4
{
  public class ParticipantTimeline
  {
    [JsonPropertyName("participantId")]
    public int ParticipantId { get; set; }

    [JsonPropertyName("csDiffPerMinDeltas")]
    public IDictionary<string, double> CsDiffPerMinDeltas { get; set; }

    [JsonPropertyName("damageTakenPerMinDeltas")]
    public IDictionary<string, double> DamageTakenPerMinDeltas { get; set; }

    [JsonPropertyName("role")]
    public string Role { get; set; }

    [JsonPropertyName("damageTakenDiffPerMinDeltas")]
    public IDictionary<string, double> DamageTakenDiffPerMinDeltas { get; set; }

    [JsonPropertyName("xpPerMinDeltas")]
    public IDictionary<string, double> XpPerMinDeltas { get; set; }

    [JsonPropertyName("xpDiffPerMinDeltas")]
    public IDictionary<string, double> XpDiffPerMinDeltas { get; set; }

    [JsonPropertyName("lane")]
    public string Lane { get; set; }

    [JsonPropertyName("creepsPerMinDeltas")]
    public IDictionary<string, double> CreepsPerMinDeltas { get; set; }

    [JsonPropertyName("goldPerMinDeltas")]
    public IDictionary<string, double> GoldPerMinDeltas { get; set; }

    [JsonExtensionData]
    public Dictionary<string, object> _AdditionalProperties { get; set; } = new Dictionary<string, object>();

    //Methods omitted for brevity
  }

Up until now, I was mapping to my domain classes as follows:

CamilleProfile.cs:

// Omitted for brevity
CreateMap<ParticipantTimeline, ParticipantTimelineDto>();
CreateMap<KeyValuePair<string, double>, ParticipantTimelineDelta>()
    .ForMember(x => x.Delta, conf => conf.MapFrom(kvp => kvp.Key))
    .ForMember(x => x.Value, conf => conf.MapFrom(kvp => kvp.Value));

ParticipantTimelineDto.cs:

public class ParticipantTimelineDto
{
    public int ParticipantId { get; set; }
        
    public IEnumerable<ParticipantTimelineDelta> CsDiffPerMinDeltas { get; set; }
        
    public IEnumerable<ParticipantTimelineDelta> DamageTakenPerMinDeltas { get; set; }
        
    public string Role { get; set; }
        
    public IEnumerable<ParticipantTimelineDelta> DamageTakenDiffPerMinDeltas { get; set; }
        
    public IEnumerable<ParticipantTimelineDelta> XpPerMinDeltas { get; set; }
        
    public IEnumerable<ParticipantTimelineDelta> XpDiffPerMinDeltas { get; set; }
        
    public string Lane { get; set; }
        
    public IEnumerable<ParticipantTimelineDelta> CreepsPerMinDeltas { get; set; }
   
    public IEnumerable<ParticipantTimelineDelta> GoldPerMinDeltas { get; set; }
}

ParticipantTimelineDelta.cs:

public class ParticipantTimelineDelta
{
    public string Delta { get; set; }
    public double Value { get; set; }

    public ParticipantTimelineDelta()
    {
    }
}

This works fine without EF, but now I'm trying to add migrations for identities, and I can't figure out my mappings with EF. When trying to create migrations, I get the following message:

There are multiple relationships between 'ParticipantTimelineDelta' and 'ParticipantTimelineDto' without configured foreign key properties. This will cause Entity Framework to create shadow properties on 'ParticipantTimelineDelta' with names dependent on the discovery order.

This is just a warning, and I understand why it's saying that. It's the actual error which then confuses me most. It is as follows:

The entity type 'ParticipantTimelineDelta' requires a primary key to be defined. If you intended to use a keyless entity type, call 'HasNoKey' in 'OnModelCreating'. For more information on keyless entity types, see https://go.microsoft.com/fwlink/?linkid=2141943.

It confuses me because I do have a PK defined. I don't particularly like the way I'm mapping the deltas, but I've yet to devise a better method of doing so. Anyways, I was wondering if with my current configuration it is possible to map them correctly at all. And if not, how should I map the deltas otherwise?

Here are my EF configurations for ParticipantTimeline and ParticipantTimelineDelta:

ParticipantTimelineConfiguration.cs:

public class ParticipantTimelineConfiguration : IEntityTypeConfiguration<ParticipantTimelineDto>
{
    public void Configure(EntityTypeBuilder<ParticipantTimelineDto> builder)
    {
        builder.ToTable("ParticipantTimeline");
        builder.HasKey(ps => ps.ParticipantId);
        builder.HasMany<ParticipantTimelineDelta>()
            .WithOne();
    }
}

ParticipantTimelineDeltaConfiguration.cs:

public class ParticipantTimelineDeltaConfiguration : IEntityTypeConfiguration<ParticipantTimelineDelta>
{
    public void Configure(EntityTypeBuilder<ParticipantTimelineDelta> builder)
    {
        builder.ToTable("ParticipantTimelineDeltas");
        builder.HasKey(d => d.Delta);
    }
}

I've tried defining HasForeignKey on the HasMany().WithOne() statement, to no avail.

The comment by Ivan Stoev ended up nudging me the right direction. I've made the following changes to ParticipantTimelineConfiguration:

public class ParticipantTimelineConfiguration : IEntityTypeConfiguration<ParticipantTimelineDto>
{
    public void Configure(EntityTypeBuilder<ParticipantTimelineDto> builder)
    {
        builder.ToTable("ParticipantTimeline");
        builder.HasKey(ps => ps.ParticipantId);
        builder.OwnsMany(pt => pt.CreepsPerMinDeltas, a =>
        {
            a.WithOwner().HasForeignKey("ParticipantTimelineId");
            a.Property<int>("Id");
            a.HasKey("Id");
        });
        builder.OwnsMany(pt => pt.CsDiffPerMinDeltas, a =>
        {
            a.WithOwner().HasForeignKey("ParticipantTimelineId");
            a.Property<int>("Id");
            a.HasKey("Id");
        });
        builder.OwnsMany(pt => pt.DamageTakenDiffPerMinDeltas, a =>
        {
            a.WithOwner().HasForeignKey("ParticipantTimelineId");
            a.Property<int>("Id");
            a.HasKey("Id");
        });
        builder.OwnsMany(pt => pt.DamageTakenPerMinDeltas, a =>
        {
            a.WithOwner().HasForeignKey("ParticipantTimelineId");
            a.Property<int>("Id");
            a.HasKey("Id");
        });
        builder.OwnsMany(pt => pt.GoldPerMinDeltas, a =>
        {
            a.WithOwner().HasForeignKey("ParticipantTimelineId");
            a.Property<int>("Id");
            a.HasKey("Id");
        });
        builder.OwnsMany(pt => pt.XpDiffPerMinDeltas, a =>
        {
            a.WithOwner().HasForeignKey("ParticipantTimelineId");
            a.Property<int>("Id");
            a.HasKey("Id");
        });
        builder.OwnsMany(pt => pt.XpPerMinDeltas, a =>
        {
            a.WithOwner().HasForeignKey("ParticipantTimelineId");
            a.Property<int>("Id");
            a.HasKey("Id");
        });
    }
}

It's not particularly pretty imo, and I expect there to be a better way of doing this. However, this'll do for now.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM