Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 617 lines (594 sloc) 28.273 kb
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
1 using System;
2 using System.Collections.Generic;
3 using System.Collections.Specialized;
4 using System.Configuration;
5 using System.Data;
6 using System.Data.Common;
7 using System.Dynamic;
8 using System.Linq;
9 using System.Text;
10 using System.Threading.Tasks;
11 using System.Data.SqlClient;
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
12 using System.Text.RegularExpressions;
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
13
14 namespace Massive {
15 public static class ObjectExtensions {
16 /// <summary>
17 /// Extension method for adding in a bunch of parameters
18 /// </summary>
19 public static void AddParams(this DbCommand cmd, params object[] args) {
20 foreach (var item in args) {
21 AddParam(cmd, item);
22 }
23 }
24 /// <summary>
25 /// Extension for adding single parameter
26 /// </summary>
27 public static void AddParam(this DbCommand cmd, object item) {
28 var p = cmd.CreateParameter();
29 p.ParameterName = string.Format("@{0}", cmd.Parameters.Count);
30 if (item == null) {
31 p.Value = DBNull.Value;
32 } else {
33 if (item.GetType() == typeof(Guid)) {
34 p.Value = item.ToString();
35 p.DbType = DbType.String;
36 p.Size = 4000;
37 } else if (item.GetType() == typeof(ExpandoObject)) {
38 var d = (IDictionary<string, object>)item;
39 p.Value = d.Values.FirstOrDefault();
40 } else {
41 p.Value = item;
42 }
43 if (item.GetType() == typeof(string))
44 p.Size = ((string)item).Length > 4000 ? -1 : 4000;
45 }
46 cmd.Parameters.Add(p);
47 }
48 /// <summary>
49 /// Turns an IDataReader to a Dynamic list of things
50 /// </summary>
51 public static List<dynamic> ToExpandoList(this IDataReader rdr) {
52 var result = new List<dynamic>();
53 while (rdr.Read()) {
54 result.Add(rdr.RecordToExpando());
55 }
56 return result;
57 }
58 public static dynamic RecordToExpando(this IDataReader rdr) {
59 dynamic e = new ExpandoObject();
60 var d = e as IDictionary<string, object>;
007d2006 »
2011-04-29 When returning data from a Query, convert DBNull values to null so ca…
61 for (int i = 0; i < rdr.FieldCount; i++)
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
62 d.Add(rdr.GetName(i), DBNull.Value.Equals(rdr[i]) ? null : rdr[i]);
63 return e;
64 }
65 /// <summary>
66 /// Turns the object into an ExpandoObject
67 /// </summary>
68 public static dynamic ToExpando(this object o) {
69 var result = new ExpandoObject();
70 var d = result as IDictionary<string, object>; //work with the Expando as a Dictionary
71 if (o.GetType() == typeof(ExpandoObject)) return o; //shouldn't have to... but just in case
72 if (o.GetType() == typeof(NameValueCollection) || o.GetType().IsSubclassOf(typeof(NameValueCollection))) {
73 var nv = (NameValueCollection)o;
74 nv.Cast<string>().Select(key => new KeyValuePair<string, object>(key, nv[key])).ToList().ForEach(i => d.Add(i));
75 } else {
76 var props = o.GetType().GetProperties();
77 foreach (var item in props) {
78 d.Add(item.Name, item.GetValue(o, null));
79 }
80 }
81 return result;
82 }
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
83
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
84 /// <summary>
85 /// Turns the object into a Dictionary
86 /// </summary>
87 public static IDictionary<string, object> ToDictionary(this object thingy) {
88 return (IDictionary<string, object>)thingy.ToExpando();
89 }
90 }
91 /// <summary>
92 /// A class that wraps your database table in Dynamic Funtime
93 /// </summary>
47c98a89 »
2011-06-08 Cleaned up a bit of stuff
94 public class DynamicModel : DynamicObject {
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
95 DbProviderFactory _factory;
4e1713b4 »
2011-06-25 Added Prototype for generating an empty object with defaults from DB.…
96 string ConnectionString;
97 public static DynamicModel Open(string connectionStringName) {
98 dynamic dm = new DynamicModel(connectionStringName);
99 return dm;
100 }
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
101 public DynamicModel(string connectionStringName, string tableName = "",
102 string primaryKeyField = "", string descriptorField = "") {
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
103 TableName = tableName == "" ? this.GetType().Name : tableName;
104 PrimaryKeyField = string.IsNullOrEmpty(primaryKeyField) ? "ID" : primaryKeyField;
4e1713b4 »
2011-06-25 Added Prototype for generating an empty object with defaults from DB.…
105 var _providerName = "System.Data.SqlClient";
abd7f24f »
2011-06-17 Fixed dumb dumb bug with provider name setting
106 _factory = DbProviderFactories.GetFactory(_providerName);
4e1713b4 »
2011-06-25 Added Prototype for generating an empty object with defaults from DB.…
107 ConnectionString = ConfigurationManager.ConnectionStrings[connectionStringName].ConnectionString;
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
108 }
3134bdce »
2011-07-01 Added back Prototype etc - seems to have been clipped with auto merge?
109
110 /// <summary>
111 /// Creates a new Expando from a Form POST - white listed against the columns in the DB
112 /// </summary>
113 public dynamic CreateFrom(NameValueCollection coll) {
114 dynamic result = new ExpandoObject();
115 var dc = (IDictionary<string, object>)result;
116 var schema = Schema;
117 //loop the collection, setting only what's in the Schema
118 foreach (var item in coll.Keys) {
119 var exists = schema.Any(x => x.COLUMN_NAME.ToLower() == item.ToString().ToLower());
120 if (exists) {
121 var key = item.ToString();
122 var val = coll[key];
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
123 dc.Add(key, val);
3134bdce »
2011-07-01 Added back Prototype etc - seems to have been clipped with auto merge?
124 }
125 }
126 return result;
127 }
128 /// <summary>
129 /// Gets a default value for the column
130 /// </summary>
131 public dynamic DefaultValue(dynamic column) {
132 dynamic result = null;
133 string def = column.COLUMN_DEFAULT;
134 if (String.IsNullOrEmpty(def)) {
135 result = null;
136 } else if (def == "getdate()" || def == "(getdate())") {
137 result = DateTime.Now.ToShortDateString();
138 } else if (def == "newid()") {
139 result = Guid.NewGuid().ToString();
140 } else {
141 result = def.Replace("(", "").Replace(")", "");
142 }
143 return result;
144 }
145 /// <summary>
146 /// Creates an empty Expando set with defaults from the DB
147 /// </summary>
148 public dynamic Prototype {
149 get {
150 dynamic result = new ExpandoObject();
151 var schema = Schema;
152 foreach (dynamic column in schema) {
153 var dc = (IDictionary<string, object>)result;
154 dc.Add(column.COLUMN_NAME, DefaultValue(column));
155 }
156 result._Table = this;
157 return result;
158 }
159 }
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
160 private string _descriptorField;
161 public string DescriptorField {
162 get {
163 return _descriptorField;
164 }
165 }
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
166 /// <summary>
bb4497e8 »
2011-06-16 Added a Schema property that returns, dynamically, all the informatio…
167 /// List out all the schema bits for use with ... whatever
168 /// </summary>
3134bdce »
2011-07-01 Added back Prototype etc - seems to have been clipped with auto merge?
169 IEnumerable<dynamic> _schema;
bb4497e8 »
2011-06-16 Added a Schema property that returns, dynamically, all the informatio…
170 public IEnumerable<dynamic> Schema {
171 get {
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
172 if (_schema == null)
173 _schema = Query("SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @0", TableName);
3134bdce »
2011-07-01 Added back Prototype etc - seems to have been clipped with auto merge?
174 return _schema;
bb4497e8 »
2011-06-16 Added a Schema property that returns, dynamically, all the informatio…
175 }
176 }
177
178 /// <summary>
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
179 /// Enumerates the reader yielding the result - thanks to Jeroen Haegebaert
180 /// </summary>
181 public virtual IEnumerable<dynamic> Query(string sql, params object[] args) {
182 using (var conn = OpenConnection()) {
183 var rdr = CreateCommand(sql, conn, args).ExecuteReader();
184 while (rdr.Read()) {
185 yield return rdr.RecordToExpando(); ;
186 }
187 }
188 }
189 public virtual IEnumerable<dynamic> Query(string sql, DbConnection connection, params object[] args) {
190 using (var rdr = CreateCommand(sql, connection, args).ExecuteReader()) {
191 while (rdr.Read()) {
192 yield return rdr.RecordToExpando(); ;
193 }
194 }
195 }
196 /// <summary>
197 /// Returns a single result
198 /// </summary>
199 public virtual object Scalar(string sql, params object[] args) {
200 object result = null;
201 using (var conn = OpenConnection()) {
202 result = CreateCommand(sql, conn, args).ExecuteScalar();
203 }
204 return result;
205 }
206 /// <summary>
207 /// Creates a DBCommand that you can use for loving your database.
208 /// </summary>
209 DbCommand CreateCommand(string sql, DbConnection conn, params object[] args) {
210 var result = _factory.CreateCommand();
211 result.Connection = conn;
212 result.CommandText = sql;
213 if (args.Length > 0)
214 result.AddParams(args);
215 return result;
216 }
217 /// <summary>
218 /// Returns and OpenConnection
219 /// </summary>
220 public virtual DbConnection OpenConnection() {
221 var result = _factory.CreateConnection();
4e1713b4 »
2011-06-25 Added Prototype for generating an empty object with defaults from DB.…
222 result.ConnectionString = ConnectionString;
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
223 result.Open();
224 return result;
225 }
226 /// <summary>
227 /// Builds a set of Insert and Update commands based on the passed-on objects.
228 /// These objects can be POCOs, Anonymous, NameValueCollections, or Expandos. Objects
229 /// With a PK property (whatever PrimaryKeyField is set to) will be created at UPDATEs
230 /// </summary>
231 public virtual List<DbCommand> BuildCommands(params object[] things) {
232 var commands = new List<DbCommand>();
233 foreach (var item in things) {
234 if (HasPrimaryKey(item)) {
235 commands.Add(CreateUpdateCommand(item, GetPrimaryKey(item)));
236 } else {
237 commands.Add(CreateInsertCommand(item));
238 }
239 }
240 return commands;
241 }
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
242
bb4497e8 »
2011-06-16 Added a Schema property that returns, dynamically, all the informatio…
243
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
244 public virtual int Execute(DbCommand command) {
245 return Execute(new DbCommand[] { command });
246 }
4e1713b4 »
2011-06-25 Added Prototype for generating an empty object with defaults from DB.…
247
248 public virtual int Execute(string sql, params object[] args) {
249 return Execute(CreateCommand(sql, null, args));
250 }
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
251 /// <summary>
252 /// Executes a series of DBCommands in a transaction
253 /// </summary>
254 public virtual int Execute(IEnumerable<DbCommand> commands) {
255 var result = 0;
256 using (var conn = OpenConnection()) {
257 using (var tx = conn.BeginTransaction()) {
258 foreach (var cmd in commands) {
259 cmd.Connection = conn;
260 cmd.Transaction = tx;
261 result += cmd.ExecuteNonQuery();
262 }
263 tx.Commit();
264 }
265 }
266 return result;
267 }
268 public virtual string PrimaryKeyField { get; set; }
269 /// <summary>
270 /// Conventionally introspects the object passed in for a field that
271 /// looks like a PK. If you've named your PrimaryKeyField, this becomes easy
272 /// </summary>
273 public virtual bool HasPrimaryKey(object o) {
274 return o.ToDictionary().ContainsKey(PrimaryKeyField);
275 }
276 /// <summary>
277 /// If the object passed in has a property with the same name as your PrimaryKeyField
278 /// it is returned here.
279 /// </summary>
280 public virtual object GetPrimaryKey(object o) {
281 object result = null;
282 o.ToDictionary().TryGetValue(PrimaryKeyField, out result);
283 return result;
284 }
285 public virtual string TableName { get; set; }
286 /// <summary>
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
287 /// Returns all records complying with the passed-in WHERE clause and arguments,
288 /// ordered as specified, limited (TOP) by limit.
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
289 /// </summary>
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
290 public virtual IEnumerable<dynamic> All(string where = "", string orderBy = "", int limit = 0, string columns = "*", params object[] args) {
291 string sql = BuildSelect(where, orderBy, limit);
292 return Query(string.Format(sql, columns, TableName), args);
293 }
294 private static string BuildSelect(string where, string orderBy, int limit) {
295 string sql = limit > 0 ? "SELECT TOP " + limit + " {0} FROM {1} " : "SELECT {0} FROM {1} ";
296 if (!string.IsNullOrEmpty(where))
297 sql += where.Trim().StartsWith("where", StringComparison.OrdinalIgnoreCase) ? where : "WHERE " + where;
298 if (!String.IsNullOrEmpty(orderBy))
299 sql += orderBy.Trim().StartsWith("order by", StringComparison.OrdinalIgnoreCase) ? orderBy : " ORDER BY " + orderBy;
300 return sql;
301 }
302
303 /// <summary>
304 /// Returns a dynamic PagedResult. Result properties are Items, TotalPages, and TotalRecords.
305 /// </summary>
306 public virtual dynamic Paged(string where = "", string orderBy = "", string columns = "*", int pageSize = 20, int currentPage = 1, params object[] args) {
307 dynamic result = new ExpandoObject();
308 var countSQL = string.Format("SELECT COUNT({0}) FROM {1}", PrimaryKeyField, TableName);
309 if (String.IsNullOrEmpty(orderBy))
310 orderBy = PrimaryKeyField;
311
312 if (!string.IsNullOrEmpty(where)) {
313 if (!where.Trim().StartsWith("where", StringComparison.OrdinalIgnoreCase)) {
314 where = "WHERE " + where;
315 }
316 }
317 var sql = string.Format("SELECT {0} FROM (SELECT ROW_NUMBER() OVER (ORDER BY {2}) AS Row, {0} FROM {3} {4}) AS Paged ", columns, pageSize, orderBy, TableName, where);
318 var pageStart = (currentPage - 1) * pageSize;
319 sql += string.Format(" WHERE Row > {0} AND Row <={1}", pageStart, (pageStart + pageSize));
320 countSQL += where;
321 result.TotalRecords = Scalar(countSQL, args);
322 result.TotalPages = result.TotalRecords / pageSize;
323 if (result.TotalRecords % pageSize > 0)
324 result.TotalPages += 1;
325 result.Items = Query(string.Format(sql, columns, TableName), args);
326 return result;
327 }
328 /// <summary>
329 /// Returns a single row from the database
330 /// </summary>
331 public virtual dynamic Single(string where, params object[] args) {
332 var sql = string.Format("SELECT * FROM {0} WHERE {1}", TableName, where);
333 return Query(sql, args).FirstOrDefault();
334 }
335 /// <summary>
336 /// Returns a single row from the database
337 /// </summary>
338 public virtual dynamic Single(object key, string columns = "*") {
339 var sql = string.Format("SELECT {0} FROM {1} WHERE {2} = @0", columns, TableName, PrimaryKeyField);
340 return Query(sql, key).FirstOrDefault();
341 }
342 /// <summary>
343 /// This will return a string/object dictionary for dropdowns etc
344 /// </summary>
345 public virtual IDictionary<string, object> KeyValues(string orderBy = "") {
346 if (String.IsNullOrEmpty(DescriptorField))
347 throw new InvalidOperationException("There's no DescriptorField set - do this in your constructor to describe the text value you want to see");
348 var sql = string.Format("SELECT {0},{1} FROM {2} ", PrimaryKeyField, DescriptorField, TableName);
349 if (!String.IsNullOrEmpty(orderBy))
350 sql += "ORDER BY " + orderBy;
351 return (IDictionary<string, object>)Query(sql);
352 }
353
354 /// <summary>
355 /// This will return an Expando as a Dictionary
356 /// </summary>
357 public virtual IDictionary<string, object> ItemAsDictionary(ExpandoObject item) {
358 return (IDictionary<string, object>)item;
359 }
360 //Checks to see if a key is present based on the passed-in value
361 public virtual bool ItemContainsKey(string key, ExpandoObject item) {
362 var dc = ItemAsDictionary(item);
363 return dc.ContainsKey(key);
364 }
365 /// <summary>
366 /// Executes a set of objects as Insert or Update commands based on their property settings, within a transaction.
367 /// These objects can be POCOs, Anonymous, NameValueCollections, or Expandos. Objects
368 /// With a PK property (whatever PrimaryKeyField is set to) will be created at UPDATEs
369 /// </summary>
370 public virtual int Save(params object[] things) {
371 foreach (var item in things) {
372 if (!IsValid(item)) {
373 throw new InvalidOperationException("Can't save this item: " + String.Join("; ", Errors.ToArray()));
374 }
375 }
376 var commands = BuildCommands(things);
377 return Execute(commands);
378 }
25d24c7e »
2011-09-26 Added a hack/fix for expandos in command creation
379 public virtual DbCommand CreateInsertCommand(dynamic expando) {
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
380 DbCommand result = null;
381 var settings = (IDictionary<string, object>)expando;
382 var sbKeys = new StringBuilder();
383 var sbVals = new StringBuilder();
384 var stub = "INSERT INTO {0} ({1}) \r\n VALUES ({2})";
385 result = CreateCommand(stub, null);
386 int counter = 0;
387 foreach (var item in settings) {
388 sbKeys.AppendFormat("{0},", item.Key);
389 sbVals.AppendFormat("@{0},", counter.ToString());
390 result.AddParam(item.Value);
391 counter++;
392 }
393 if (counter > 0) {
394 var keys = sbKeys.ToString().Substring(0, sbKeys.Length - 1);
395 var vals = sbVals.ToString().Substring(0, sbVals.Length - 1);
396 var sql = string.Format(stub, TableName, keys, vals);
397 result.CommandText = sql;
398 } else throw new InvalidOperationException("Can't parse this object to the database - there are no properties set");
399 return result;
400 }
401 /// <summary>
402 /// Creates a command for use with transactions - internal stuff mostly, but here for you to play with
403 /// </summary>
25d24c7e »
2011-09-26 Added a hack/fix for expandos in command creation
404 public virtual DbCommand CreateUpdateCommand(dynamic expando, object key) {
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
405 var settings = (IDictionary<string, object>)expando;
406 var sbKeys = new StringBuilder();
407 var stub = "UPDATE {0} SET {1} WHERE {2} = @{3}";
408 var args = new List<object>();
409 var result = CreateCommand(stub, null);
410 int counter = 0;
411 foreach (var item in settings) {
412 var val = item.Value;
5ad17a52 »
2011-08-30 Changed CurrentCultuerIgnoreCase to OrdinalIgnoreCase - thanks justinvp!
413 if (!item.Key.Equals(PrimaryKeyField, StringComparison.OrdinalIgnoreCase) && item.Value != null) {
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
414 result.AddParam(val);
415 sbKeys.AppendFormat("{0} = @{1}, \r\n", item.Key, counter.ToString());
416 counter++;
417 }
418 }
419 if (counter > 0) {
420 //add the key
421 result.AddParam(key);
422 //strip the last commas
423 var keys = sbKeys.ToString().Substring(0, sbKeys.Length - 4);
424 result.CommandText = string.Format(stub, TableName, keys, PrimaryKeyField, counter);
425 } else throw new InvalidOperationException("No parsable object was sent in - could not divine any name/value pairs");
426 return result;
427 }
428 /// <summary>
429 /// Removes one or more records from the DB according to the passed-in WHERE
430 /// </summary>
431 public virtual DbCommand CreateDeleteCommand(string where = "", object key = null, params object[] args) {
432 var sql = string.Format("DELETE FROM {0} ", TableName);
433 if (key != null) {
434 sql += string.Format("WHERE {0}=@0", PrimaryKeyField);
435 args = new object[] { key };
436 } else if (!string.IsNullOrEmpty(where)) {
5ad17a52 »
2011-08-30 Changed CurrentCultuerIgnoreCase to OrdinalIgnoreCase - thanks justinvp!
437 sql += where.Trim().StartsWith("where", StringComparison.OrdinalIgnoreCase) ? where : "WHERE " + where;
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
438 }
439 return CreateCommand(sql, null, args);
440 }
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
441
442 public bool IsValid(dynamic item) {
443 Errors.Clear();
444 Validate(item);
445 return Errors.Count == 0;
446 }
447
448 //Temporary holder for error messages
449 public IList<string> Errors = new List<string>();
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
450 /// <summary>
451 /// Adds a record to the database. You can pass in an Anonymous object, an ExpandoObject,
452 /// A regular old POCO, or a NameValueColletion from a Request.Form or Request.QueryString
453 /// </summary>
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
454 public virtual dynamic Insert(object o) {
25d24c7e »
2011-09-26 Added a hack/fix for expandos in command creation
455 var ex = o.ToExpando();
456 if (!IsValid(ex)) {
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
457 throw new InvalidOperationException("Can't insert: " + String.Join("; ", Errors.ToArray()));
458 }
25d24c7e »
2011-09-26 Added a hack/fix for expandos in command creation
459 if (BeforeSave(ex)) {
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
460 using (var conn = OpenConnection()) {
25d24c7e »
2011-09-26 Added a hack/fix for expandos in command creation
461 var cmd = CreateInsertCommand((ExpandoObject)ex);
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
462 cmd.Connection = conn;
463 cmd.ExecuteNonQuery();
464 cmd.CommandText = "SELECT @@IDENTITY as newID";
25d24c7e »
2011-09-26 Added a hack/fix for expandos in command creation
465 ex.ID = cmd.ExecuteScalar();
466 Inserted(ex);
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
467 }
25d24c7e »
2011-09-26 Added a hack/fix for expandos in command creation
468 return ex;
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
469 } else {
470 return null;
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
471 }
472 }
473 /// <summary>
474 /// Updates a record in the database. You can pass in an Anonymous object, an ExpandoObject,
475 /// A regular old POCO, or a NameValueCollection from a Request.Form or Request.QueryString
476 /// </summary>
477 public virtual int Update(object o, object key) {
25d24c7e »
2011-09-26 Added a hack/fix for expandos in command creation
478 var ex = o.ToExpando();
479 if (!IsValid(ex)) {
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
480 throw new InvalidOperationException("Can't Update: " + String.Join("; ", Errors.ToArray()));
481 }
482 var result = 0;
25d24c7e »
2011-09-26 Added a hack/fix for expandos in command creation
483 if (BeforeSave(ex)) {
484 result = Execute(CreateUpdateCommand((ExpandoObject)ex, key));
485 Updated(ex);
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
486 }
487 return result;
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
488 }
489 /// <summary>
490 /// Removes one or more records from the DB according to the passed-in WHERE
491 /// </summary>
492 public int Delete(object key = null, string where = "", params object[] args) {
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
493 var deleted = this.Single(key);
494 var result = 0;
495 if (BeforeDelete(deleted)) {
496 result = Execute(CreateDeleteCommand(where: where, key: key, args: args));
497 Deleted(deleted);
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
498 }
499 return result;
500 }
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
501
502 //Hooks
503 public virtual void Validate(dynamic item) { }
504 public virtual void Inserted(dynamic item) { }
505 public virtual void Updated(dynamic item) { }
506 public virtual void Deleted(dynamic item) { }
507 public virtual bool BeforeDelete(dynamic item) { return true; }
508 public virtual bool BeforeSave(dynamic item) { return true; }
509
510 //validation methods
511 public virtual void ValidatesPresenceOf(object value, string message = "Required") {
512 if (value == null)
513 Errors.Add(message);
514 if (String.IsNullOrEmpty(value.ToString()))
515 Errors.Add(message);
516 }
517 //fun methods
518 public virtual void ValidatesNumericalityOf(object value, string message = "Should be a number") {
519 var type = value.GetType().Name;
520 var numerics = new string[] { "Int32", "Int16", "Int64", "Decimal", "Double", "Single", "Float" };
521 if (!numerics.Contains(type)) {
522 Errors.Add(message);
523 }
bb4497e8 »
2011-06-16 Added a Schema property that returns, dynamically, all the informatio…
524 }
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
525 public virtual void ValidateIsCurrency(object value, string message = "Should be money") {
526 if (value == null)
527 Errors.Add(message);
528 decimal val = decimal.MinValue;
529 decimal.TryParse(value.ToString(), out val);
530 if (val == decimal.MinValue)
531 Errors.Add(message);
532
533
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
534 }
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
535
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
536 /// <summary>
4ae06a0a »
2011-06-17 Changed my mind - ActiveRecord is based on some smooth Ruby. Lets use…
537 /// A helpful query tool
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
538 /// </summary>
539 public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result) {
540 //parse the method
4ae06a0a »
2011-06-17 Changed my mind - ActiveRecord is based on some smooth Ruby. Lets use…
541 var constraints = new List<string>();
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
542 var counter = 0;
4ae06a0a »
2011-06-17 Changed my mind - ActiveRecord is based on some smooth Ruby. Lets use…
543 var info = binder.CallInfo;
544 // accepting named args only... SKEET!
ee4539f4 »
2011-06-17 Fixed WHERE spacing in Paged method
545 if (info.ArgumentNames.Count != args.Length) {
4ae06a0a »
2011-06-17 Changed my mind - ActiveRecord is based on some smooth Ruby. Lets use…
546 throw new InvalidOperationException("Please use named arguments for this type of query - the column name, orderby, columns, etc");
547 }
548 //first should be "FindBy, Last, Single, First"
549 var op = binder.Name;
550 var columns = " * ";
551 string orderBy = string.Format(" ORDER BY {0}", PrimaryKeyField);
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
552 string sql = "";
4ae06a0a »
2011-06-17 Changed my mind - ActiveRecord is based on some smooth Ruby. Lets use…
553 string where = "";
554 var whereArgs = new List<object>();
555
556 //loop the named args - see if we have order, columns and constraints
557 if (info.ArgumentNames.Count > 0) {
ee4539f4 »
2011-06-17 Fixed WHERE spacing in Paged method
558
4ae06a0a »
2011-06-17 Changed my mind - ActiveRecord is based on some smooth Ruby. Lets use…
559 for (int i = 0; i < args.Length; i++) {
560 var name = info.ArgumentNames[i].ToLower();
561 switch (name) {
562 case "orderby":
563 orderBy = " ORDER BY " + args[i];
564 break;
565 case "columns":
566 columns = args[i].ToString();
567 break;
568 default:
ee4539f4 »
2011-06-17 Fixed WHERE spacing in Paged method
569 constraints.Add(string.Format(" {0} = @{1}", name, counter));
4ae06a0a »
2011-06-17 Changed my mind - ActiveRecord is based on some smooth Ruby. Lets use…
570 whereArgs.Add(args[i]);
571 counter++;
572 break;
573 }
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
574 }
575 }
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
576
4ae06a0a »
2011-06-17 Changed my mind - ActiveRecord is based on some smooth Ruby. Lets use…
577 //Build the WHERE bits
578 if (constraints.Count > 0) {
579 where = " WHERE " + string.Join(" AND ", constraints.ToArray());
580 }
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
581 //probably a bit much here but... yeah this whole thing needs to be refactored...
582 if (op.ToLower() == "count") {
583 result = Scalar("SELECT COUNT(*) FROM " + TableName + where, whereArgs.ToArray());
584 } else if (op.ToLower() == "sum") {
585 result = Scalar("SELECT SUM(" + columns + ") FROM " + TableName + where, whereArgs.ToArray());
586 } else if (op.ToLower() == "max") {
587 result = Scalar("SELECT MAX(" + columns + ") FROM " + TableName + where, whereArgs.ToArray());
588 } else if (op.ToLower() == "min") {
589 result = Scalar("SELECT MIN(" + columns + ") FROM " + TableName + where, whereArgs.ToArray());
590 } else if (op.ToLower() == "avg") {
591 result = Scalar("SELECT AVG(" + columns + ") FROM " + TableName + where, whereArgs.ToArray());
4ae06a0a »
2011-06-17 Changed my mind - ActiveRecord is based on some smooth Ruby. Lets use…
592 } else {
593
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
594 //build the SQL
595 sql = "SELECT TOP 1 " + columns + " FROM " + TableName + where;
596 var justOne = op.StartsWith("First") || op.StartsWith("Last") || op.StartsWith("Get") || op.StartsWith("Single");
4ae06a0a »
2011-06-17 Changed my mind - ActiveRecord is based on some smooth Ruby. Lets use…
597
a1ec04d7 »
2011-09-26 Big update - removed async bits as they left a connection open. Added…
598 //Be sure to sort by DESC on the PK (PK Sort is the default)
599 if (op.StartsWith("Last")) {
600 orderBy = orderBy + " DESC ";
601 } else {
602 //default to multiple
603 sql = "SELECT " + columns + " FROM " + TableName + where;
604 }
605
606 if (justOne) {
607 //return a single record
608 result = Query(sql + orderBy, whereArgs.ToArray()).FirstOrDefault();
609 } else {
610 //return lots
611 result = Query(sql + orderBy, whereArgs.ToArray());
612 }
613 }
bdfd2a2a »
2011-06-08 Added async methods (thanks to Damien Edwards) and Rails ActiveRecord…
614 return true;
615 }
616 }
b03bf616 »
2011-02-15 light refactoring; 500 lines
617 }
Something went wrong with that request. Please try again.