donnytian / Npoi.Mapper

Use this tool to import or export data with Excel file. The tool is a convention based mapper between strong typed object and Excel data via NPOI.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Does not map (save) inherited interface properties

gyula-uszkai opened this issue · comments

I came across a situation where if the strongly typed object implements an interface which inherits another interface, the base interface properties are not mapped.

Example

var mapper = new Mapper(@"c:\temp\NewTest.xlsx");
           var input = new List<IUserStringModel> // here is the important line
           {
               new UserStringModel{
                   BirthDate = DateTime.Now.ToString(),
                   FirstName = "aaaaa",
                   Message="First again"
               },
               new UserStringModel{
                   BirthDate = DateTime.Now.ToString(),
                   FirstName = "aaaaa"
               }
           };

           mapper.Put(input, "Status4", true);

           mapper.Save(@"c:\temp\NewTest.xlsx");

where

public interface IUserStringModel : IStringModel
    {
        string BirthDate { get; set; }
        string FirstName { get; set; }
    }

 public interface IStringModel
    {
        public string Status { get; set; }
        public string Message { get; set; }
    }
    public class StringModelBase : IStringModel
    {
        public string Status { get; set; }
        public string Message { get; set; }
    }

    public class UserStringModel : StringModelBase, IUserStringModel
    {
        public string FirstName { get; set; }

        public string BirthDate { get; set; }
    }

Expected: 4 columns are saved in the excel FirstName, BirthDate, Status, Message
Actual: only FirstName, BirthDate are saved

If I change the input type as var input = new List<UserStringModel> then all 4 properties are saved.
If I change IUserStringModel to contain the Status and Message properties itself (not inherit) then all 4 properties are saved.

If the interface is inheriting some properties, the ones inherited are not mapped if the derived interface is used as a type for the put method input parameter (example above).

It seems that this is also a problem with serialization. See https://docs.microsoft.com/en-us/dotnet/standard/serialization/system-text-json-polymorphism
Found a solution based on the article: apply a cast to object then it will save as expected
mapper.Put(input.Cast<object>(), "Status4", true);
Hope it will help someone.

if you define the list with List<IUserStringModel>, means you can put any concrete type that implements IUserStringModel into the list.
So I may question your expectation "Expected: 4 columns are saved".
And, casting it to object actually is dangerous here, mapper will infer a concrete type by the first element in the list, so if any item in the list is another concrete type, we will have issue.

So just keep it simple, to use a concrete type to define the list.

Thank you for the answer but I think that I did not manage to properly explain myself.

If the system properly saves when using an interface with 4 properties I would expect that it also properly saved when using interface inheritance.

Also thank you for pointing out that the mapper will infer a concrete type by the first element. I will have that mind in the future. Currently this is fine as I don't mix different type in the same list, but I do create different list that implement the same interface. This is especially helpful when dealing with localized excel headers where the header text is translated in different languages but you want to be able to process them in a similar fashion.

Asking not to use interfaces seems like a bad idea in general and many times even impossible (when using third party libraries and modules where you might not know the concrete type)

Hi, one thing to be noted is that when I say "mapper will infer a concrete type from the first element in the list", there is a precondition: the target type is object. As long as the type is not object, the mapper will use the specified type without any further detection.

You can see code here, it's simple enough.