Monday, March 2, 2009

DBObject.Where

Ok it works. I also created a checker for the opertators so we don't get invalid/malicious code.


///
/// Returns a DBList of objects that match the Where
///

/// Property to Match
/// "=", "<", ">", "LIKE"
/// Value to Match
/// Type of the object to get (Must inherent from DBOBject)
/// Connection String to use
///
static protected DBList Where(string property, string evaluator, object value, Type who_type, string connection)
{
DBList list = new DBList();

//Find the Column Name and check that we have everything, if fail return the blank list
string column_name = DBObject.GetColumn(property, who_type);
if (column_name == string.Empty || evaluator == string.Empty || value == null) { return list; }
//Check the evaluator, If fail, Return a blank list
evaluator = DBObject.CheckEvaluator(evaluator);
if (evaluator == string.Empty) { return list; }

//Create the WHERE
string query = DBObject.SelectQuery(who_type) + " WHERE " + column_name + " " + evaluator + "?value";

using (MySqlConnection conn = new MySqlConnection(connection))
{
MySqlCommand cmd = new MySqlCommand(query, conn);
cmd.Parameters.AddWithValue("?value", value);

try
{
conn.Open();
MySqlDataReader reader = cmd.ExecuteReader();

while (reader.Read())
{
DBObject obj = (DBObject)DBObject.FromReader(reader, who_type);
list.Add( obj );
}

conn.Close();
}
catch (Exception ex)
{
Console.WriteLine("\n\nERROR:\n" + ex.Message);
}
}

//No matter what happens, always return a valid list
return list;
}


///
/// Accepted Evaluators: '<', '<=', '>', '>=', '=', '!=', 'LIKE', 'REGEXP', 'IS NOT'
///

///
///
static protected string CheckEvaluator(string evaluator)
{
//Lower and trim
evaluator = evaluator.ToLower().Trim();

switch (evaluator)
{
case "<":
return evaluator;
break;
case "<=":
return evaluator;
break;
case ">":
return evaluator;
break;
case ">=":
return evaluator;
break;
case "=":
return evaluator;
break;
case "!=":
return evaluator;
break;
case "like":
return evaluator;
break;
case "regexp":
return evaluator;
break;
case "is not":
return evaluator;
break;
default:
return string.Empty;
}
}


And an example:


//Now lets try to get a list of users
Console.WriteLine("\n\nTrying the Where Methiod");
DBList users1 = DBObject.Where("id", "<=", 1313, typeof(DBUser), Utility.connMy);
foreach (DBUser u4 in users1)
{
Console.WriteLine(u4.ID + " : " + u4.FullName);
}

Next step.

