vendredi 11 mars 2016

C# Generic binding

I am working on an assignment for school and trying to implement as much features just for learning sake. Hence I've made a generic mapper that maps databse tables to objects to see what's possible. The Db in this case is local. I know I'm making loads and loads of calls and should go around this very differently but....

Everything works as intended except for when a class has a Collection of another class.

Example:

class Student {

public int Id { get; set; }
public string Name { get; set; }
}

My method for filling a list of all the students in the database.

public List<TModel> MapEntitiesFromDb<TModel>(string tablename, string customquery = "") where TModel : class, new() 
    {
        try
        {
            sql = ValidateSelectSql(tablename, customquery);
        }
        catch (AccessViolationException ex) { Console.WriteLine(ex.Message); }

        command.CommandText = sql;
        command.Connection = conn;

        List<TModel> list = new List<TModel>();
        try
        {
            using (conn)
            {
                Type t = new TModel().GetType();

                conn.Open();
                using (reader = command.ExecuteReader())
                {

                    if (t.GetProperties().Length != reader.FieldCount)
                        throw new Exception("There is a mismatch between the amount of properties and the database columns. Please check the input code and try again.");

                    //Possible check is to store each column and property name in arrays and match them to a new boolean array, if there's 1 false throw an exception.
                    string columnname;
                    string propertyname;

                    //Pairing properties with columns 
                    while (reader.Read())
                    {
                        TModel obj = new TModel();

                        for (int i = 0; i < reader.FieldCount; i++)
                        {
                            columnname = reader.GetName(i).ToString().ToLower();

                            PropertyInfo[] properties = t.GetProperties();
                            foreach (PropertyInfo propertyinfo in properties)
                            {
                                propertyname = propertyinfo.Name.ToLower();

                                if (propertyname == columnname)
                                {
                                    propertyinfo.SetValue(obj, reader.GetValue(i));
                                    break;
                                }
                            }
                        }

                        list.Add(obj);

                    }
                }
            }
        }
        catch (Exception ex) { Console.WriteLine(ex.Message); }

        return list;
    }

My ValidateSelectSql just returns the sql string that needs to be used in the query.

After calling:

List<Student> = MapEntitiesFromDb<Student>("students");

It will return a list with all the students like intended.

Things go wrong when I add a collection for example:

class Student {

public Student()
{
    this.Courses = new List<Course>();

    string customsqlquery = ::: this works and is tested! :::
    Courses = MapEntitiesFromDb<Course>("", customsqlquery);
}

public int Id { get; set; }
public string Name { get; set; }

public ICollection<Course> Courses;
}

The courses list returned empty and with some help of the debugger tool I found out at the time of creating the object the Id property is 0 of course. In my query I am filtering on student Id but at the time of executing the method to fill the Courses list in the constructor the Id of student will always be 0 becuase it's set at a later stage and the result will be no courses in the list.

I'm wondering if I should put a check for an ICollection property after the other properties are set and if so execute a method on the object that in return executes the method that's now inside the constructor?

I can't call any methods on TModel, else it would be as simple as finding if TModel has a collection property and call obj.FillCollection(); after the Id property has been assigned in the GetEntitiesFromDb method.

I was also thinking about recursion. Again I'd have to find if obj has a collection property and then call GetEntitiesFromDB but it seems undoable because I also need to find out the type in between <> and I Can't send any customquery from the outside...

Maybe tackle it from a whole other perspective?

I can really use some advice on how to tackle this problem.





Aucun commentaire:

Enregistrer un commentaire