Home > OS >  how to access the class properties based on condition
how to access the class properties based on condition

Time:10-13

I have below the object and its internal objects and I have added custom attributes to the property names which are required for the report.

public class Space
{
    public SpaceIdentity SpaceIdentity { get; set; } = new();
    public SpaceGeometry SpaceGeometry { get; set; } = new();
    public AirBalance AirBalance { get; set; } = new();
    public EngineeringChecks EngineeringChecks { get; set; } = new();
}
public class SpaceIdentity
{
    public int ElementId { get; set; } // Not required
    [DisplayNameWithUnits(DisplayName = "Space Number", IsIncludedInReport2 = true)]
    public string Number { get; set; }
    [DisplayNameWithUnits(DisplayName = "Space Name", IsIncludedInReport2 = true, IsIncludedInReport1 = true)]
    public string Name { get; set; }
    [DisplayNameWithUnits(DisplayName = "Room Number", IsIncludedInReport1 = true)]
    public string RoomNumber { get; set; }
    [DisplayNameWithUnits(DisplayName = "Room Name", IsIncludedInReport1 = true)]
    public string RoomName { get; set; }
}

public class SpaceGeometry
{
    public Vertex LocationPoint { get; set; } // this is not required
    [DisplayNameWithUnits(DisplayName = "Space Area", Units = "(ft²)", IsIncludedInReport1 = true)]
    public double FloorArea { get; set; }
}
.....

Here I am building an excel report, which I want to use property display name's as header column names of that report. Here are some of the properties attribute information used in multiple reports. What I did was I added a bool condition attribute like (isIncludedInReport1) and loop through the properties of space and loop through the properties of inner object(SpaceGeometry) to get a particular property name and its attribute values based on this boolean condition.

What I am looking for here is without adding these bool attributes, is there any way to access the property names based on condition. I thought about adding interfaces, but that is not possible here because I have multiple inner classes having properties that I need to include in a single report.

Could anyone please let me know is there any other way to achieve this?

Update:

   var columnResult = new OrderedDictionary();
    GetReportHeaderColumnName(typeof(Space), columnResult);
    
    public static void GetReportHeaderColumnName(Type type, OrderedDictionary headerNameByUnit)
    {
        var properties = type.GetProperties();
        foreach (var propertyInfo in properties)
        {
            if (propertyInfo.PropertyType.IsClass && !propertyInfo.PropertyType.FullName.StartsWith("System."))
            {
                if (propertyInfo.PropertyType == typeof(Overridable<double>))
                {
                    AddReportHeaderName(headerNameByUnit, propertyInfo);
                }
                else
                {
                    GetReportHeaderColumnName(propertyInfo.PropertyType, headerNameByUnit);
                }
            }
            else
            {
                AddReportHeaderName(headerNameByUnit, propertyInfo);
            }
        }
    }

    protected static void AddReportHeaderName(OrderedDictionary columnResult, PropertyInfo propertyInfo)
    {
        if (propertyInfo.GetCustomAttributes(typeof(DisplayNameWithUnitsAttribute), true).Any())
        {
            var displayNameWithUnitsAttribute = (DisplayNameWithUnitsAttribute)Attribute.GetCustomAttribute(propertyInfo, typeof(DisplayNameWithUnitsAttribute));

            if (displayNameWithUnitsAttribute.IsIncludedInReport2)
            {
                columnResult.Add(displayNameWithUnitsAttribute.DisplayName, displayNameWithUnitsAttribute.Units);
            }
        }
    }

CodePudding user response:

What do you want to achieve by making it another way? I assume you want to make your code scalable so adding more reports is easier.

Do you still want to use attributes on the classes themselves? If so, you could make a new attribute, and tag your properties like so:

public class SpaceIdentity
{
    public int ElementId { get; set; } // Not required
    [DisplayNameWithUnits(DisplayName = "Space Number")]
    [IncludeInReport(2)]
    public string Number { get; set; }
    
    [DisplayNameWithUnits(DisplayName = "Space Name")]
    [IncludeInReport(1)]
    [IncludeInReport(2)]
    public string Name { get; set; }

    [DisplayNameWithUnits(DisplayName = "Room Number")]
    [IncludeInReport(1)]
    public string RoomNumber { get; set; }

    [DisplayNameWithUnits(DisplayName = "Room Name")]
    [IncludeInReport(1)]
    public string RoomName { get; set; }
}

Or do you want to separate the report from the classes, so your report is defined in another file? If so, maybe a 2-dimensional list of property names would do the trick:

List<List<string>> Report1 = new()
{
    new(){nameof(Space.SpaceIdentity), nameof(SpaceIdentity.Name)},
    new(){nameof(Space.SpaceIdentity), nameof(SpaceIdentity.RoomNumber)},
    new(){nameof(Space.SpaceIdentity), nameof(SpaceIdentity.RoomName)},
    new(){nameof(Space.SpaceGeometry), nameof(SpaceGeometry.FloorArea)}
};

CodePudding user response:

Another way that doesn't use reflection is to just use a list of ReportProperty

public record ReportProperty<T>(
    Func<T, string> ValueFunc,
    string DisplayName,
    string? Unit = null
    );

List<ReportProperty<Space>> report1 = new(){
    new( s => s.SpaceIdentity.Number, "Space Number"),
    new( s => s.SpaceIdentity.RoomNumber, "Room Number"),
    new( s => s.SpaceIdentity.RoomName, "Room Name"),
    new( s => s.SpaceGeometry.FloorArea.ToString(), "Floor Area", "Ft2"),
};


  • Related