I have two thoughts of what the next step should be. The first is the ability to return a collection of objects (maybe LINQ computable, but I'm not worried about that right now.) The other is that the object should be able to fetch it's own tags.

In my system, each object has tags associated with it. This allows objects to 'subscribe' to other objects. For example, we have a user object and a task object. The user can use tags to subscribe to a task (or many tasks). Many users can subscribe to a same task using this method. The tasks also have an optional prefix. This prefix can say things like; who owns the task, who can modify it, who can complete it, etc. (Just having the tag means you've subscribed and can thus view it.)
So when I get the object it really needs to go and fetch all if it's tags as well.

Because objects rarely have only a single tag, I should probably start with the collections. I'm going to call them lists from now on. These lists must inherit from CollectionBase class in order keep compatibility.

So I think what I want, is a DBList : CollectionBase object that can be used as the return value for static methods.

So I could do something like 
(DBList)DBObject.Where("Age", ">=", "25", typeof(DBUser), Utility.connMy);
and it will return a list of all the users whos age is 25 or older. Or
(DBList)DBObject.Where("Nake", "LIKE", "%chris%", typeof(DBUser), Utility.connMy);

I'm not opposed at all of passing the evaluator as text (">=", "LIKE", "=", "<", etc.) Where this does become a problem is when we need to specify more than a single Where clause. But I figure thats a bridge we can cross when we come to it.

Wednesday, February 25, 2009

DBObject rev .03

Alright, now I have a working .By
It actually simplifies things a lot, to the point I'm starting to double think making everything static.

As you can see, you can simplly call something like:

DBUser chris = (DBUser)DBObject.By("firstname", "chris", typeof(DBUser), Utility.connMy);
DBUser bob = (DBUser)DBObject.By("id", "1327", typeof(DBUser), Utility.connMy);


And get back a valid user.

This isn't prefect, for example, what if there are two people named Chris? this would only return an object for the first record returned. Which leads into my next step, returning a list of DBObjects.


///
/// This is the baseclass for DB Object created by Chris Richards
///

[Serializable]
public class DBObject
{
//Holds all the properties in the object
protected Hashtable _properties = new Hashtable();

protected DBObject() { }


///
/// Get the Object By a single property
///

/// The Property you want to get the Object By
/// Value of the Property
/// Type of the object to get (Must inherent from DBOBject)
/// Connection String to use
///
static protected DBObject By(string property, object value, Type who_type, string connection)
{
DBObject obj = null; //Either it will be set to a valid object, or it will remain null

//Find the Column Name for the property requested
string column_name = DBObject.GetColumn(property, who_type);

if (column_name != string.Empty)
{
//Append the WHERE clause
string query = DBObject.SelectQuery(who_type) + " WHERE " + column_name + "=?value";

using (MySqlConnection conn = new MySqlConnection(connection))
{
MySqlCommand cmd = new MySqlCommand(query, conn);
cmd.Parameters.AddWithValue("?value", value);

try
{
conn.Open();
MySqlDataReader reader = cmd.ExecuteReader();

if (reader.Read())
{
obj = (DBObject)DBObject.FromReader(reader, who_type);
}

conn.Close();
}
catch (Exception ex)
{
Console.WriteLine("\n\nERROR:\n" + ex.Message);
}
}

}

//Return what we have
return obj;
}

///
/// Gets the Column name of a Property
///

/// Name of the Property
/// Type of the object to get (Must inherent from DBOBject)
///
static protected string GetColumn(string property, Type who_type)
{
foreach (PropertyInfo who_properity in who_type.GetProperties())
{
//Find the matching property
if (who_properity.Name.ToLower() == property.ToLower())
{
foreach (object attribute in who_properity.GetCustomAttributes(false))
{
//Get the column
if (attribute is DBColumn)
{
return ((DBColumn)attribute).Column;
}
}
}
}

//Couldn't find it
return string.Empty;
}

///
/// Gets the SELECT ... FROM query for this object
///

/// Type of the object to get (Must inherent from DBOBject)
///
static protected string SelectQuery(Type who_type)
{
//Generate the Select Query
string _select = "SELECT";

//Now Set all the select columns
foreach (PropertyInfo properity in who_type.GetProperties())
{
foreach (object attribute in properity.GetCustomAttributes(false))
{
if (attribute is DBColumn)
{
_select += " " + ((DBColumn)attribute).Column + ",";
}
}
}

//Remove the last Comma
_select = _select.Substring(0, _select.Length - 1);

//Get the Table Name for this Object
foreach (object attr in who_type.GetCustomAttributes(false))
{
if (attr is DBTable)
{
//Add the Table to our List
_select += " FROM " + ((DBTable)attr).Table;
}
}
//Now Return it
return _select;
}

///
/// This will populate the object from the reader.
/// It will not advance the reader.
///

///
/// Type of the object to get (Must inherent from DBOBject)
///
static protected DBObject FromReader(MySqlDataReader reader, Type who_type)
{
DBObject who = (DBObject)Activator.CreateInstance(who_type, true);
//Look for all the properities
foreach (PropertyInfo properity in who_type.GetProperties())
{
if (properity.CanWrite)
{
object[] attributes = properity.GetCustomAttributes(false);
foreach (object attribute in attributes)
{
if (attribute is DBColumn)
{
try
{
properity.SetValue(who, reader[((DBColumn)attribute).Column], null);
}
catch (IndexOutOfRangeException)
{
//Just Skip it
}
catch (Exception ex)
{
string junk = ex.Message;
}
}
}
}
}
return who;
}
}


Here is my test object.


[Serializable]
[DB.MetaData.DBTable("users")]
class DBUser : DBObject
{
[DBColumn("first_name")]
public string FirstName
{
get
{
if (!_properties.ContainsKey("first_name"))
{
_properties["first_name"] = string.Empty;
}
return (string)_properties["first_name"];
}

set
{
_properties["first_name"] = value;
}
}

[DBColumn("last_name")]
public string LastName
{
get
{
if (!_properties.ContainsKey("last_name"))
{
_properties["last_name"] = string.Empty;
}
return (string)_properties["last_name"];
}

set
{
_properties["last_name"] = value;
}
}

public string FullName
{
get { return this.FirstName + " " + this.LastName; }
}

[DBColumn("id", true)]
public int ID
{
get
{
if (!_properties.ContainsKey("id"))
{
_properties["id"] = -1;
}
return (int)_properties["id"];
}

set
{
_properties["id"] = value;
}
}


protected DBUser() { }

static public void DEBUG()
{

string suery = DBObject.SelectQuery(typeof(DBUser)) + " WHERE id=?id";

Console.Write("Going to run: " + suery);

using (MySqlConnection conn = new MySqlConnection(Utility.connMy))
{
MySqlCommand cmd = new MySqlCommand(suery, conn);
cmd.Parameters.AddWithValue("?id", 1327);

try
{
conn.Open();
MySqlDataReader reader = cmd.ExecuteReader();

if (reader.Read())
{
DBUser user = (DBUser)DBObject.FromReader(reader, typeof(DBUser));
Console.WriteLine("\nGot: " + user.FullName);
}

conn.Close();
}
catch (Exception ex)
{
Console.WriteLine("\n\nERROR:\n" + ex.Message);
}
}

//Now try the properity
Console.WriteLine("\n\nTrying the By Methiod (ID)");
DBUser u2 = (DBUser)DBObject.By("id", "1327", typeof(DBUser), Utility.connMy);
if (u2 != null)
{
Console.WriteLine("Got: " + u2.FullName);
}
else
{
Console.WriteLine("Oops, got a null back");
}

//Try another property
Console.WriteLine("\n\nTrying the By Methiod (FirstName)");
DBUser u3 = (DBUser)DBObject.By("firstname", "chris", typeof(DBUser), Utility.connMy);
if (u3 != null)
{
Console.WriteLine("Got: " + u3.FullName);
}
else
{
Console.WriteLine("Oops, got a null back");
}
}
}

Next Steps

So far I think things are going good. I could even start to use this in the real world. But I'd like to complete a few more features first.

Having a .By(Property, Value) method. That way we could get an object by any defined property.

Having something like .Get(Property) and .Set(Property) that will return the value of any property. Right now the child class has to access the underlying _properties hash. I don't necessarily like that idea, it gives the developer a chance to screw up more than just the property they are working with (since all properties are through the same hash.) Also methods like that would mean that the developer wouldn't have to check if the property already exists and if they should return a default value. On the down side, how do you define what the default should be? probably in the attribute.

DBObject rev .02

Ok, here is the code with the static modifications.
I like this better, but I don't like the whole "(DBUser)DBObject.FromReader(reader, typeof(DBUser));" (or "(DBUser)DBUser.FromReader(reader, typeof(DBUser));") thing. It just seems like I'm having to type to much. Then again, the child class could encapsulate some of this passing by type.

DBObject



///
/// This is the baseclass for DB Object created by Chris Richards
///

[Serializable]
public class DBObject
{
//Holds all the properties in the object
protected Hashtable _properties = new Hashtable();

protected DBObject() { }

///
/// This will populate all of the properties from the table by the column 'id'
///

///
///
public object ByID(int id)
{
return new DBObject();
}

///
/// Gets the SELECT ... FROM query for this object
///

static protected string SelectQuery(Type who)
{
//Generate the Select Query
string _select = "SELECT";

//Now Set all the select columns
foreach (PropertyInfo properity in who.GetProperties())
{
foreach (object attribute in properity.GetCustomAttributes(false))
{
if (attribute is DBColumn)
{
_select += " " + ((DBColumn)attribute).Column + ",";
}
}
}

//Remove the last Comma
_select = _select.Substring(0, _select.Length - 1);

//Get the Table Name for this Object
foreach (object attr in who.GetCustomAttributes(false))
{
if (attr is DBTable)
{
//Add the Table to our List
_select += " FROM " + ((DBTable)attr).Table;
}
}
//Now Return it
return _select;
}

///
/// This will populate the object from the reader.
/// It will not advance the reader.
///

///
///
static protected DBObject FromReader(MySqlDataReader reader, Type who_type)
{
DBObject who = (DBObject)Activator.CreateInstance(who_type, true);
//Look for all the properities
foreach (PropertyInfo properity in who_type.GetProperties())
{
if (properity.CanWrite)
{
object[] attributes = properity.GetCustomAttributes(false);
foreach (object attribute in attributes)
{
if (attribute is DBColumn)
{
try
{
properity.SetValue(who, reader[((DBColumn)attribute).Column], null);
}
catch (IndexOutOfRangeException)
{
//Just Skip it
}
catch (Exception ex)
{
//ErrorLog.Log(-1, this.GetType().Name + " reader Error", ex.Message);
string junk = ex.Message;
}
}
}
}
else
{
ErrorLog.Log(-1, "Can't Write " + who.GetType().Name + "'s Properity", "Properity: " + properity.Name);
}
}
return who;
}
}


Test Code


And here is the test example

string suery = DBObject.SelectQuery(typeof(DBUser)) + " WHERE id=?id";

Console.Write("Going to run: " + suery);

using (MySqlConnection conn = new MySqlConnection(Utility.connMy))
{
MySqlCommand cmd = new MySqlCommand(suery, conn);
cmd.Parameters.AddWithValue("?id", 1327);

try
{
conn.Open();
MySqlDataReader reader = cmd.ExecuteReader();

if (reader.Read())
{
DBUser user = (DBUser)DBObject.FromReader(reader, typeof(DBUser));
Console.WriteLine("\nGot: " + user.FirstName);
}

conn.Close();
}
catch (Exception ex)
{
Console.WriteLine("\n\nERROR:\n" + ex.Message);
}
}

Static again

I haven't been able to work on this for a while because of school and work. I was rereading my last post and I noticed that I have to create a new DBUser in order to get the SelectQuery for it. I don't like this, it violates my 'new' rule. One of the reasons it's not a static method is because it caches the query string. But it doesn't really matter if it caches the query if you call a new object each time, it eliminates the point of the cache.

So this is where I'm going to pick things back up, I'm going to make those two static and post the code.

The only problem is, how so I make these static? The base needs to know the child's type to work. When they wheren't static I could just use the this keyword. But as a static methiod I no longer have access to that keyword. The only alternative I can think of off the top of my head is requireing a type passed to both methods. I don't know that I quite like that idea.

Thursday, January 1, 2009

Decisions

So as I'm thinking about the DBOBject I just created. I ask myself, "Why make a protected method FromReader() vs just using a constructor? Why a protected method and not just a static method?"

The way I see it, using the "new" keyword to create a database object should create a table in the database. So using the datareader in the constructor isn't an option because we are retrieving the object from the database, not adding one to it.

The static question is a bit tricker. I'm not 100% sure that it shouldn't be a a static method. It would eliminate a step. right now you have to do something like:


DBUser user = new DBUser();

string suery = user.SelectQuery + " WHERE id=?id";

Console.Write("Going to run: " + suery);

using (MySqlConnection conn = new MySqlConnection(Utility.connMy))
{
MySqlCommand cmd = new MySqlCommand(suery, conn);
cmd.Parameters.AddWithValue("?id", 1327);

try
{
conn.Open();
MySqlDataReader reader = cmd.ExecuteReader();

if (reader.Read())
{
user.FromReader(reader);
}

conn.Close();
}
catch (Exception ex)
{
Console.WriteLine("\n\nERROR:\n" + ex.Message);
}
}



Which means we are still calling new on the object (even though it's internally, it still violates the whole 'new should be to add a record' idea. If I changed .FromReader to be a static method, then we could simply do this:


DBUser user = null;

string suery = user.SelectQuery + " WHERE id=?id";

Console.Write("Going to run: " + suery);

using (MySqlConnection conn = new MySqlConnection(Utility.connMy))
{
MySqlCommand cmd = new MySqlCommand(suery, conn);
cmd.Parameters.AddWithValue("?id", 1327);

try
{
conn.Open();
MySqlDataReader reader = cmd.ExecuteReader();

if (reader.Read())
{
user = DBUser.FromReader(reader);
}

conn.Close();
}
catch (Exception ex)
{
Console.WriteLine("\n\nERROR:\n" + ex.Message);
}
}


It still takes the same number of lines of code, but it doesn't violate the "new keyword" rule.

I think I like the method being static. Mostly because it stays constant.