Skip to content

Overriden property not getting used #74

Open

Description

@honzapatCZ

Hi
I am having a base type and a few derived types of it.

[Table("JournalEntryRefs")]
public abstract class JournalEntryRef
{
    [Projectable]
    public virtual string? DisplayName => null;
}

[Table("JournalEntryRefs")]
public class InvoiceJournalEntryRef : JournalEntryRef
{
    [Projectable]
    public override string? DisplayName => Invoice.Number != null ? Invoice.Number : "";
}

[Table("JournalEntryRefs")]
public class PaymentJournalEntryRef : JournalEntryRef
{
    [Projectable]
    public override string? DisplayName => Payment.Number;
}

When I query all the JournalEntryRef's(ie a collection somewhere) I specify that I want the reference column(beside others) and I'd expect that as such it would return the Invoice number for Invoice JournalEntryRef and Payment number for PaymentEntryRef. But currently they are always null.
Is that a bug? If so how can we fix it? Or is that entirely not possible?

Activity

  1. koenbeuk commented on May 25, 2023

    @koenbeuk
    Collaborator

    Can you try and use methods instead of properties and see if the issue persists? We have done work to support calling derived projectable methods, I'm not sure if this work also translated to properties.

    Looking forward to hearing back.

  2. honzapatCZ commented on May 28, 2023

    @honzapatCZ
    Author

    Can you try and use methods instead of properties and see if the issue persists? We have done work to support calling derived projectable methods, I'm not sure if this work also translated to properties.

    Looking forward to hearing back.

    Still the same

        [Table("JournalEntryRefs")]
        public abstract class JournalEntryRef
        {
            [Projectable]
            public string? DisplayName => GetDisplayName();
    
            [Projectable]
            public virtual string? GetDisplayName() => null;
        }
    
        [Table("JournalEntryRefs")]
        public class InvoiceJournalEntryRef : JournalEntryRef
        {
            [Projectable]
            public override string? GetDisplayName() => Invoice.Number != null ? Invoice.Number : "";
        }
    
        [Table("JournalEntryRefs")]
        public class PaymentJournalEntryRef : JournalEntryRef
        {
            [Projectable]
            public override string? GetDisplayName() => Payment.Number;
        }
    
        [Table("JournalEntryRefs")]
        public class BankEntryJournalEntryRef : JournalEntryRef
        {
            [Projectable]
            public override string? GetDisplayName() => "BE";
        }

    Tho to be 100% exact Iam accessing it yet from another class(JournalEntries) as Projectable

    [Projectable]
    public string? Reference => JournalEntryRef != null ? JournalEntryRef.DisplayName : null;

    EDIT: Here's the SQL when querying for all the Journal Entries, it seems the the if case is getting generated incorrectly.

    SELECT `j`.`Id`, `j0`.`AccountNo`, `j0`.`CompanyId`, `j0`.`ActiveFrom`, `j0`.`ActiveTo`, `j0`.`CreatedAt`, `j0`.`DeletedAt`, `j0`.`Description`, `j0`.`Type`, `j0`.`UpdatedAt`, `j1`.`AccountNo`, `j1`.`CompanyId`, `j1`.`ActiveFrom`, `j1`.`ActiveTo`, `j1`.`CreatedAt`, `j1`.`DeletedAt`, `j1`.`Description`, `j1`.`Type`, `j1`.`UpdatedAt`, CASE
              WHEN `j2`.`Id` IS NOT NULL THEN NULL
              ELSE NULL
          END, `j`.`Description`, `j`.`AccountReceivableNo`, `j`.`AccountPayableNo`, `j`.`Amount`, `j`.`Date`
          FROM `JournalEntries` AS `j`
          INNER JOIN `JournalAccounts` AS `j0` ON (`j`.`AccountReceivableNo` = `j0`.`AccountNo`) AND (`j`.`CompanyId` = `j0`.`CompanyId`)
          INNER JOIN `JournalAccounts` AS `j1` ON (`j`.`AccountPayableNo` = `j1`.`AccountNo`) AND (`j`.`CompanyId` = `j1`.`CompanyId`)
          LEFT JOIN `JournalEntryRefs` AS `j2` ON `j`.`Id` = `j2`.`JournalEntryId`
          WHERE `j`.`CompanyId` = @__p_0
          ORDER BY `j`.`Date` DESC
  3. koenbeuk commented on Jun 3, 2023

    @koenbeuk
    Collaborator

    Tho to be 100% exact Iam accessing it yet from another class(JournalEntries) as Projectable

    There is a problem, during the query compilation phase, we do not know what JournalEntryRef is except for that its JournalEntryRef. So the only sensible thing it can do is project JournalEntryRef.GetDisplayName. Only when the query executes can it resolve what JournalEntryRef actually is.

    You'll have to come up with something creative in order to bind to the correct implementation during query execution, e.g.

    public string? Reference => JournalEntryRef is InvoiceJournalEntryRef ? ((InvoiceJournalEntryRef)JournalEntryRef).GetDisplayName() :  JournalEntryRef is PaymentJournalEntryRef ? ((PaymentJournalEntryRef )JournalEntryRef).GetDisplayName() : JournalEntryRef is BankEntryJournalEntryRef ? ((BankEntryJournalEntryRef )JournalEntryRef).GetDisplayName() : null;
  4. added
    invalidThis doesn't seem right
    wontfixThis will not be worked on
    on Jun 3, 2023
  5. honzapatCZ commented on Jun 3, 2023

    @honzapatCZ
    Author

    Tho to be 100% exact Iam accessing it yet from another class(JournalEntries) as Projectable

    There is a problem, during the query compilation phase, we do not know what JournalEntryRef is except for that its JournalEntryRef. So the only sensible thing it can do is project JournalEntryRef.GetDisplayName. Only when the query executes can it resolve what JournalEntryRef actually is.

    You'll have to come up with something creative in order to bind to the correct implementation during query execution, e.g.

    public string? Reference => JournalEntryRef is InvoiceJournalEntryRef ? ((InvoiceJournalEntryRef)JournalEntryRef).GetDisplayName() :  JournalEntryRef is PaymentJournalEntryRef ? ((PaymentJournalEntryRef )JournalEntryRef).GetDisplayName() : JournalEntryRef is BankEntryJournalEntryRef ? ((BankEntryJournalEntryRef )JournalEntryRef).GetDisplayName() : null;

    And can't exactly this be generated by Projectables?
    Ie: If we know it's virtual the actual implementation will looks thru what inherits this class and generates the ifs?

  6. koenbeuk commented on Jun 4, 2023

    @koenbeuk
    Collaborator

    Ie: If we know it's virtual the actual implementation will looks thru what inherits this class and generates the ifs?

    Technically, yes. However that is a big ask. This would need substantial design. Feel free to propose something but at the moment, this would not be supported :)

  7. honzapatCZ commented on Jun 4, 2023

    @honzapatCZ
    Author

    Ie: If we know it's virtual the actual implementation will looks thru what inherits this class and generates the ifs?

    Technically, yes. However that is a big ask. This would need substantial design. Feel free to propose something but at the moment, this would not be supported :)

    Ok, Ill look into it

  8. Xriuk commented on May 25, 2026

    @Xriuk

    Would it be enough for this to work to retrieve all the derived types implementations of each method and then add a switch/if else if based on the type?
    Then if the method is used in other projections we would need to duplicate and replace the whole expression for each type.

  9. PhenX commented on May 25, 2026

    @PhenX
    Member

    That may work, yes.

  10. Xriuk commented on May 25, 2026

    @Xriuk

    I wish I could help on this, but in my project NeatMapper I'm using Expressions directly, so I only know about projections/expansions/inlining related to them. I know nothing about Roslyn code generation unfortunately :/

  11. Xriuk commented on Jun 4, 2026

    @Xriuk

    Just to add a note: this would require supporting both abstract and virtual classes/members, this might require changing the Projectable attribute implementation.

    Abstract class/member (no body required for Projectable)

    public abstract class Base{
      [Projectable]
      public abstract string MyProp { get; }
    }
    
    public class Derived1 : Base{
      [Projectable]
      public override string MyProp => "MyProp1";
    }
    
    ...
    
    public class DerivedN : Base{
      public string MyPropInternal { get; }
    
      [Projectable]
      public override string MyProp => MyPropInternal;
    }

    This would generate an expression like this (no default case):

    public static Expression<Func<Base, string>> Base_MyProp(){
      return @this =>
        @this is Derived1 ? "MyProp1" :
        ...
        ((DerivedN)@this).MyPropInternal;
    }

    Virtual member

    public class Base{
      [Projectable]
      public virtual string MyProp => "Default";
    }
    
    public class Derived1 : Base{
      [Projectable]
      public override string MyProp => "MyProp1";
    }
    
    ...
    
    public class DerivedN : Base{
      public string MyPropInternal { get; }
    
      [Projectable]
      public override string MyProp => MyPropInternal;
    }

    This would generate an expression like this (with last default case):

    public static Expression<Func<Base, string>> Base_MyProp(){
      return @this =>
        @this is Derived1 ? "MyProp1" :
        ...
        @this is DerivedN ? ((DerivedN)@this).MyPropInternal :
        "Default";
    }
  12. Xriuk commented on Jun 12, 2026

    @Xriuk

    Just to update that I started working on this, will follow with a PR

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    invalidThis doesn't seem rightwontfixThis will not be worked on

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions