Skip to content

Instantly share code, notes, and snippets.

@thecodejunkie
Created February 15, 2011 09:08
Show Gist options
  • Save thecodejunkie/827295 to your computer and use it in GitHub Desktop.
Save thecodejunkie/827295 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Dynamic;
using System.Text.RegularExpressions;
using System.Globalization;
using System.Collections.Specialized;
using System.Data;
using System.Configuration;
using System.Data.Common;
namespace Massive{
public static class ObjectExtensions{
/// <summary>
/// Extension method for adding in a bunch of parameters
/// </summary>
public static void AddParams(this DbCommand cmd, object[] args) {
foreach (var item in args) {
AddParam(cmd,item);
}
}
/// <summary>
/// Extension for adding single parameter
/// </summary>
public static void AddParam(this DbCommand cmd, object item) {
var p = cmd.CreateParameter();
p.ParameterName = string.Format("@{0}", cmd.Parameters.Count);
//fix for NULLs as parameter values
if (item == null) {
p.Value = DBNull.Value;
} else {
//fix for Guids
if (item.GetType() == typeof(Guid)) {
p.Value = item.ToString();
p.DbType = DbType.String;
} else {
p.Value = item;
}
}
cmd.Parameters.Add(p);
}
/// <summary>
/// Turns an IDataReader to a Dynamic list of things
/// </summary>
public static List<dynamic> ToExpandoList(this IDataReader rdr) {
var result = new List<dynamic>();
//work with the Expando as a Dictionary
while (rdr.Read()) {
dynamic e = new ExpandoObject();
var d = e as IDictionary<string, object>;
for (int i = 0; i < rdr.FieldCount; i++) {
d.Add(rdr.GetName(i), rdr[i]);
}
result.Add(e);
}
return result;
}
/// <summary>
/// Turns the object into an ExpandoObject
/// </summary>
/// <param name="o"></param>
/// <returns></returns>
public static dynamic ToExpando(this object o) {
var result = new ExpandoObject();
//work with the Expando as a Dictionary
var d = result as IDictionary<string, object>;
//shouldn't have to... but just in case
if (o.GetType() == typeof(ExpandoObject)) {
return o;
}
//special for form submissions
if (o.GetType() == typeof(NameValueCollection)) {
var nv = (NameValueCollection)o;
//there's a better way to do this... I just don't know what it is...
foreach (var item in nv.Keys) {
var key = item.ToString();
d.Add(key, nv.Get(key));
}
} else {
//assume it's a regular lovely object
var props = o.GetType().GetProperties();
foreach (var item in props) {
var val = item.GetValue(o, null);
d.Add(item.Name, val);
}
}
return result;
}
/// <summary>
/// Turns the object into a Dictionary
/// </summary>
/// <param name="thingy"></param>
/// <returns></returns>
public static IDictionary<string, object> ToDictionary(this object thingy) {
var expando = thingy.ToExpando();
return (IDictionary<string, object>)expando;
}
}
/// <summary>
/// A class that wraps your database table in Dynamic Funtime
/// </summary>
public abstract class DynamicModel:DynamicObject {
DbProviderFactory _factory;
string _connectionStringName;
string _connectionString;
public IList<dynamic> Query(string sql, params object[] args) {
var result = new List<dynamic>();
var cmd = CreateCommand(sql, args);
using (var conn = OpenConnection()) {
cmd.Connection= conn;
var rdr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
result = rdr.ToExpandoList();
//can't help it - like being explicit
rdr.Dispose();
cmd.Dispose();
}
return result;
}
/// <summary>
/// Creates a DBCommand that you can use for loving your database.
/// </summary>
DbCommand CreateCommand(string sql, params object[] args) {
DbCommand result = null;
result = _factory.CreateCommand();
result.CommandText = sql;
if(args.Length > 0)
result.AddParams(args);
return result;
}
DbConnection GetConnection() {
var connection = _factory.CreateConnection();
connection.ConnectionString = _connectionString;
return connection;
}
DbConnection OpenConnection() {
var conn = GetConnection();
conn.Open();
return conn;
}
/// <summary>
/// Creates a slick, groovy little wrapper for your action
/// </summary>
/// <param name="connectionStringName"></param>
public DynamicModel(string connectionStringName) {
//can be overridden by property setting
TableName = this.GetType().Name;
_connectionStringName = connectionStringName;
var providerName = "System.Data.SqlClient";
if (ConfigurationManager.ConnectionStrings[_connectionStringName] != null) {
providerName = ConfigurationManager.ConnectionStrings[_connectionStringName].ProviderName ?? "System.Data.SqlClient";
} else {
throw new InvalidOperationException("Can't find a connection string with the name '" + _connectionStringName + "'");
}
_factory = DbProviderFactories.GetFactory(providerName);
_connectionString = ConfigurationManager.ConnectionStrings[_connectionStringName].ConnectionString;
}
string _primaryKeyField;
/// <summary>
/// Conventionally returns a PK field. The default is "ID" if you don't set one
/// </summary>
public string PrimaryKeyField {
get {
//a bit of convention here
if (!string.IsNullOrEmpty(_primaryKeyField))
return _primaryKeyField;
//oh well - did our best
return "ID";
}
set {
_primaryKeyField = value;
}
}
/// <summary>
/// Conventionally introspects the object passed in for a field that
/// looks like a PK. If you've named your PrimaryKeyField, this becomes easy
/// </summary>
public bool HasPrimaryKey(object o) {
var result = o.ToDictionary().ContainsKey(PrimaryKeyField);
return result;
}
/// <summary>
/// If the object passed in has a property with the same name as your PrimaryKeyField
/// it is returned here.
/// </summary>
public object GetPrimaryKey(object o) {
var d = o.ToDictionary();
object result = null;
d.TryGetValue(PrimaryKeyField, out result);
return result;
}
/// <summary>
/// The name of the Database table we're working with. This defaults to
/// the class name - set this value if it's different
/// </summary>
public string TableName { get; set; }
/// <summary>
/// Adds a record to the database. You can pass in an Anonymous object, an ExpandoObject,
/// A regular old POCO, or a NameValueColletion from a Request.Form or Request.QueryString
/// </summary>
public dynamic Insert(object o) {
dynamic result = 0;
if (BeforeInsert(o)) {
var cmd = CreateInsertCommand(o);
using (var conn = OpenConnection()) {
cmd.Connection = conn;
result = cmd.ExecuteScalar();
cmd.Dispose();
AfterInsert(o);
}
}
return result;
}
/// <summary>
/// Creates a command for use with transactions - internal stuff mostly, but here for you to play with
/// </summary>
public DbCommand CreateInsertCommand(object o) {
DbCommand result = null;
//turn this into an expando - we'll need that for the validators
var expando = o.ToExpando();
var settings = (IDictionary<string, object>)expando;
var sbKeys = new StringBuilder();
var sbVals = new StringBuilder();
var stub = "INSERT INTO {0} ({1}) \r\n VALUES ({2}); \r\nSELECT SCOPE_IDENTITY()";
result = CreateCommand(stub);
int counter = 0;
foreach (var item in settings) {
sbKeys.AppendFormat("{0},", item.Key);
sbVals.AppendFormat("@{0},", counter.ToString());
result.AddParam(item.Value);
counter++;
}
if (counter > 0) {
//strip off trailing commas
var keys = sbKeys.ToString().Substring(0, sbKeys.Length - 1);
var vals = sbVals.ToString().Substring(0, sbVals.Length - 1);
var sql = string.Format(stub, TableName, keys, vals);
result.CommandText = sql;
} else {
throw new InvalidOperationException("Can't parse this object to the database - there are no properties set");
}
return result;
}
/// <summary>
/// Creates a command for use with transactions - internal stuff mostly, but here for you to play with
/// </summary>
public DbCommand CreateUpdateCommand(object o, object key) {
var expando = o.ToExpando();
var settings = (IDictionary<string, object>)expando;
var sbKeys = new StringBuilder();
var stub = "UPDATE {0} SET {1} WHERE {2} = @{3}";
var args = new List<object>();
var result = CreateCommand(stub);
int counter = 0;
foreach (var item in settings) {
var val = item.Value;
if (!item.Key.Equals(PrimaryKeyField, StringComparison.CurrentCultureIgnoreCase) && item.Value != null) {
result.AddParam(val);
sbKeys.AppendFormat("{0} = @{1}, \r\n", item.Key, counter.ToString());
counter++;
}
}
if (counter > 0) {
//add the key
result.AddParam(key);
//strip the last commas
var keys = sbKeys.ToString().Substring(0, sbKeys.Length - 4);
var sql = string.Format(stub, TableName, keys, PrimaryKeyField, counter);
result.CommandText = sql;
} else {
//throw
throw new InvalidOperationException("No parsable object was sent in - could not divine any name/value pairs");
}
return result;
}
/// <summary>
/// Updates a record in the database. You can pass in an Anonymous object, an ExpandoObject,
/// A regular old POCO, or a NameValueColletion from a Request.Form or Request.QueryString
/// </summary>
public int Update(object o, object key) {
//turn this into an expando - we'll need that for the validators
int result = 0;
if (BeforeUpdate(o)) {
using (var conn = OpenConnection()) {
var cmd = CreateUpdateCommand(o, key);
result = cmd.ExecuteNonQuery();
AfterUpdate(o);
}
}
return result;
}
/// <summary>
/// Updates a bunch of records in the database within a transaction. You can pass Anonymous objects, ExpandoObjects,
/// Regular old POCOs - these all have to have a PK set
/// </summary>
public int InsertMany(IEnumerable<object> things) {
int result = 0;
using (var conn = OpenConnection()) {
var tx = conn.BeginTransaction();
foreach (var item in things) {
if (BeforeInsert(item)) {
var cmd = CreateInsertCommand(item);
cmd.Connection = conn;
cmd.Transaction = tx;
cmd.ExecuteNonQuery();
cmd.Dispose();
AfterInsert(item);
}
result++;
}
tx.Commit();
conn.Dispose();
tx.Dispose();
}
return result;
}
/// <summary>
/// Updates a bunch of records in the database within a transaction. You can pass Anonymous objects, ExpandoObjects,
/// Regular old POCOs - these all have to have a PK set
/// </summary>
public int UpdateMany(IEnumerable<object> things) {
//turn this into an expando - we'll need that for the validators
int result = 0;
using (var conn = OpenConnection()) {
var tx = conn.BeginTransaction();
foreach (var item in things) {
var pk = GetPrimaryKey(item);
if (pk == null)
throw new InvalidOperationException("Please be sure to set a value for the primary key");
if (BeforeUpdate(item)) {
var cmd = CreateUpdateCommand(item, pk);
cmd.Connection = conn;
cmd.Transaction = tx;
cmd.ExecuteNonQuery();
cmd.Dispose();
AfterUpdate(item);
result++;
}
}
tx.Commit();
conn.Dispose();
tx.Dispose();
}
return result;
}
/// <summary>
/// If you're feeling lazy, or are just unsure about whether to use Update or Insert you can use
/// this method. It will look for a PrimaryKeyField with a set value to determine if this should
/// be an Insert or Save. You can pass in an Anonymous object, an ExpandoObject,
/// A regular old POCO, or a NameValueColletion from a Request.Form or Request.QueryString
/// </summary>
public dynamic Save(object o) {
dynamic result = 0;
if (BeforeSave(o)) {
var expando = o.ToExpando();
//decide insert or update
if (HasPrimaryKey(expando)) {
result = Update(expando, GetPrimaryKey(o));
} else {
result = Insert(expando);
}
AfterSave(o);
}
return result;
}
/// <summary>
/// Removes a record from the database
/// </summary>
public int Delete(object key) {
//execute
var sql = string.Format("DELETE FROM {0} WHERE {1} = @0", TableName, PrimaryKeyField);
var result = 0;
var cmd = CreateCommand(sql, key);
using (var conn = OpenConnection()) {
cmd.Connection = conn;
result = cmd.ExecuteNonQuery();
cmd.Dispose();
}
return result;
}
/// <summary>
/// Removes one or more records from the DB according to the passed-in WHERE
/// </summary>
public dynamic Delete(string where, params object[] args) {
//execute
var sql = string.Format("DELETE FROM {0} ", TableName);
if (!where.Trim().StartsWith("where", StringComparison.CurrentCultureIgnoreCase)) {
sql += "WHERE ";
}
sql += where;
var result = 0;
var cmd = CreateCommand(sql, args);
using (var conn = OpenConnection()) {
cmd.Connection = conn;
result = cmd.ExecuteNonQuery();
cmd.Dispose();
}
return result;
}
/// <summary>
/// Returns all records complying with the passed-in WHERE clause and arguments,
/// ordered as specified, limited (TOP) by limit.
/// </summary>
public IEnumerable<dynamic> All(string where="", string orderBy="", int limit=0, params object[] args) {
string sql = "SELECT * FROM {0} ";
if (limit > 0) {
sql = "SELECT TOP "+limit+" * FROM {0} ";
}
if (!string.IsNullOrEmpty(where)) {
if (where.Trim().StartsWith("where", StringComparison.CurrentCultureIgnoreCase)) {
sql += where;
} else {
sql += "WHERE " + where;
}
}
if (!String.IsNullOrEmpty(orderBy)) {
if (!orderBy.Trim().StartsWith("order by", StringComparison.CurrentCultureIgnoreCase)) {
sql += " ORDER BY ";
}
sql += orderBy;
}
return Query(string.Format(sql, TableName), args);
}
/// <summary>
/// Returns a single row from the database
/// </summary>
/// <returns>ExpandoObject</returns>
public dynamic Single(object key) {
var sql = string.Format("SELECT * FROM {0} WHERE {1} = @0", TableName, PrimaryKeyField);
var single = Query(sql,key).FirstOrDefault();
return single;
}
#region hooks
//hooks for save routines
public virtual bool BeforeInsert(object o) {
return true;
}
public virtual bool BeforeUpdate(object o) {
return true;
}
public virtual bool BeforeSave(object o) {
return true;
}
public virtual bool BeforeDelete(object key) {
return true;
}
public virtual void AfterInsert(object o) { }
public virtual void AfterUpdate(object o) { }
public virtual void AfterSave(object o) { }
public virtual void AfterDelete(object key) { }
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment