Search code examples
associationslinq2db

Linq2db association is null


I have 2 domain entities, member and themesettings. A member can have 0 or 1 themesettings and I have set it up using the fluentmapping:

public class Member(int id, Guid externalId, string name, string email, ThemeSettings? settings) : AggregateRoot(id)
{
    public Guid ExternalId { get; private set; } = externalId;
    public string Name { get; private set; } = name;
    public string Email { get; private set; } = email;

    // Associations
    public ThemeSettings? ThemeSettings { get; set; } = settings;
}

public class ThemeSettings(
    int id,
    int memberId,
    Theme theme,
    SidebarPosition sidebarPosition,
    SidebarBehavior sidebarBehavior,
    bool sidebarCompact) : Entity(id)
{
    public int MemberId { get; private set; } = memberId;
    public Theme Theme { get; set; } = theme;
    public SidebarPosition SidebarPosition { get; set; } = sidebarPosition;
    public SidebarBehavior SidebarBehavior { get; set; } = sidebarBehavior;
    public bool SidebarCompact { get; set; } = sidebarCompact;
}


builder
            .Entity<Member>()
            .HasSchemaName("Member")
            .HasTableName("Member")
            .Property(e => e.Id).IsPrimaryKey().IsIdentity().HasColumnName("Id")
            .Property(e => e.Code).IsNotNull().HasColumnName("Code")
            .Property(e => e.ExternalId).IsNotNull().HasColumnName("ExternalId")
            .Property(e => e.Name).IsNotNull().HasColumnName("Name")
            .Property(e => e.Email).IsNotNull().HasColumnName("Email")
            .Property(e => e.ThemeSettings)
            .Association(e => e.ThemeSettings, e => e.Id, c => c!.MemberId, true);


builder
            .Entity<ThemeSettings>()
            .HasSchemaName("Member")
            .HasTableName("ThemeSettings")
            .Property(e => e.Id).IsPrimaryKey().IsIdentity().HasColumnName(nameof(Entity.Id))
            .Property(e => e.Code).IsNotNull().HasColumnName("Code")
            .Property(e => e.MemberId).HasColumnName("MemberId")
            .Property(e => e.Theme).HasConversion(v => (int)v, v => (Theme)v).HasColumnName(nameof(ThemeSettings.Theme))
            .Property(e => e.SidebarBehavior).HasConversion(v => (int)v, v => (SidebarBehavior)v)
            .HasColumnName(nameof(ThemeSettings.SidebarBehavior))
            .Property(e => e.SidebarPosition).HasConversion(v => (int)v, v => (SidebarPosition)v)
            .HasColumnName(nameof(ThemeSettings.SidebarPosition))
            .Property(e => e.SidebarCompact).HasColumnName(nameof(ThemeSettings.SidebarCompact));

I then query the Member using LoadWith on the themesettings. I see the query is generated correctly and returns 1 row, as expected:

DECLARE @externalId UniqueIdentifier -- Guid
SET     @externalId = 'd4cee0e2-6341-43c0-93c8-f1acad44195a'

SELECT
    [member_1].[Id],
    [member_1].[Code],
    [member_1].[ExternalId],
    [member_1].[Name],
    [member_1].[Email],
    [a_ThemeSettings].[Id],
    [a_ThemeSettings].[Code],
    [a_ThemeSettings].[MemberId],
    [a_ThemeSettings].[Theme],
    [a_ThemeSettings].[SidebarPosition],
    [a_ThemeSettings].[SidebarBehavior],
    [a_ThemeSettings].[SidebarCompact]
FROM
    [Member].[Member] [member_1]
        LEFT JOIN [Member].[ThemeSettings] [a_ThemeSettings] ON [member_1].[Id] = [a_ThemeSettings].[MemberId]
WHERE
    [member_1].[ExternalId] = @externalId


[07:41:42 Information] LinqToDB.Data.DataConnection
Query Execution Time (AfterExecute): 00:00:00.0015098


[07:41:42 Information] LinqToDB.Data.DataConnection
Total Execution Time (Completed): 00:00:00.0018493. Rows Count: 1.

It initializes the Member object for me correctly but for some reason, the ThemeSettings property is still null. I cannot figure out what I have done wrong, any ideas? If I run this query manually on the database, the data is correct and contains the valid theme settings values for this member.

The data is loaded using a repository and specification which contains the includes:

public async Task<IEnumerable<T>> GetBySpecificationAsync(ISpecification<T> spec)
{
    var query = db.GetTable<T>().Where(spec.Criteria);

    query = spec.Includes.Aggregate(query,
        (current, include) => current.LoadWith(include));
    return await query.ToListAsync();
}

public class MemberByExternalIdSpecification(Guid externalId)
    : Specification<Domain.MemberAggregate.Member>(
        member => member.ExternalId == externalId,
        i => i.ThemeSettings);
    

The second line in the specification base initializer is the array of incudes that is processed in the repository.

Any ideas?


Solution

  • If anyone was wondering, I had overlooked the fact the primary constructor is used by linq2db and uses the parameter names to pass the data. The name of the ThemeSettings argument had to be fixed from settings to themeSettings.

    Thanks to @Svyatoslav Danyliv for pointing that out to me on the linq2db issues section here: https://github.com/linq2db/linq2db/issues/4478

    public class Member(int id, Guid externalId, string name, string email, ThemeSettings? themeSettings) : AggregateRoot(id)
    {
        public Guid ExternalId { get; private set; } = externalId;
        public string Name { get; private set; } = name;
        public string Email { get; private set; } = email;
    
        // Associations
        public ThemeSettings? ThemeSettings { get; set; } = themeSettings;
    }