Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Whitelist and object-based columns #31

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
136 changes: 58 additions & 78 deletions Massive.cs
Expand Up @@ -6,9 +6,7 @@
using System.Data.Common;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Collections;
using System.Text.RegularExpressions;
using System.Reflection;

namespace Massive {
public static class ObjectExtensions {
Expand Down Expand Up @@ -87,7 +85,7 @@ public static class ObjectExtensions {
/// <summary>
/// A class that wraps your database table in Dynamic Funtime
/// </summary>
public class DynamicModel {
public class DynamicModel {
DbProviderFactory _factory;
string _connectionString;

Expand Down Expand Up @@ -164,28 +162,29 @@ public class DynamicModel {
/// With a PK property (whatever PrimaryKeyField is set to) will be created at UPDATEs
/// </summary>
public virtual List<DbCommand> BuildCommands(params object[] things) {
var commands = new List<DbCommand>();
foreach (var item in things) {
if (HasPrimaryKey(item)) {
commands.Add(CreateUpdateCommand(item,GetPrimaryKey(item)));
}else{
commands.Add(CreateInsertCommand(item));
}
}

return commands;
return BuildCommandsWithWhitelist(null, things);
}
/// <summary>
/// Builds a set of Insert and Update commands based on the passed-on objects.
/// These objects can be POCOs, Anonymous, NameValueCollections, or Expandos. Objects
/// With a PK property (whatever PrimaryKeyField is set to) will be created at UPDATEs
/// </summary>
public virtual List<DbCommand> BuildCommandsWithWhitelist(object whitelist, params object[] things) {
return things.Select(item => HasPrimaryKey(item) ? CreateUpdateCommand(item,GetPrimaryKey(item),whitelist) : CreateInsertCommand(item,whitelist)).ToList();
}
/// <summary>
/// Executes a set of objects as Insert or Update commands based on their property settings, within a transaction.
/// These objects can be POCOs, Anonymous, NameValueCollections, or Expandos. Objects
/// With a PK property (whatever PrimaryKeyField is set to) will be created at UPDATEs
/// </summary>
public virtual int Save(params object[] things) {
var commands = BuildCommands(things);
return Execute(commands);
return SaveWithWhitelist(null, things);
}
public virtual int Execute(DbCommand command) {
return Execute(new DbCommand[] { command });
public virtual int SaveWithWhitelist(object whitelist, params object[] things) {
return Execute(BuildCommandsWithWhitelist(whitelist, things));
}
public virtual int Execute(params DbCommand[] command) {
return Execute((IEnumerable<DbCommand>)command);
}
/// <summary>
/// Executes a series of DBCommands in a transaction
Expand Down Expand Up @@ -225,57 +224,45 @@ public class DynamicModel {
/// <summary>
/// Creates a command for use with transactions - internal stuff mostly, but here for you to play with
/// </summary>
public virtual DbCommand CreateInsertCommand(object o) {
DbCommand result = null;
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})";
result = CreateCommand(stub,null);
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) {
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;
public virtual DbCommand CreateInsertCommand(object o, object whitelist = null) {
const string stub = "INSERT INTO {0} ({1}) \r\n VALUES ({2})";
var result = CreateCommand(stub,null);
var items = FilterItems(o, whitelist).ToList();
if (items.Any()) {
var keys = string.Join(",", items.Select(item => item.Key).ToArray());
var vals = string.Join(",", items.Select((_, i) => "@" + i.ToString()).ToArray());
result.AddParams(items.Select(item => item.Value).ToArray());
result.CommandText = string.Format(stub, TableName, keys, vals);
} 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 virtual 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>();
public virtual DbCommand CreateUpdateCommand(object o, object key, object whitelist = null) {
const string stub = "UPDATE {0} SET {1} WHERE {2} = @{3}";
var result = CreateCommand(stub,null);
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);
result.CommandText = string.Format(stub, TableName, keys, PrimaryKeyField, counter);
var items = FilterItems(o, whitelist).Where(item => !item.Key.Equals(PrimaryKeyField, StringComparison.CurrentCultureIgnoreCase) && item.Value != null).ToList();
if (items.Any()) {
var keys = string.Join(",", items.Select((item, i) => string.Format("{0} = @{1} \r\n", item.Key, i)).ToArray());
result.AddParams(items.Select(item => item.Value).Concat(new[]{key}).ToArray());
result.CommandText = string.Format(stub, TableName, keys, PrimaryKeyField, items.Count);
} else throw new InvalidOperationException("No parsable object was sent in - could not divine any name/value pairs");
return result;
}
private static IEnumerable<KeyValuePair<string,object>> FilterItems(object o, object whitelist) {
IEnumerable<KeyValuePair<string, object>> settings = o.ToDictionary();
var whitelistValues = GetColumns(whitelist).Select(s => s.Trim());
if (!string.Equals("*", whitelistValues.FirstOrDefault(), StringComparison.Ordinal))
settings = settings.Join(whitelistValues, s => s.Key.Trim(), w => w, (s,_) => s, StringComparer.OrdinalIgnoreCase);
return settings;
}
private static IEnumerable<string> GetColumns(object columns) {
return (columns == null) ? new[]{"*"} :
(columns is string) ? ((string)columns).Split(new[]{','}, StringSplitOptions.RemoveEmptyEntries) :
(columns is Type) ? ((Type)columns).GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance).Select(prop => prop.Name)
: (columns as IEnumerable<string>) ?? columns.ToDictionary().Select(kvp => kvp.Key);
}
/// <summary>
/// Removes one or more records from the DB according to the passed-in WHERE
/// </summary>
Expand All @@ -293,10 +280,10 @@ public class DynamicModel {
/// 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 virtual object Insert(object o) {
public virtual object Insert(object o, object whitelist = null) {
dynamic result = 0;
using (var conn = OpenConnection()) {
var cmd = CreateInsertCommand(o);
var cmd = CreateInsertCommand(o, whitelist);
cmd.Connection = conn;
cmd.ExecuteNonQuery();
cmd.CommandText = "SELECT @@IDENTITY as newID";
Expand All @@ -308,58 +295,51 @@ public class DynamicModel {
/// Updates a record in the database. You can pass in an Anonymous object, an ExpandoObject,
/// A regular old POCO, or a NameValueCollection from a Request.Form or Request.QueryString
/// </summary>
public virtual int Update(object o, object key) {
return Execute(CreateUpdateCommand(o, key));
public virtual int Update(object o, object key, object whitelist = null) {
return Execute(CreateUpdateCommand(o, key, whitelist));
}
/// <summary>
/// Removes one or more records from the DB according to the passed-in WHERE
/// </summary>
public int Delete(object key = null, string where = "", params object[] args) {
public virtual int Delete(object key = null, string where = "", params object[] args) {
return Execute(CreateDeleteCommand(where: where, key:key, args: args));
}
/// <summary>
/// Returns all records complying with the passed-in WHERE clause and arguments,
/// ordered as specified, limited (TOP) by limit.
/// </summary>
public virtual IEnumerable<dynamic> All(string where = "", string orderBy = "", int limit = 0, string columns = "*", params object[] args) {
public virtual IEnumerable<dynamic> All(string where = "", string orderBy = "", int limit = 0, object columns = null, params object[] args) {
string sql = limit > 0 ? "SELECT TOP " + limit + " {0} FROM {1} " : "SELECT {0} FROM {1} ";
if (!string.IsNullOrEmpty(where))
sql += where.Trim().StartsWith("where", StringComparison.CurrentCultureIgnoreCase) ? where : "WHERE " + where;
if (!String.IsNullOrEmpty(orderBy))
sql += orderBy.Trim().StartsWith("order by", StringComparison.CurrentCultureIgnoreCase) ? orderBy : " ORDER BY " + orderBy;
return Query(string.Format(sql, columns,TableName), args);
return Query(string.Format(sql, string.Join(",", GetColumns(columns)), TableName), args);
}

/// <summary>
/// Returns a dynamic PagedResult. Result properties are Items, TotalPages, and TotalRecords.
/// </summary>
public virtual dynamic Paged(string where = "", string orderBy = "", string columns = "*", int pageSize = 20, int currentPage =1, params object[] args) {
public virtual dynamic Paged(string where = "", string orderBy = "", object columns = null, int pageSize = 20, int currentPage =1, params object[] args) {
dynamic result = new ExpandoObject();
var countSQL = string.Format("SELECT COUNT({0}) FROM {1}", PrimaryKeyField, TableName);
if (String.IsNullOrEmpty(orderBy))
orderBy = PrimaryKeyField;

if (!string.IsNullOrEmpty(where)) {
if (!where.Trim().StartsWith("where", StringComparison.CurrentCultureIgnoreCase)) {
where = "WHERE " + where;
}
}
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);
var sql = string.Format("SELECT {0} FROM (SELECT ROW_NUMBER() OVER (ORDER BY {1}) AS Row, {0} FROM {2}) AS Paged ", string.Join(",", GetColumns(columns)), orderBy, TableName);
var pageStart = (currentPage -1) * pageSize;
sql+= string.Format(" WHERE Row >={0} AND Row <={1}",pageStart, (pageStart + pageSize));
countSQL += where;
result.TotalRecords = Scalar(countSQL,args);
result.TotalPages = result.TotalRecords / pageSize;
if (result.TotalRecords % pageSize > 0)
result.TotalPages += 1;
result.Items = Query(string.Format(sql, columns, TableName), args);
result.Items = Query(sql, args);
return result;
}
/// <summary>
/// Returns a single row from the database
/// </summary>
public virtual dynamic Single(object key, string columns = "*") {
var sql = string.Format("SELECT {0} FROM {1} WHERE {2} = @0", columns,TableName, PrimaryKeyField);
public virtual dynamic Single(object key, object columns = null) {
var sql = string.Format("SELECT {0} FROM {1} WHERE {2} = @0", string.Join(",", GetColumns(columns)), TableName, PrimaryKeyField);
return Fetch(sql, key).FirstOrDefault();
}
}
Expand Down
3 changes: 1 addition & 2 deletions README.markdown
Expand Up @@ -3,8 +3,7 @@ Massive is a Single File Database Lover. Move over Bacon - Taste is got a new fr


I'm sharing this with the world because we need another way to access data - don't you think? Truthfully - I wanted to see if I could flex the C# 4 stuff and
run up data access with a single file. I used to have this down to 350 lines, but you also needed to reference WebMatrix.Data. Now you don't - this is ready to roll
and weighs in at a lovely 524 lines of code. Most of which is comments.
run up data access with a single file. This is ready to roll and weighs in at a lovely 346 lines of code. Most of which is comments.

How To Install It?
------------------
Expand Down