How to configure composite key in EF Core fluent API when the composite key is composed by 2 Value Object

47 Views Asked by At

I have these 3 Value Object (LanguageId, AttributeSetId and AttributeSetTranslationId

public record LanguageId(string LangCode);

public record AttributeSetId(Guid Value);

public record AttributeSetTranslationId(AttributeSetId AttributeSetId, LanguageId LanguageId);

public sealed class AttributeSet
{
    private readonly List<AttributeSetTranslation> _attributeSetTranslations = new();

    public AttributeSetId Id { get; private set; }

    public IReadOnlyCollection<AttributeSetTranslation> AttributeSetTranslations => _attributeSetTranslations.ToList();
}
public class AttributeSetTranslation 
{
    public AttributeSetTranslationId Id { get; private set; }
    public string Name { get; private set; }
    public string Description { get; private set; }
}

As you can see, the Value Object AttributeSetTranslationId is a composition of the 2 Value Objects (AttribuetSetId and LanguageId)

Anybody have an idea how to configure the key and the conversion for AttributeSet in EF Core Fluent API

So far this is what I got and it doesn't work.

public class AttributeConfigurations : IEntityTypeConfiguration<AttributeSet>
{
    public void Configure(EntityTypeBuilder<AttributeSet> builder)
    {
        builder.HasKey(x => x.Id);

        builder.Property(x => x.Id)
            .HasConversion(
                attributeSetId => attributeSetId.Value,
                value => new AttributeSetId(value));

        builder.OwnsMany(builder => builder.AttributeSetTranslations, sb =>
        {
            sb.ToTable("attribute_set_translations");

            sb.HasKey(s => new { s.Id.AttributeSetId, s.Id.LanguageId });

            sb.Property(s => s.Id)
                .HasConversion(
                    id => new { id.AttributeSetId, id.LanguageId },
                    value => new AttributeSetTranslationId(value.AttributeSetId, value.LanguageId));

        });
    }
}

Here is the error when I'm trying to add the migration.

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.  ---> System.ArgumentException: The expression 's => new <>f__AnonymousType92(AttributeSetId = s.Id.AttributeSetId, LanguageId = s.Id.LanguageId)' is not a valid member access expression. The expression should represent a simple property or field access: 't =\> t.MyProperty'. When specifying multiple properties or fields, use an anonymous type: 't =\> new { t.MyProperty, t.MyField }'. (Parameter 'memberAccessExpression') at Microsoft.EntityFrameworkCore.Infrastructure.ExpressionExtensions.GetMemberAccessList(LambdaExpression memberAccessExpression) at Microsoft.EntityFrameworkCore.Metadata.Builders.OwnedNavigationBuilder2.HasKey(Expression1 keyExpression) at ProductCentral.Infrastructure.Persistence.Configurations.AttributeConfigurations.\<\>c.\<Configure\>b__0_5(OwnedNavigationBuilder2 sb) in C:\Source\applive\13500-product-central\backend\ProductCentral.Infrastructure\Persistence\Configurations\AttributeConfigurations.cs:line 23    at Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder1.OwnsMany\[TRelatedEntity\](Expression1 navigationExpression, Action1 buildAction) at ProductCentral.Infrastructure.Persistence.Configurations.AttributeConfigurations.Configure(EntityTypeBuilder1 builder) in \AttributeConfigurations.cs:line 19    at Microsoft.EntityFrameworkCore.ModelBuilder.ApplyConfiguration[TEntity](IEntityTypeConfiguration`1 configuration)at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)at System.Reflection.MethodInvoker.Invoke(Object obj, IntPtr* args, BindingFlags invokeAttr)

I know I could avoid having 2 value objects in AttributeSetTranslationId and simply have a regular ID then add the LanguageId and AttributeSet straight into the Attribute Entity

like that


public class AttributeSetTranslation : Auditable
{
    public AttributeSetTranslationId Id { get; private set; }
    public LanguageId LanguageId { get; private set; }
    public AttributeId AttributeId { get; private set; }
    public string Name { get; private set; }
    public string Description { get; private set; }
}

But in my opinion, it would be better to encapsulate the LanguageId and AttributeId in the AttributeSetTranslationId as this is the composite primary key of the object AttributeSetTranslation.

Thanks a lot :)

0

There are 0 best solutions below