Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Added whitelist functionality and object-based column specification #9

Closed
wants to merge 4 commits into from

1 participant

@Talljoe

Massive is turning out to be quite useful to me! Thank you.

These changes maybe useful to the world, maybe not. I added the ability to specify a whitelist of columns to update/insert by supplying a comma-delimited list, a string collection, a Type, or a dictionary/object whose keys/property names are used. I then, in a separate commit, shared this column specification method with the query operators. Backwards compatibility is maintained. Net difference: -13.

Cheers!

Joe Wasson added some commits
Joe Wasson Minor cleanup. bb3c6df
Joe Wasson Add ability to specify a whitelist on insert and update.
Note: This code also changes functionality such that Update can't update the primary key.  I feel this is a good thing.
7f04b08
Joe Wasson Share object-based column specifications with query operations. e66c968
Joe Wasson Maybe keeping backwards compatibility is better. Adding back ability …
…to set primary key during insert.
f611a68
@Talljoe

My other pull request obviates this one (and I want to merge it into my master) so I'm closing it. If you do want to take this one and not the refactor (FransBouma/Massive#11) you can pull from Talljoe/massive@f611a68

This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Feb 22, 2011
  1. Minor cleanup.

    Joe Wasson authored
  2. Add ability to specify a whitelist on insert and update.

    Joe Wasson authored
    Note: This code also changes functionality such that Update can't update the primary key.  I feel this is a good thing.
  3. Maybe keeping backwards compatibility is better. Adding back ability …

    Joe Wasson authored
    …to set primary key during insert.
This page is out of date. Refresh to see the latest.
Showing with 63 additions and 76 deletions.
  1. +62 −75 Massive.cs
  2. +1 −1  README.markdown
View
137 Massive.cs
@@ -6,16 +6,16 @@
using System.Data.Common;
using System.Dynamic;
using System.Linq;
-using System.Text;
-using System.Collections;
+using System.Reflection;
using System.Text.RegularExpressions;
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) {
+ public static void AddParams(this DbCommand cmd, IEnumerable<object> args) {
foreach (var item in args) {
AddParam(cmd, item);
}
@@ -52,7 +52,7 @@ public static class ObjectExtensions {
var result = new List<dynamic>();
while (rdr.Read()) {
dynamic e = new ExpandoObject();
- var d = e as IDictionary<string, object>;
+ var d = (IDictionary<string, object>)e;
for (int i = 0; i < rdr.FieldCount; i++)
d.Add(rdr.GetName(i), rdr[i]);
result.Add(e);
@@ -114,7 +114,7 @@ public class DynamicModel {
var rdr = CreateCommand(sql, conn, args).ExecuteReader(CommandBehavior.CloseConnection);
while (rdr.Read()) {
var e = new ExpandoObject();
- var d = e as IDictionary<string, object>;
+ var d = (IDictionary<string, object>)e;
for (var i = 0; i < rdr.FieldCount; i++)
d.Add(rdr.GetName(i), rdr[i]);
yield return e;
@@ -131,11 +131,9 @@ public class DynamicModel {
/// Returns a single result
/// </summary>
public object Scalar(string sql, params object[] args) {
- object result = null;
using (var conn = OpenConnection()) {
- result = CreateCommand(sql, conn, args).ExecuteScalar();
+ return CreateCommand(sql, conn, args).ExecuteScalar();
}
- return result;
}
/// <summary>
/// Creates a DBCommand that you can use for loving your database.
@@ -164,16 +162,15 @@ public class DynamicModel {
/// With a PK property (whatever PrimaryKeyField is set to) will be created at UPDATEs
/// </summary>
public 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 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.
@@ -181,11 +178,13 @@ public class DynamicModel {
/// With a PK property (whatever PrimaryKeyField is set to) will be created at UPDATEs
/// </summary>
public int Save(params object[] things) {
- var commands = BuildCommands(things);
- return Execute(commands);
+ return SaveWithWhitelist(null, things);
+ }
+ public int SaveWithWhitelist(object whitelist, params object[] things) {
+ return Execute(BuildCommandsWithWhitelist(whitelist, things));
}
- public int Execute(DbCommand command) {
- return Execute(new DbCommand[] { command });
+ public int Execute(params DbCommand[] command) {
+ return Execute((IEnumerable<DbCommand>)command);
}
/// <summary>
/// Executes a series of DBCommands in a transaction
@@ -217,7 +216,7 @@ public class DynamicModel {
/// it is returned here.
/// </summary>
public object GetPrimaryKey(object o) {
- object result = null;
+ object result;
o.ToDictionary().TryGetValue(PrimaryKeyField, out result);
return result;
}
@@ -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 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 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));
+ 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 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 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}));
+ 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>
@@ -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 object Insert(object o) {
+ public 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";
@@ -308,8 +295,8 @@ 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 int Update(object o, object key) {
- return Execute(CreateUpdateCommand(o, key));
+ public 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
@@ -321,24 +308,24 @@ public class DynamicModel {
/// 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, string columns = "*", params object[] args) {
+ public 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 dynamic Paged(string where = "", string orderBy = "", string columns = "*", int pageSize = 20, int currentPage =1, params object[] args) {
+ public 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;
- var sql = string.Format("SELECT {0} FROM (SELECT ROW_NUMBER() OVER (ORDER BY {2}) AS Row, {0} FROM {3}) AS Paged ",columns,pageSize,orderBy,TableName);
+ 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));
var pagedWhere = "";
@@ -353,14 +340,14 @@ public class DynamicModel {
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 dynamic Single(object key, string columns = "*") {
- var sql = string.Format("SELECT {0} FROM {1} WHERE {2} = @0", columns,TableName, PrimaryKeyField);
+ public 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();
}
}
View
2  README.markdown
@@ -4,7 +4,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.
+and weighs in at a lovely 354 lines of code. Most of which is comments.
How To Install It?
------------------
Something went wrong with that request. Please try again.