ObjectExtensions

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
    }
}
原文地址:https://www.cnblogs.com/zcm123/p/2999600.html