8

I am trying to implement a generic scheduler object in C# 4 which will output a table in HTML. Basic aim is to show some object along with various attributes, and whether it was doing something in a given time period.

The scheduler will output a table displaying the headers:

Detail Field 1 ....N| Date1.........N

I want to initialise the table with a start date and an end date to create the date range (ideally could also do other time periods e.g. hours but that isn't vital). I then want to provide a generic object which will have associated events.

Where an object has events within the period I want a table cell to be marked

E.g.

Name Height Weight 1/1/2011 2/1/2011 3/1/20011...... 31/1/2011
Ben  5.11    75       X        X                       X
Bill 5.7     83                X        X

So I created scheduler with Start Date=1/1/2011 and end date 31/1/2011

I'd like to give it my person object (already sorted) and tell it which fields I want displayed (Name, Height, Weight)

Each person has events which have a start date and end date. Some events will start and end outwith but they should still be shown on the relevant date etc.

Ideally I'd like to have been able to provide it with say a class booking object as well. So I'm trying to keep it generic.

I have seen Javasript implementations etc of similar.

What would a good data structure be for this? Any thoughts on techniques I could use to make it generic. I am not great with generics so any tips appreciated.

5
  • with generics you still need a base class or interface to make it typesafe. You can use Reflection to get the Properties at runtime, but this is slower and throws in runtime only
    – Firo
    Dec 12, 2011 at 13:28
  • Yeah I was expecting the base class of the data structure to have some sort of structure holding side column headers (which may be 1...to n), some sort of structure holding main columns headers (which would be as big as the time period), and then structures holding the actual rows. The generic part was the ability to pass some random object and define which I would then define the columns I required for etc. Dec 12, 2011 at 14:25
  • Ever thought about using the "dynamic" keyword for this task?
    – Dimi Takis
    Apr 11, 2012 at 11:14
  • 1
    This interesting discussion about designing around data might give you direction/inspiration. Sep 13, 2012 at 11:41
  • If you don't need to do any actual operations on the field data (height, weight, etc.) you could use reflection as outlined in this answer on Stack Overflow to get the property values and names to display from the object.
    – nemec
    Oct 6, 2012 at 17:37

2 Answers 2

2

I don't think this is too tough. What am I missing?

using System;
using System.Collections.Generic;

public class ScheduledPerson {
    public string Name {get; set;}

    public Dictionary<string, Object> Fields {
        get { return _fields; }
        set { _fields = value; }
    }
    private Dictionary<string, Object> _fields = new Dictionary<string, Object>();

    public List<Tuple<DateTime, DateTime>> Events {
        get { return _events; }
        set { _events = value; }
    }
    private List<Tuple<DateTime, DateTime>> _events = new List<Tuple<DateTime, DateTime>>();

    public ScheduledPerson(string name){
        Name = name;
    }
}

I don't see a good way to make our ScheduledPerson classes generic, as it seems the fields can be anything. I'm storing the field values as Objects, as I see nothing that requires them to be dynamics. Just make sure all field value types have a sensible ToString() implementation.

If you want Events to be a List of DateRange or of your own Event class instead of Tuple, feel free.

It then remains for you to write a separate class to render each ScheduledPerson in a table, along with figuring out all the headers from all ScheduledPerson records. It you are dealing with ten thousand people, you'll want a better solution that has all the headers stored, but for most applications, it won't be too bad to enumerate all the Fields on all the ScheduledPersons to figure out the headers.

0

In general, I wouldn't store the data for your solution based on the way that you want to display it. That leads to very specific solutions that will make things difficult when your needs change. I would break things down in terms of entities in your solution and then create a set of LINQ Queries that would generate your display data when it is time to generate the report.

All the code below is structural and omitting initialization, etc.

public class Person
{
   public string Name {get;set;}
   public double Height {get;set;}
   public double Weight {get;set;}
}

public class ScheduledEvent
{
   public Person Attendee {get;set;}
   public DateTime EventDate {get;set;}
}

public class Schedule
{
   public DateTime StartDate {get;set; }
   public DateTIme EndDate {get;set;}
   List<Person> Persons {get;set;}
   List<ScheduledEvent> SheduledEvents {get;set;}
}

Now, in practice, I would store these in a database of some kind and do all the queries at runtime with something like NHibernate, or Entity Framework. But for in-memory demo purposes, queries to produce your table lines would be like the following:

class TableRow
{
   string Name { get;set; }
   string Height {get;set; }
   string Weight {get;set; }
   List<string> DatesWithEvents {get; }
}

var columns = List<string>{ "Name", "Height", "Weight", }.Concat(
                schedule.ScheduledEvents
                  .Select(e=> e.EventDate.ToShortDate())
                  .Distinct())
              .ToList();
var rows = new List<TableRow>();

foreach(var p in schedule.Persons) 
{
   var row = new TableRow();
   row.Name = p.Name;
   row.Height = p.Height;
   row.Weight = p.Weight;
   row.DatesWithEvents = schedule.ScheduledEvents
     .Where(e => e.Person == p)
     .Select(e => e.EventDate.ToShortDate()).Distinct().ToList();
   rows.Add(row);
}    

Then in your renderer, just match up the columns to know where to stick the 'X' to mark that the date is filled. With more effort, you can refactor this into a much more elegant solution, but this is the basics.

Not the answer you're looking for? Browse other questions tagged or ask your own question.