Question

Goal :

Our application is built using multiple types (e.g. Person, PersonSite(ICollection), Site - I selected those classes because they have a relationship). What we want to do is to be able to export some properties from the first type (Person) in an excel table and then export some other properties from the second type (PersonSite) inside the same excel file but in a new table on the same sheet.

The result should look like this :

 _________________   ________________________   ________________
|                 | |                        | |                |
|PERSON PROPERTIES| | PERSONSITE PROPERTIES  | |SITE PROPERTIES |
|_________________| |________________________| |________________|
|  Name Person 1  | |Relation type for item 1| | Name for item 1|
|_________________| |________________________| |________________|
                    |Relation type for item 2| | Name for item 2|
                    |________________________| |________________|
                    |Relation type for item 3| | Name for item 3|
                    |________________________| |________________|
 _________________   ________________________   ________________
|  Name Person 2  | |Relation type for item 1| | Name for item 1|
|_________________| |________________________| |________________|
                    |Relation type for item 2| | Name for item 1|
                    |________________________| |________________|

So for every PersonSite contained in the list, we would like to create a table that will be inserted just after the table of the Person.

So this is how look the Person class (subset of the class) :

public class Person : IObject
{
   public ICollection<PersonSite> PersonSites {get;set;}
}

Now the PersonSite class (subset) :

public class PersonSite : IObject
{
   public Person Person {get;set;}
   public Site Site {get;set;}
   public RelationType RelationType {get;set;}
}

The Site class (subset) :

public class Site : IObject
{
   public ICollection<PersonSite> PersonSites {get;set;}
}

So we decided to write a CSVExporter class that use expressions to retrieve the properties that must be exported.

This is the scheme we have to implement this :

                    
                            ____
                           |    |0..*
 ______________          __|____|______       1..* _______________
| CSV EXPORTER |________| CSV TABLE (T)|__________| CSV COLUMN (T)|
|______________|        |______________|          |_______________|
                               |
                               |1..*
                         ______|________
                        | CSV ROWS (T)  |
                        |_______________|

So the CSVTable use a generic type that is IObject (as used in the different classes).

A table can have multiple table (e.g. The Person table has a PersonSite table and a PersonSite table has a Site table). But the type used is different since we navigate through the different classes (those classes must have a relationship).

When adding a subtable to a table we should provide en expression that will grab the items of the correct type from the main items (Person => Person.PersonSite)

So we wrote the following piece of code for the table:

public class CSVExportTable<T>
    where T : IObject
{

    private Matrix<string> Matrix { get; set; }
    private ICollection<CSVExportTableColumn<T>> Columns { get; set; }
    private ICollection<CSVExportTableRow<T>> Rows { get; set; }
    private ICollection<CSVExportTable<IObject>> SubTables { get; set; }
    private Expression<Func<T, object>> Link { get; set; }


    public CSVExportTable()
    {
        this.Matrix = new Matrix<string>();
        this.Columns = new List<CSVExportTableColumn<T>>();
        this.SubTables = new List<CSVExportTable<IObject>>();
        this.Rows = new List<CSVExportTableRow<T>>();
    }

    public CSVExportTable<R> AddSubTable<R>(Expression<Func<T, object>> link) where R : IObject
    {
        /* This is where we create the link between the main table items and the subtable items (= where we retreive Person => Person.PersonSites as an ICollection<R> since the subtable has a different type (T != R but they have the same interface(IObject))*/
    }

    public void AddColumn(Expression<Func<T, object>> exportProperty)
    {
        this.Columns.Add(new CSVExportTableColumn<T>(exportProperty));
    }

    public Matrix<string> GenerateMatrix()
    {
        int rowIndex= 0;
        foreach (CSVExportTableRow<T> row in this.Rows)
        {
            int columnIndex = 0;
            foreach (CSVExportTableColumn<T> column in this.Columns)
            {
                this.Matrix = this.Matrix.AddValue(rowIndex, columnIndex, ((string)column.ExportProperty.Compile().DynamicInvoke(row.Item)));
                columnIndex++;
            }
            rowIndex++;
        }
        return this.Matrix;
    }

    public Matrix<string> ApplyTemplate(ICollection<T> items)
    {
        // Generate rows
        foreach (T item in items)
        {
            this.Rows.Add(new CSVExportTableRow<T>(item));
        }
        // Instantiate the matrix
        Matrix<string> matrix = new Matrix<string>();

        // Generate matrix for every row
        foreach (var row in this.Rows)
        {
            matrix = GenerateMatrix();
            // Generate matrix for every sub table
            foreach (var subTable in this.SubTables)
            {
                // This it where we should call ApplyTemplate for the current subTable with the elements that the link expression gave us(ICollection).
            }
        }
        return matrix;
    }
}

And finally here is the CSVExportTableColumn class :

public class CSVExportTableColumn<T> where T : IObject
{
    public Expression<Func<T, object>> ExportProperty { get; set; }

    public CSVExportTableColumn(Expression<Func<T, object>> exportProperty)
    {
        this.ExportProperty = exportProperty;
    }
}

Has anyone ever done something like that ? Or are we heading to the wrong path ? If this seems to be a good path, how can we create the link (relationship) expression and use it to retrieve the correct items to use on the last call ?

Was it helpful?

Solution

Sorry for the delay,

So finally, we got it working like this :

public Matrix<string> ApplyTemplate(object items)
    {
        ICollection<T> castedItems = new List<T>();
        // Cast items as a List<T>
        if (items is List<T>)
        {
            castedItems = (ICollection<T>)items;
        }
        else if (items is HashSet<T>)
        {
            castedItems = ((HashSet<T>)items).ToList();
        }
        // Generate rows
        foreach (T item in castedItems)
        {
            this.Rows.Add(new CSVExportTableRow<T>(item));
        }
        // Instantiate the matrix
        Matrix<string> matrix = new Matrix<string>();

        // Generate matrix for every row
        foreach (var row in this.Rows)
        {
            matrix = GenerateMatrix();
            // Generate matrix for every sub table
            foreach (var subTable in this.SubTables)
            {
                matrix = matrix.AddMatrix(subTable.ApplyTemplate(this.Link.Compile().DynamicInvoke(row.Item)));
            }
        }
        return matrix;
    }

This will apply the template for any sub table.

Hope this can be useful for others.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top