C# - Build Your Own ORM - Part 5 - Update and Delete
In the previous post, of this series, I provided a simple function to Insert new objects into a table. In this, the final post, I will be providing functions to Update and Delete objects along with ideas on ways to extend and improve on the existing code. Recall that my objective is to demonstrate that one does not require much code to duplicate the basic functionality of a typical ORM and thus, one should seriously consider building their own.
The Update Function
The Update function's algorithm is, as expected, similar to that of the Insert function in the previous post. The function cycles through all the public fields of the given object and compiles the associated values into a list taking care to isolate the object's "id" as it must be added to the end of the list. This is because the "id" will be applied to the resulting SQL as the last parameter. The function then builds the requisite SQL and executes it.
As with the Insert function, there are a number of assumptions made about the object-relational mapping. The "id" field is expected to represent a single unique key column in the corresponding table. The object type's name must match a table name. And finally, the object field names must match table column names.
1: public static int Update(System.Data.Odbc.OdbcConnection conn, object obj)
2: {
3: string sql, field_names = null;
4: System.Type obj_type;
5: System.Collections.ArrayList param_vals;
6: object id=null;
7: int res;
8: System.Data.Odbc.OdbcCommand cmd;
9:
10: obj_type = obj.GetType();
11: param_vals = new System.Collections.ArrayList();
12:
13: // iterate through all public fields of the given object
14: foreach (System.Reflection.FieldInfo obj_field in obj_type.GetFields())
15: {
16: // if the current field is not the "id" field
17: if (obj_field.Name != "id")
18: {
19: // record the field's name and value
20: field_names = Append_Str(field_names, obj_field.Name + "=?");
21: param_vals.Add(obj_field.GetValue(obj));
22: }
23: // otherwise record the id's value
24: else
25: id = obj_field.GetValue(obj);
26: }
27: // since the id is the last parameter in the sql query, we add it to the end of the parameter list
28: param_vals.Add(id);
29:
30: // build the necessary update query
31: sql = "update " + obj_type.Name + " set " + field_names + " where id=?";
32:
33: // and execute
34: cmd = Build_Command(conn, sql, param_vals.ToArray());
35: res = cmd.ExecuteNonQuery();
36:
37: return res;
38: }
The Delete Function
The Delete function is very simple. It extracts the "id" from the given object and uses it to build the necessary SQL.
1: public static void Delete (System.Data.Odbc.OdbcConnection conn, object obj)
2: {
3: string sql;
4: System.Type obj_type;
5: System.Data.Odbc.OdbcCommand cmd;
6: object id=null;
7:
8: obj_type = obj.GetType();
9:
10: // iterate through all public fields of the given object to record it's "id"
11: foreach (System.Reflection.FieldInfo field in obj_type.GetFields())
12: if (field.Name == "id")
13: id = field.GetValue(obj);
14:
15: // consruct and execute the necessary sql
16: sql = "delete from " + obj_type.Name + " where id=?";
17: cmd = Build_Command(conn, sql, id);
18: cmd.ExecuteNonQuery();
19: }
Usage
Following is some sample code illustrating the expected usage of these functions.1: System.Data.Odbc.OdbcConnection conn;
2: job new_job;
3:
4: conn = new System.Data.Odbc.OdbcConnection("...");
5: conn.Open();
6:
7: job = new job();
8: job.id = 2;
9: job.title = "Android Team Leader";
10: job.application_date = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day);
11: job.agent_id = 2;
12:
13: orm.Update(conn, job);
14: orm.Delete(conn, job);
15:
16: conn.Close();
Improvements
I've already mentioned possible improvements such as attribute or XML based configuration options along with additional parameters to the various functions. By now though, you might have noticed the repeating occurrence of the OdbcConnection parameter in most functions. This suggests that we could internalise it within the "orm" class and stop declaring its member functions as "static". Along with a shift from ODBC specific classes to their base classes, this would allow us to create child classes representing various database types, such MySQL, MS SQL Server, or Oracle. The child classes could then provide their own SQL implementations.
Another suggested improvement is the addition of functions that return a single object, return a single value, and return a list representing a single column. All these functions can be easily implemented with the existing code.
The Complete Code
1: public class orm
2: {
3: public static void Populate_Object(object obj, System.Data.Odbc.OdbcDataReader data)
4: {
5: int c;
6: string col_name;
7: object col_val;
8: System.Reflection.FieldInfo obj_field;
9: System.Type obj_type;
10:
11: if (obj!=null && data!=null)
12: {
13: obj_type=obj.GetType();
14:
15: // iterate through the datareader columns and look for matching object fields
16: for (c = 0; c < data.FieldCount; c++)
17: {
18: col_name = data.GetName(c);
19:
20: // look for an object field with the same name as the current column
21: obj_field = obj_type.GetField(col_name);
22: if (obj_field != null)
23: {
24: // take the actual value for the current column and...
25: col_val = data.GetValue(c);
26:
27: // write it into the matching object field
28: obj_field.SetValue(obj, col_val);
29: }
30: }
31: }
32: }
33:
34: public static System.Collections.Generic.List<T> Select<T>(System.Data.Odbc.OdbcConnection conn, string sql, params object[] param_vals) where T : new()
35: {
36: System.Collections.Generic.List<T> res = null;
37: T obj;
38: System.Data.Odbc.OdbcDataReader data;
39: System.Data.Odbc.OdbcCommand cmd;
40:
41: // prepare sql query and add parameter values if necessary
42: cmd = Build_Command(conn, sql, param_vals);
43:
44: // execute query and convert result into an object list
45: data = cmd.ExecuteReader();
46: if (data.HasRows)
47: {
48: res = new System.Collections.Generic.List<T>();
49: while (data.Read())
50: {
51: obj = new T();
52: Populate_Object(obj, data);
53:
54: res.Add(obj);
55: }
56: }
57: data.Close();
58:
59: return res;
60: }
61:
62: public static System.Data.Odbc.OdbcCommand Build_Command(System.Data.Odbc.OdbcConnection conn, string sql, params object[] param_vals)
63: {
64: System.Data.Odbc.OdbcCommand cmd = null;
65: int c;
66:
67: cmd = new System.Data.Odbc.OdbcCommand(sql, conn);
68:
69: // cycle through function parameters and insert as sql command parameters
70: if (param_vals != null && param_vals.Length > 0)
71: {
72: for (c = 0; c < param_vals.Length; c++)
73: {
74: cmd.Parameters.AddWithValue(c.ToString(), param_vals[c]);
75: }
76: }
77: return cmd;
78: }
79:
80: public static string Append_Str(string orig_str, string new_str)
81: {
82: string res = orig_str;
83:
84: if (!string.IsNullOrEmpty(new_str))
85: {
86: if (!string.IsNullOrEmpty(orig_str))
87: res = orig_str + ", " + new_str;
88: else
89: res = new_str;
90: }
91: return res;
92: }
93:
94: public static void Insert(System.Data.Odbc.OdbcConnection conn, object obj)
95: {
96: System.Type obj_type;
97: string field_names=null, param_placeholders=null, sql;
98: System.Collections.ArrayList param_vals;
99: System.Data.Odbc.OdbcCommand cmd;
100: object id;
101:
102: obj_type = obj.GetType();
103: param_vals = new System.Collections.ArrayList();
104:
105: // cycle through all public fields of the given object
106: foreach (System.Reflection.FieldInfo obj_field in obj_type.GetFields())
107: {
108: // ignore id field and assume it's an auto-generated key column
109: if (obj_field.Name!="id")
110: {
111: // include object field name as table column name
112: field_names = Append_Str(field_names, obj_field.Name);
113:
114: // add parameter placeholder
115: param_placeholders = Append_Str(param_placeholders, "?");
116:
117: // record object field value to insert as table column value
118: param_vals.Add(obj_field.GetValue(obj));
119: }
120: }
121:
122: // build "insert" sql command
123: sql = "insert into " + obj_type.Name +
124: " (" + field_names + ") output inserted.id values (" + param_placeholders + ")";
125:
126: // execute sql and place new key id value into given object
127: cmd = Build_Command(conn, sql, param_vals.ToArray());
128: id = cmd.ExecuteScalar();
129: if (id!=null)
130: obj_type.GetField("id").SetValue(obj, id);
131: }
132:
133: public static int Update(System.Data.Odbc.OdbcConnection conn, object obj)
134: {
135: string sql, field_names = null;
136: System.Type obj_type;
137: System.Collections.ArrayList param_vals;
138: object id=null;
139: int res;
140: System.Data.Odbc.OdbcCommand cmd;
141:
142: obj_type = obj.GetType();
143: param_vals = new System.Collections.ArrayList();
144:
145: // iterate through all public fields of the given object
146: foreach (System.Reflection.FieldInfo obj_field in obj_type.GetFields())
147: {
148: // if the current field is not the "id" field
149: if (obj_field.Name != "id")
150: {
151: // record the field's name and value
152: field_names = Append_Str(field_names, obj_field.Name + "=?");
153: param_vals.Add(obj_field.GetValue(obj));
154: }
155: // otherwise record the id's value
156: else
157: id = obj_field.GetValue(obj);
158: }
159: // since the id is the last parameter in the sql query, we add it to the end of the parameter list
160: param_vals.Add(id);
161:
162: // build the necessary update query
163: sql = "update " + obj_type.Name + " set " + field_names + " where id=?";
164:
165: // and execute
166: cmd = Build_Command(conn, sql, param_vals.ToArray());
167: res = cmd.ExecuteNonQuery();
168:
169: return res;
170: }
171:
172: public static void Delete (System.Data.Odbc.OdbcConnection conn, object obj)
173: {
174: string sql;
175: System.Type obj_type;
176: System.Data.Odbc.OdbcCommand cmd;
177: object id=null;
178:
179: obj_type = obj.GetType();
180:
181: // iterate through all public fields of the given object to record it's "id"
182: foreach (System.Reflection.FieldInfo field in obj_type.GetFields())
183: if (field.Name == "id")
184: id = field.GetValue(obj);
185:
186: // consruct and execute the necessary sql
187: sql = "delete from " + obj_type.Name + " where id=?";
188: cmd = Build_Command(conn, sql, id);
189: cmd.ExecuteNonQuery();
190: }
191: }