EPPlus icon indicating copy to clipboard operation
EPPlus copied to clipboard

LoadFromCollection ordering nested table columns while filtering member info

Open c3hb opened this issue 2 years ago • 7 comments

When specifying member info to include, it seems the order of the members specified determines the order of the columns (which is great!).

The following gives me columns Id, FirstName, LastName in that order.

LoadFromCollection(items, c => {
    c.Members = new MemberInfo[]
    {
        x.GetProperty("Id"),
        x.GetProperty("FirstName")
        x.GetProperty("LastName")
    }
});

Now that #1052 has been fixed, adding in a EPPlusNestedTableColumn attribute to this produces unexpected ordering.

LoadFromCollection(items, c => {
    c.Members = new MemberInfo[]
    {
        x.GetProperty("Id"),
        x.GetProperty("FirstName")
        x.GetProperty("LastName")
        x.GetProperty("Residence")
    }
});

// With the residence property on the model looking like...
[EPPlusNestedTableColumn(HeaderPrefix = "Residence.")]
public virtual Residence Residence { get; set; }

The above produces columns in the order of Id, Residence.Address, FirstName, Residence.Id, LastName. The nested table columns have been scattered about while the main properties remain in the correct order.

I would expect to keep all residence columns together and display them in the position specified in the member info, which in this case is after LastName.

c3hb avatar Nov 29 '23 20:11 c3hb

Thanks for reporting, we'll have a look at this.

swmal avatar Nov 30 '23 08:11 swmal

@ChristopherBrownie Just to make this clearer, can you give me the class definitions (or at least enough to replicate this issue), including the attributes, for the T class in LoadFromCollection<T> and the Residence class?

swmal avatar Nov 30 '23 08:11 swmal

Sure thing, here's a simple version of the classes:

public class Person : BaseModel
{
    [Key]
    public int Id { get; set; }

    [ForeignKey(nameof(Residence))]
    public int? ResidenceSystemID { get; set; }

    [OfficeOpenXml.Attributes.EpplusNestedTableColumn(HeaderPrefix = "Residence.")]
    public virtual Residence Residence { get; set; }

    [Display(Name = "First Name")]
    public string FirstName { get; set; }

    [Display(Name = "Last Name")]
    public string LastName { get; set; }

    public int? Age { get; set; }
}

public class Residence : BaseModel
{
    [OfficeOpenXml.Attributes.EpplusIgnore]
    public virtual ICollection<Person> Residents { get; set; }

    public string Address { get; set; }
    
    public string City { get; set; }

    public string State { get; set; }
    
    public string Zip { get; set; }

    public string Type { get; set; }
}

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

    public DateTime? CreatedDate { get; set; }

    public DateTime? LastEditDate { get; set; }
}

c3hb avatar Nov 30 '23 22:11 c3hb

@ChristopherBrownie - I had a look at this, don't know which EPPlus/.NET version you are using, so tested with the latest EPPlus 7 code base under .NET 6. From what I can see the columns are (using the classes in your previous message):

  • Id
  • FirstName
  • LastName
  • Residence.Address
  • Residence.City
  • Residence.State
  • Residence.Zip
  • Residence.Type
  • Residence.Id
  • Residence.CreatedDate
  • Residence.LastEditDate

The first three are in the order as specified by the members sent in to the function. And since no other order was provided for the Residence properties are ordered as they were provided by .NET reflection (that's why the base class properties comes last). My recommendation would be that you specify the order you want using one of the following two options:

  1. Add the EPPlusTableColumn or the EPPlusNestedTableColumn attributes with the Order property set on all properties that you want to include. Then add the EPPlusIgnore attribute on properties that you don't want to include. Remove the members from the call to LoadFromCollection.
  2. Keep the members in the call to LoadFromCollection and add the EPPlusTableColumnSortOrder on class level on both classes. You must have it on the Person class too for this to work, even if the members are in the right order. See example
[EPPlusTableColumnSortOrder(Properties = new string[] { nameof(Id), nameof(FirstName), nameof(LastName), nameof(Residence)})]
public class Person : BaseModel
{

}

[EPPlusTableColumnSortOrder(Properties = new string[] { nameof(Id), nameof(CreatedDate), nameof(LastEditDate), nameof(Address), nameof(City), nameof(Zip), nameof(State), nameof(Type)})]
public class Residence : BaseModel
{

}

swmal avatar Dec 01 '23 11:12 swmal

Thanks for looking into this. You are correct I am in .NET 6 with EPPlus 7.0.2.

I seem to be getting inconsistent results on my end. Based on the feedback you provided for ordering properties, is the order of specified filtered members used to set the order of the columns? I assumed it was, but the wiki doesn't specify. It seems to work for me without a nested table column, but is this ordering just coincidental or an intended feature?

c3hb avatar Dec 01 '23 19:12 c3hb

The member filter can be used to specify the order without the nested class, it is a very different internal logic in EPPlus for how to build up the columns when this attribute is added. Since the members filter only works on the outermost class the sortorder will be built up using a combination of the members and any other ordering specified by attributes. My advice in the previous message is based on how it works today, when I debugged this I noticed that in this particular case (when combined with a nested property) the "reflection-order" was used.

I will try to look deeper into this, ideally the order of the members should define the column order even if one of the properties is a nested class, if not possible we will update the docs/wiki. Until we have found a better solution for this, my advice is to define the sort order via attributes as described above if you are using any other attribute than EPPlusIgnore.

swmal avatar Dec 04 '23 12:12 swmal

@ChristopherBrownie I have looked into this again today and we can make this more consistent in the next version. I'm working on a solution where any members provided via the MemberInfo[] will override the sort order as specified in attributes. Needs some more testing, for example when there are inheritance as in your example. Will ping you again when this is testable in our development nuget feed.

swmal avatar Dec 06 '23 15:12 swmal