Initial commit of first working version that is used in production environment.

This commit is contained in:
grzegorz.russek
2012-08-10 10:14:49 +00:00
commit 6996111bec
46 changed files with 8489 additions and 0 deletions

View File

@@ -0,0 +1,65 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System.Data;
using System.Text;
namespace DynamORM.Builders
{
/// <summary>Delete query builder.</summary>
public class DynamicDeleteQueryBuilder : DynamicQueryBuilder<DynamicDeleteQueryBuilder>
{
/// <summary>Initializes a new instance of the <see cref="DynamicDeleteQueryBuilder"/> class.</summary>
/// <param name="table">Parent dynamic table.</param>
public DynamicDeleteQueryBuilder(DynamicTable table)
: base(table)
{
}
/// <summary>Fill command with query.</summary>
/// <param name="command">Command to fill.</param>
/// <returns>Filled instance of <see cref="IDbCommand"/>.</returns>
public override IDbCommand FillCommand(IDbCommand command)
{
StringBuilder sb = new StringBuilder();
sb.AppendFormat("DELETE FROM {0}", TableName);
FillWhere(command, sb);
return command.SetCommand(sb.ToString());
}
/// <summary>Execute this builder.</summary>
/// <returns>Number of affected rows.</returns>
public override dynamic Execute()
{
return DynamicTable.Execute(this);
}
}
}

View File

@@ -0,0 +1,192 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using DynamORM.Mapper;
namespace DynamORM.Builders
{
/// <summary>Insert query builder.</summary>
public class DynamicInsertQueryBuilder : DynamicQueryBuilder<DynamicInsertQueryBuilder>
{
/// <summary>Gets list of columns that will be seected.</summary>
public IDictionary<string, DynamicColumn> ValueColumns { get; private set; }
/// <summary>Initializes a new instance of the <see cref="DynamicInsertQueryBuilder"/> class.</summary>
/// <param name="table">Parent dynamic table.</param>
public DynamicInsertQueryBuilder(DynamicTable table)
: base(table)
{
ValueColumns = new Dictionary<string, DynamicColumn>();
}
/// <summary>Add insert fields.</summary>
/// <param name="column">Insert column and value.</param>
/// <returns>Builder instance.</returns>
public virtual DynamicInsertQueryBuilder Insert(DynamicColumn column)
{
if (ValueColumns.ContainsKey(column.ColumnName.ToLower()))
ValueColumns[column.ColumnName.ToLower()] = column;
else
ValueColumns.Add(column.ColumnName.ToLower(), column);
return this;
}
/// <summary>Add insert fields.</summary>
/// <param name="column">Insert column.</param>
/// <param name="value">Insert value.</param>
/// <returns>Builder instance.</returns>
public virtual DynamicInsertQueryBuilder Insert(string column, object value)
{
if (value is DynamicColumn)
{
var v = (DynamicColumn)value;
if (string.IsNullOrEmpty(v.ColumnName))
v.ColumnName = column;
return Insert(v);
}
if (ValueColumns.ContainsKey(column.ToLower()))
ValueColumns[column.ToLower()].Value = value;
else
ValueColumns.Add(column.ToLower(), new DynamicColumn
{
ColumnName = column,
Value = value
});
return this;
}
/// <summary>Add insert fields.</summary>
/// <param name="o">Set insert value as properties and values of an object.</param>
/// <returns>Builder instance.</returns>
public virtual DynamicInsertQueryBuilder Insert(object o)
{
var dict = o.ToDictionary();
var mapper = DynamicMapperCache.GetMapper(o.GetType());
if (mapper != null)
{
foreach (var con in dict)
if (!mapper.Ignored.Contains(con.Key))
Insert(mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key, con.Value);
}
else
foreach (var con in dict)
Insert(con.Key, con.Value);
return this;
}
/// <summary>Add where condition.</summary>
/// <param name="column">Condition column with operator and value.</param>
/// <returns>Builder instance.</returns>
public override DynamicInsertQueryBuilder Where(DynamicColumn column)
{
throw new NotSupportedException();
}
/// <summary>Add where condition.</summary>
/// <param name="column">Condition column.</param>
/// <param name="op">Condition operator.</param>
/// <param name="value">Condition value.</param>
/// <returns>Builder instance.</returns>
public override DynamicInsertQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value)
{
throw new NotSupportedException();
}
/// <summary>Add where condition.</summary>
/// <param name="column">Condition column.</param>
/// <param name="value">Condition value.</param>
/// <returns>Builder instance.</returns>
public override DynamicInsertQueryBuilder Where(string column, object value)
{
throw new NotSupportedException();
}
/// <summary>Add where condition.</summary>
/// <param name="conditions">Set conditions as properties and values of an object.</param>
/// <param name="schema">If <c>true</c> use schema to determine key columns and ignore those which
/// aren't keys.</param>
/// <returns>Builder instance.</returns>
public override DynamicInsertQueryBuilder Where(object conditions, bool schema = false)
{
throw new NotSupportedException();
}
/// <summary>Fill command with query.</summary>
/// <param name="command">Command to fill.</param>
/// <returns>Filled instance of <see cref="IDbCommand"/>.</returns>
public override IDbCommand FillCommand(IDbCommand command)
{
if (ValueColumns.Count == 0)
throw new InvalidOperationException("Insert query should contain columns to change.");
StringBuilder builderColumns = new StringBuilder();
StringBuilder builderValues = new StringBuilder();
bool first = true;
var db = DynamicTable.Database;
foreach (var v in ValueColumns)
{
int pos = command.Parameters.Count;
if (!first)
{
builderColumns.Append(", ");
builderValues.Append(", ");
}
db.DecorateName(builderColumns, v.Value.ColumnName);
db.GetParameterName(builderValues, pos);
command.AddParameter(this, v.Value);
first = false;
}
return command.SetCommand("INSERT INTO {0} ({1}) VALUES ({2})", TableName, builderColumns, builderValues);
}
/// <summary>Execute this builder.</summary>
/// <returns>Number of affected rows.</returns>
public override dynamic Execute()
{
return DynamicTable.Execute(this);
}
}
}

View File

@@ -0,0 +1,381 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using DynamORM.Mapper;
namespace DynamORM.Builders
{
/// <summary>Base query builder.</summary>
/// <typeparam name="T">Return type of methods that should return self.</typeparam>
public abstract class DynamicQueryBuilder<T> : IDynamicQueryBuilder where T : class
{
/// <summary>Gets <see cref="DynamicTable"/> instance.</summary>
public DynamicTable DynamicTable { get; private set; }
/// <summary>Gets where conditions.</summary>
public List<DynamicColumn> WhereConditions { get; private set; }
/// <summary>Gets table schema.</summary>
public Dictionary<string, DynamicSchemaColumn> Schema { get; private set; }
/// <summary>Gets a value indicating whether database supports standard schema.</summary>
public bool SupportSchema { get; private set; }
/// <summary>Gets table name.</summary>
public string TableName { get; private set; }
/// <summary>Initializes a new instance of the DynamicQueryBuilder class.</summary>
/// <param name="table">Parent dynamic table.</param>
public DynamicQueryBuilder(DynamicTable table)
{
DynamicTable = table;
TableName = table.TableName;
WhereConditions = new List<DynamicColumn>();
SupportSchema = (DynamicTable.Database.Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema;
Schema = DynamicTable.Schema;
}
/// <summary>Set table name.</summary>
/// <param name="name">Name of table.</param>
/// <returns>Builder instance.</returns>
public virtual T Table(string name)
{
if (TableName.ToLower() != name.ToLower())
{
TableName = name;
Schema = DynamicTable.Database.GetSchema(TableName);
if (Schema == null)
throw new InvalidOperationException("Can't assign type as a table for which schema can't be build.");
}
return this as T;
}
/// <summary>Set table name.</summary>
/// <typeparam name="Y">Type representing table.</typeparam>
/// <returns>Builder instance.</returns>
public virtual T Table<Y>()
{
return Table(typeof(T));
}
/// <summary>Set table name.</summary>
/// <param name="type">Type representing table.</param>
/// <returns>Builder instance.</returns>
public virtual T Table(Type type)
{
var mapper = DynamicMapperCache.GetMapper(type);
string name = string.Empty;
if (mapper == null)
throw new InvalidOperationException("Cant assign unmapable type as a table.");
else
name = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ?
mapper.Type.Name : mapper.Table.Name;
if (TableName.ToLower() != name.ToLower())
{
TableName = name;
Schema = DynamicTable.Database.GetSchema(type);
}
return this as T;
}
/// <summary>Add where condition.</summary>
/// <param name="column">Condition column with operator and value.</param>
/// <returns>Builder instance.</returns>
public virtual T Where(DynamicColumn column)
{
WhereConditions.Add(column);
return this as T;
}
/// <summary>Add where condition.</summary>
/// <param name="column">Condition column.</param>
/// <param name="op">Condition operator.</param>
/// <param name="value">Condition value.</param>
/// <returns>Builder instance.</returns>
public virtual T Where(string column, DynamicColumn.CompareOperator op, object value)
{
if (value is DynamicColumn)
{
var v = (DynamicColumn)value;
if (string.IsNullOrEmpty(v.ColumnName))
v.ColumnName = column;
return Where(v);
}
else if (value is IEnumerable<DynamicColumn>)
{
foreach (var v in (IEnumerable<DynamicColumn>)value)
Where(v);
return this as T;
}
WhereConditions.Add(new DynamicColumn
{
ColumnName = column,
Operator = op,
Value = value
});
return this as T;
}
/// <summary>Add where condition.</summary>
/// <param name="column">Condition column.</param>
/// <param name="value">Condition value.</param>
/// <returns>Builder instance.</returns>
public virtual T Where(string column, object value)
{
return Where(column, DynamicColumn.CompareOperator.Eq, value);
}
/// <summary>Add where condition.</summary>
/// <param name="conditions">Set conditions as properties and values of an object.</param>
/// <param name="schema">If <c>true</c> use schema to determine key columns and ignore those which
/// aren't keys.</param>
/// <returns>Builder instance.</returns>
public virtual T Where(object conditions, bool schema = false)
{
if (conditions is DynamicColumn)
return Where((DynamicColumn)conditions);
var dict = conditions.ToDictionary();
var mapper = DynamicMapperCache.GetMapper(conditions.GetType());
foreach (var con in dict)
{
if (mapper.Ignored.Contains(con.Key))
continue;
string colName = mapper != null ? mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key : con.Key;
DynamicSchemaColumn? col = null;
if (schema)
{
col = Schema.TryGetNullable(colName.ToLower());
if (!col.HasValue)
throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", con.Key));
if (!col.Value.IsKey)
continue;
colName = col.Value.Name;
}
Where(colName, con.Value);
}
return this as T;
}
/// <summary>Get string representation of operator.</summary>
/// <param name="op">Operator object.</param>
/// <returns>String representation of operator.</returns>
public string ToOperator(DynamicColumn.CompareOperator op)
{
switch (op)
{
case DynamicColumn.CompareOperator.Eq: return "=";
case DynamicColumn.CompareOperator.Not: return "<>";
case DynamicColumn.CompareOperator.Like: return "LIKE";
case DynamicColumn.CompareOperator.NotLike: return "NOT LIKE";
case DynamicColumn.CompareOperator.Lt: return "<";
case DynamicColumn.CompareOperator.Lte: return "<=";
case DynamicColumn.CompareOperator.Gt: return ">";
case DynamicColumn.CompareOperator.Gte: return ">=";
case DynamicColumn.CompareOperator.Between:
case DynamicColumn.CompareOperator.In:
default:
throw new ArgumentException(string.Format("This operator ('{0}') requires more than conversion to string.", op));
}
}
/// <summary>Fill where part of a query.</summary>
/// <param name="command">Command to fill.</param>
/// <param name="sb">String builder.</param>
public virtual void FillWhere(IDbCommand command, StringBuilder sb)
{
// Yes, this method qualifies fo refactoring... but it's fast
bool first = true;
var db = DynamicTable.Database;
foreach (var v in WhereConditions)
{
var col = Schema.TryGetNullable(v.ColumnName);
string column = col.HasValue ? col.Value.Name : v.ColumnName;
if ((column.IndexOf(db.LeftDecorator) == -1 || column.IndexOf(db.LeftDecorator) == -1) &&
(column.IndexOf('(') == -1 || column.IndexOf(')') == -1))
column = db.DecorateName(column);
if (v.Value == null)
{
#region Null operators
if (v.Operator == DynamicColumn.CompareOperator.Not || v.Operator == DynamicColumn.CompareOperator.Eq)
sb.AppendFormat(" {0} {1} IS{2} NULL",
first ? "WHERE" : "AND", column,
v.Operator == DynamicColumn.CompareOperator.Not ? " NOT" : string.Empty);
else
throw new InvalidOperationException("NULL can only be compared by IS or IS NOT operator.");
#endregion
}
else if (v.Operator != DynamicColumn.CompareOperator.In &&
v.Operator != DynamicColumn.CompareOperator.Between)
{
#region Standard operators
int pos = command.Parameters.Count;
sb.AppendFormat(" {0} {1} {2} ",
first ? "WHERE" : "AND", column,
ToOperator(v.Operator));
db.GetParameterName(sb, pos);
command.AddParameter(this, v);
#endregion
}
else if (((object)v.Value).GetType().IsCollection() || v.Value is IEnumerable<object>)
{
#region In or Between operator
if (v.Operator == DynamicColumn.CompareOperator.Between)
{
#region Between operator
var vals = (v.Value as IEnumerable<object>).Take(2).ToList();
if (vals == null && v.Value is Array)
vals = ((Array)v.Value).Cast<object>().ToList();
if (vals.Count == 2)
{
sb.AppendFormat(" {0} {1} BETWEEN ",
first ? "WHERE" : "AND", column);
// From parameter
db.GetParameterName(sb, command.Parameters.Count);
v.Value = vals[0];
command.AddParameter(this, v);
sb.Append(" AND ");
// To parameter
db.GetParameterName(sb, command.Parameters.Count);
v.Value = vals[1];
command.AddParameter(this, v);
// Reset value
v.Value = vals;
}
else
throw new InvalidOperationException("BETWEEN must have two values.");
#endregion
}
else if (v.Operator == DynamicColumn.CompareOperator.In)
{
#region In operator
sb.AppendFormat(" {0} {1} IN(",
first ? "WHERE" : "AND", column);
bool firstParam = true;
var vals = v.Value as IEnumerable<object>;
if (vals == null && v.Value is Array)
vals = ((Array)v.Value).Cast<object>() as IEnumerable<object>;
foreach (var val in vals)
{
int pos = command.Parameters.Count;
if (!firstParam)
sb.Append(", ");
db.GetParameterName(sb, pos);
v.Value = val;
command.AddParameter(this, v);
firstParam = false;
}
v.Value = vals;
sb.Append(")");
#endregion
}
else
throw new Exception("BAZINGA. You have reached unreachable code.");
#endregion
}
else
throw new InvalidOperationException(
string.Format("Operator was {0}, but value wasn't enumerable. Column: '{1}'", v.Operator.ToString().ToUpper(), col));
first = false;
}
}
/// <summary>Fill command with query.</summary>
/// <param name="command">Command to fill.</param>
/// <returns>Filled instance of <see cref="IDbCommand"/>.</returns>
public abstract IDbCommand FillCommand(IDbCommand command);
/// <summary>Execute this builder.</summary>
/// <returns>Result of an execution..</returns>
public abstract dynamic Execute();
}
}

View File

@@ -0,0 +1,272 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
namespace DynamORM.Builders
{
/// <summary>Select query builder.</summary>
public class DynamicSelectQueryBuilder : DynamicQueryBuilder<DynamicSelectQueryBuilder>
{
/// <summary>Gets dictionary of columns that will be seected.</summary>
public List<DynamicColumn> Columns { get; private set; }
/// <summary>Gets dictionary of columns that will be used to group query.</summary>
public List<DynamicColumn> Group { get; private set; }
/// <summary>Gets dictionary of columns that will be used to order query.</summary>
public List<DynamicColumn> Order { get; private set; }
private int? _top = null;
private int? _limit = null;
private int? _offset = null;
private bool _distinct = false;
/// <summary>Initializes a new instance of the <see cref="DynamicSelectQueryBuilder" /> class.</summary>
/// <param name="table">Parent dynamic table.</param>
public DynamicSelectQueryBuilder(DynamicTable table)
: base(table)
{
Columns = new List<DynamicColumn>();
Group = new List<DynamicColumn>();
Order = new List<DynamicColumn>();
}
/// <summary>Add select columns.</summary>
/// <param name="columns">Columns to add to object.</param>
/// <returns>Builder instance.</returns>
public DynamicSelectQueryBuilder Select(params DynamicColumn[] columns)
{
foreach (var col in columns)
Columns.Add(col);
return this;
}
/// <summary>Add select columns.</summary>
/// <param name="columns">Columns to add to object.</param>
/// <remarks>Column format consist of <c>Column Name</c>, <c>Alias</c> and
/// <c>Aggregate function</c> in this order separated by '<c>:</c>'.</remarks>
/// <returns>Builder instance.</returns>
public DynamicSelectQueryBuilder Select(params string[] columns)
{
return Select(columns.Select(c => DynamicColumn.ParseSelectColumn(c)).ToArray());
}
/// <summary>Add select columns.</summary>
/// <param name="columns">Columns to group by.</param>
/// <returns>Builder instance.</returns>
public DynamicSelectQueryBuilder GroupBy(params DynamicColumn[] columns)
{
foreach (var col in columns)
Group.Add(col);
return this;
}
/// <summary>Add select columns.</summary>
/// <param name="columns">Columns to group by.</param>
/// <remarks>Column format consist of <c>Column Name</c> and
/// <c>Alias</c> in this order separated by '<c>:</c>'.</remarks>
/// <returns>Builder instance.</returns>
public DynamicSelectQueryBuilder GroupBy(params string[] columns)
{
return GroupBy(columns.Select(c => DynamicColumn.ParseSelectColumn(c)).ToArray());
}
/// <summary>Add select columns.</summary>
/// <param name="columns">Columns to order by.</param>
/// <returns>Builder instance.</returns>
public DynamicSelectQueryBuilder OrderBy(params DynamicColumn[] columns)
{
foreach (var col in columns)
Order.Add(col);
return this;
}
/// <summary>Add select columns.</summary>
/// <param name="columns">Columns to order by.</param>
/// <remarks>Column format consist of <c>Column Name</c> and
/// <c>Alias</c> in this order separated by '<c>:</c>'.</remarks>
/// <returns>Builder instance.</returns>
public DynamicSelectQueryBuilder OrderBy(params string[] columns)
{
return OrderBy(columns.Select(c => DynamicColumn.ParseOrderByColumn(c)).ToArray());
}
/// <summary>Set top if database support it.</summary>
/// <param name="top">How many objects select.</param>
/// <returns>Builder instance.</returns>
public DynamicSelectQueryBuilder Top(int? top)
{
if ((DynamicTable.Database.Options & DynamicDatabaseOptions.SupportTop) != DynamicDatabaseOptions.SupportTop)
throw new NotSupportedException("Database doesn't support TOP clause.");
_top = top;
return this;
}
/// <summary>Set top if database support it.</summary>
/// <param name="limit">How many objects select.</param>
/// <returns>Builder instance.</returns>
public DynamicSelectQueryBuilder Limit(int? limit)
{
if ((DynamicTable.Database.Options & DynamicDatabaseOptions.SupportLimitOffset) != DynamicDatabaseOptions.SupportLimitOffset)
throw new NotSupportedException("Database doesn't support LIMIT clause.");
_limit = limit;
return this;
}
/// <summary>Set top if database support it.</summary>
/// <param name="offset">How many objects skip selecting.</param>
/// <returns>Builder instance.</returns>
public DynamicSelectQueryBuilder Offset(int? offset)
{
if ((DynamicTable.Database.Options & DynamicDatabaseOptions.SupportLimitOffset) != DynamicDatabaseOptions.SupportLimitOffset)
throw new NotSupportedException("Database doesn't support OFFSET clause.");
_offset = offset;
return this;
}
/// <summary>Set distinct mode.</summary>
/// <param name="distinct">Distinct mode.</param>
/// <returns>Builder instance.</returns>
public DynamicSelectQueryBuilder Distinct(bool distinct = true)
{
_distinct = distinct;
return this;
}
/// <summary>Fill command with query.</summary>
/// <param name="command">Command to fill.</param>
/// <returns>Filled instance of <see cref="IDbCommand"/>.</returns>
public override IDbCommand FillCommand(IDbCommand command)
{
StringBuilder sb = new StringBuilder();
var db = DynamicTable.Database;
sb.AppendFormat("SELECT{0}{1} ",
_distinct ? " DISTINCT" : string.Empty,
_top.HasValue ? string.Format(" TOP {0}", _top.Value) : string.Empty);
BuildColumns(sb, db);
sb.Append(" FROM ");
db.DecorateName(sb, TableName);
FillWhere(command, sb);
BuildGroup(sb, db);
BuildOrder(sb, db);
if (_limit.HasValue)
sb.AppendFormat(" LIMIT {0}", _limit.Value);
if (_offset.HasValue)
sb.AppendFormat(" OFFSET {0}", _offset.Value);
return command.SetCommand(sb.ToString());
}
private void BuildColumns(StringBuilder sb, DynamicDatabase db)
{
if (Columns.Count > 0)
{
bool first = true;
// Not pretty but blazing fast
Columns.ForEach(c =>
{
if (!first)
sb.Append(", ");
c.ToSQLSelectColumn(db, sb);
first = false;
});
}
else
sb.Append("*");
}
private void BuildGroup(StringBuilder sb, DynamicDatabase db)
{
if (Group.Count > 0)
{
sb.Append(" GROUP BY ");
bool first = true;
// Not pretty but blazing fast
Group.ForEach(c =>
{
if (!first)
sb.Append(", ");
c.ToSQLGroupByColumn(db, sb);
first = false;
});
}
}
private void BuildOrder(StringBuilder sb, DynamicDatabase db)
{
if (Order.Count > 0)
{
sb.Append(" ORDER BY ");
bool first = true;
// Not pretty but blazing fast
Order.ForEach(c =>
{
if (!first)
sb.Append(", ");
c.ToSQLOrderByColumn(db, sb);
first = false;
});
}
}
/// <summary>Execute this builder.</summary>
/// <returns>Enumerator of objects expanded from query.</returns>
public override dynamic Execute()
{
if (Columns.Count <= 1 && ((_top.HasValue && _top.Value == 1) || (_limit.HasValue && _limit.Value == 1)))
return DynamicTable.Scalar(this);
else
return DynamicTable.Query(this);
}
}
}

View File

@@ -0,0 +1,238 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using DynamORM.Mapper;
namespace DynamORM.Builders
{
/// <summary>Update query builder.</summary>
public class DynamicUpdateQueryBuilder : DynamicQueryBuilder<DynamicUpdateQueryBuilder>
{
/// <summary>Gets list of columns that will be seected.</summary>
public IDictionary<string, DynamicColumn> ValueColumns { get; private set; }
/// <summary>Initializes a new instance of the <see cref="DynamicUpdateQueryBuilder" /> class.</summary>
/// <param name="table">Parent dynamic table.</param>
public DynamicUpdateQueryBuilder(DynamicTable table)
: base(table)
{
ValueColumns = new Dictionary<string, DynamicColumn>();
}
/// <summary>Add update value or where condition using schema.</summary>
/// <param name="column">Update or where column name and value.</param>
/// <returns>Builder instance.</returns>
public virtual DynamicUpdateQueryBuilder Update(DynamicColumn column)
{
var col = Schema.TryGetNullable(column.ColumnName.ToLower());
if (!col.HasValue && SupportSchema)
throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", column));
if (col.HasValue && col.Value.IsKey)
Where(column);
else
Values(column.ColumnName, column.Value);
return this;
}
/// <summary>Add update value or where condition using schema.</summary>
/// <param name="column">Update or where column name.</param>
/// <param name="value">Column value.</param>
/// <returns>Builder instance.</returns>
public virtual DynamicUpdateQueryBuilder Update(string column, object value)
{
var col = Schema.TryGetNullable(column.ToLower());
if (!col.HasValue && SupportSchema)
throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", column));
if (col.HasValue && col.Value.IsKey)
Where(column, value);
else
Values(column, value);
return this;
}
/// <summary>Add update values and where condition columns using schema.</summary>
/// <param name="conditions">Set values or conditions as properties and values of an object.</param>
/// <returns>Builder instance.</returns>
public virtual DynamicUpdateQueryBuilder Update(object conditions)
{
if (conditions is DynamicColumn)
return Update((DynamicColumn)conditions);
var dict = conditions.ToDictionary();
var mapper = DynamicMapperCache.GetMapper(conditions.GetType());
foreach (var con in dict)
{
if (mapper.Ignored.Contains(con.Key))
continue;
string colName = mapper != null ? mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key : con.Key;
var col = Schema.TryGetNullable(colName.ToLower());
if (!col.HasValue && SupportSchema)
throw new InvalidOperationException(string.Format("Column '{0}' not found in schema, can't use universal approach.", colName));
if (col.HasValue)
{
colName = col.Value.Name;
if (col.Value.IsKey)
{
Where(colName, con.Value);
continue;
}
}
Values(colName, con.Value);
}
return this;
}
/// <summary>Add update fields.</summary>
/// <param name="column">Update column and value.</param>
/// <returns>Builder instance.</returns>
public virtual DynamicUpdateQueryBuilder Values(DynamicColumn column)
{
if (ValueColumns.ContainsKey(column.ColumnName.ToLower()))
ValueColumns[column.ColumnName.ToLower()] = column;
else
ValueColumns.Add(column.ColumnName.ToLower(), column);
return this;
}
/// <summary>Add update fields.</summary>
/// <param name="column">Update column.</param>
/// <param name="value">Update value.</param>
/// <returns>Builder instance.</returns>
public virtual DynamicUpdateQueryBuilder Values(string column, object value)
{
if (value is DynamicColumn)
{
var v = (DynamicColumn)value;
if (string.IsNullOrEmpty(v.ColumnName))
v.ColumnName = column;
return Values(v);
}
if (ValueColumns.ContainsKey(column.ToLower()))
ValueColumns[column.ToLower()].Value = value;
else
ValueColumns.Add(column.ToLower(), new DynamicColumn
{
ColumnName = column,
Value = value
});
return this;
}
/// <summary>Add update fields.</summary>
/// <param name="values">Set update value as properties and values of an object.</param>
/// <returns>Builder instance.</returns>
public virtual DynamicUpdateQueryBuilder Values(object values)
{
if (values is DynamicColumn)
return Values((DynamicColumn)values);
var dict = values.ToDictionary();
var mapper = DynamicMapperCache.GetMapper(values.GetType());
if (mapper != null)
{
foreach (var con in dict)
if (!mapper.Ignored.Contains(con.Key))
Values(mapper.PropertyMap.TryGetValue(con.Key) ?? con.Key, con.Value);
}
else
foreach (var con in dict)
Values(con.Key, con.Value);
return this;
}
/// <summary>Fill command with query.</summary>
/// <param name="command">Command to fill.</param>
/// <returns>Filled instance of <see cref="IDbCommand"/>.</returns>
public override IDbCommand FillCommand(IDbCommand command)
{
if (ValueColumns.Count == 0)
throw new InvalidOperationException("Update query should contain columns to change.");
StringBuilder sb = new StringBuilder();
var db = DynamicTable.Database;
sb.Append("UPDATE ");
db.DecorateName(sb, TableName);
sb.Append(" SET ");
bool first = true;
foreach (var v in ValueColumns)
{
int pos = command.Parameters.Count;
if (!first)
sb.Append(", ");
db.DecorateName(sb, v.Value.ColumnName);
sb.Append(" = ");
db.GetParameterName(sb, pos);
command.AddParameter(this, v.Value);
first = false;
}
FillWhere(command, sb);
return command.SetCommand(sb.ToString());
}
/// <summary>Execute this builder.</summary>
/// <returns>Number of affected rows.</returns>
public override dynamic Execute()
{
return DynamicTable.Execute(this);
}
}
}

View File

@@ -0,0 +1,55 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System.Collections.Generic;
using System.Data;
namespace DynamORM.Builders
{
/// <summary>Base query builder interface.</summary>
public interface IDynamicQueryBuilder
{
/// <summary>Gets <see cref="DynamicTable"/> instance.</summary>
DynamicTable DynamicTable { get; }
/// <summary>Gets table schema.</summary>
Dictionary<string, DynamicSchemaColumn> Schema { get; }
/// <summary>Gets a value indicating whether database supports standard schema.</summary>
bool SupportSchema { get; }
/// <summary>Fill command with query.</summary>
/// <param name="command">Command to fill.</param>
/// <returns>Filled instance of <see cref="IDbCommand"/>.</returns>
IDbCommand FillCommand(IDbCommand command);
/// <summary>Execute this builder.</summary>
/// <returns>Result of an execution..</returns>
dynamic Execute();
}
}

79
DynamORM/DynamORM.csproj Normal file
View File

@@ -0,0 +1,79 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{63963ED7-9C78-4672-A4D4-339B6E825503}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>DynamORM</RootNamespace>
<AssemblyName>DynamORM</AssemblyName>
<FileAlignment>512</FileAlignment>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<DebugSymbols>true</DebugSymbols>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="System.Data" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Builders\DynamicDeleteQueryBuilder.cs" />
<Compile Include="Builders\DynamicInsertQueryBuilder.cs" />
<Compile Include="Builders\DynamicQueryBuilder.cs" />
<Compile Include="Builders\DynamicSelectQueryBuilder.cs" />
<Compile Include="Builders\DynamicUpdateQueryBuilder.cs" />
<Compile Include="Builders\IDynamicQueryBuilder.cs" />
<Compile Include="DynamicColumn.cs" />
<Compile Include="DynamicCommand.cs" />
<Compile Include="DynamicConnection.cs" />
<Compile Include="DynamicDatabase.cs" />
<Compile Include="DynamicDatabaseOptions.cs" />
<Compile Include="DynamicExtensions.cs" />
<Compile Include="DynamicSchemaColumn.cs" />
<Compile Include="DynamicTable.cs" />
<Compile Include="DynamicTransaction.cs" />
<Compile Include="Helpers\CollectionComparer.cs" />
<Compile Include="Helpers\FrameworkTools.cs" />
<Compile Include="Mapper\ColumnAttribute.cs" />
<Compile Include="Mapper\DynamicMapperCache.cs" />
<Compile Include="Mapper\DynamicPropertyInvoker.cs" />
<Compile Include="Mapper\DynamicTypeMap.cs" />
<Compile Include="Mapper\IgnoreAttribute.cs" />
<Compile Include="Mapper\TableAttribute.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

423
DynamORM/DynamicColumn.cs Normal file
View File

@@ -0,0 +1,423 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Text;
namespace DynamORM
{
/// <summary>Small utility class to manage single columns.</summary>
public class DynamicColumn
{
#region Enums
/// <summary>Order By Order.</summary>
public enum SortOrder
{
/// <summary>Ascending order.</summary>
Asc,
/// <summary>Descending order.</summary>
Desc
}
/// <summary>Dynamic query operators.</summary>
public enum CompareOperator
{
/// <summary>Equals operator (default).</summary>
Eq,
/// <summary>Not equal operator.</summary>
Not,
/// <summary>Like operator.</summary>
Like,
/// <summary>Not like operator.</summary>
NotLike,
/// <summary>In operator.</summary>
In,
/// <summary>Less than operator.</summary>
Lt,
/// <summary>Less or equal operator.</summary>
Lte,
/// <summary>Greather than operator.</summary>
Gt,
/// <summary>Greather or equal operator.</summary>
Gte,
/// <summary>Between two values.</summary>
Between,
}
#endregion Enums
#region Constructors
/// <summary>Initializes a new instance of the <see cref="DynamicColumn" /> class.</summary>
public DynamicColumn() { }
/// <summary>Initializes a new instance of the <see cref="DynamicColumn" /> class.</summary>
/// <remarks>Constructor provided for easier object creation in qeries.</remarks>
/// <param name="columnName">Name of column to set.</param>
public DynamicColumn(string columnName)
{
ColumnName = columnName;
}
/// <summary>Initializes a new instance of the <see cref="DynamicColumn" /> class.</summary>
/// <remarks>Constructor provided for easier object creation in qeries.</remarks>
/// <param name="columnName">Name of column to set.</param>
/// <param name="oper">Compare column to value(s) operator.</param>
/// <param name="value">Parameter value(s).</param>
public DynamicColumn(string columnName, CompareOperator oper, object value)
: this(columnName)
{
Operator = oper;
Value = value;
}
#endregion Constructors
#region Properties
/// <summary>Gets or sets column name.</summary>
public string ColumnName { get; set; }
/// <summary>Gets or sets column alias.</summary>
/// <remarks>Select specific.</remarks>
public string Alias { get; set; }
/// <summary>Gets or sets aggregate function used on column.</summary>
/// <remarks>Select specific.</remarks>
public string Aggregate { get; set; }
/// <summary>Gets or sets order direction.</summary>
public SortOrder Order { get; set; }
/// <summary>Gets or sets value for parameters.</summary>
public object Value { get; set; }
/// <summary>Gets or sets condition operator.</summary>
public CompareOperator Operator { get; set; }
#endregion Properties
#region Query creation helpers
#region Operators
private DynamicColumn SetOperatorAndValue(CompareOperator c, object v)
{
Operator = c;
Value = v;
return this;
}
/// <summary>Helper method setting <see cref="DynamicColumn.Operator"/>
/// to <see cref="CompareOperator.Eq"/> and
/// <see cref="DynamicColumn.Value"/> to provided <c>value</c>.</summary>
/// <param name="value">Value of parameter to set.</param>
/// <returns>Returns self.</returns>
public DynamicColumn Eq(object value)
{
return SetOperatorAndValue(CompareOperator.Eq, value);
}
/// <summary>Helper method setting <see cref="DynamicColumn.Operator"/>
/// to <see cref="CompareOperator.Not"/> and
/// <see cref="DynamicColumn.Value"/> to provided <c>value</c>.</summary>
/// <param name="value">Value of parameter to set.</param>
/// <returns>Returns self.</returns>
public DynamicColumn Not(object value)
{
return SetOperatorAndValue(CompareOperator.Not, value);
}
/// <summary>Helper method setting <see cref="DynamicColumn.Operator"/>
/// to <see cref="CompareOperator.Like"/> and
/// <see cref="DynamicColumn.Value"/> to provided <c>value</c>.</summary>
/// <param name="value">Value of parameter to set.</param>
/// <returns>Returns self.</returns>
public DynamicColumn Like(object value)
{
return SetOperatorAndValue(CompareOperator.Like, value);
}
/// <summary>Helper method setting <see cref="DynamicColumn.Operator"/>
/// to <see cref="CompareOperator.NotLike"/> and
/// <see cref="DynamicColumn.Value"/> to provided <c>value</c>.</summary>
/// <param name="value">Value of parameter to set.</param>
/// <returns>Returns self.</returns>
public DynamicColumn NotLike(object value)
{
return SetOperatorAndValue(CompareOperator.NotLike, value);
}
/// <summary>Helper method setting <see cref="DynamicColumn.Operator"/>
/// to <see cref="CompareOperator.Gt"/> and
/// <see cref="DynamicColumn.Value"/> to provided <c>value</c>.</summary>
/// <param name="value">Value of parameter to set.</param>
/// <returns>Returns self.</returns>
public DynamicColumn Greater(object value)
{
return SetOperatorAndValue(CompareOperator.Gt, value);
}
/// <summary>Helper method setting <see cref="DynamicColumn.Operator"/>
/// to <see cref="CompareOperator.Lt"/> and
/// <see cref="DynamicColumn.Value"/> to provided <c>value</c>.</summary>
/// <param name="value">Value of parameter to set.</param>
/// <returns>Returns self.</returns>
public DynamicColumn Less(object value)
{
return SetOperatorAndValue(CompareOperator.Lt, value);
}
/// <summary>Helper method setting <see cref="DynamicColumn.Operator"/>
/// to <see cref="CompareOperator.Gte"/> and
/// <see cref="DynamicColumn.Value"/> to provided <c>value</c>.</summary>
/// <param name="value">Value of parameter to set.</param>
/// <returns>Returns self.</returns>
public DynamicColumn GreaterOrEqual(object value)
{
return SetOperatorAndValue(CompareOperator.Gte, value);
}
/// <summary>Helper method setting <see cref="DynamicColumn.Operator"/>
/// to <see cref="CompareOperator.Lte"/> and
/// <see cref="DynamicColumn.Value"/> to provided <c>value</c>.</summary>
/// <param name="value">Value of parameter to set.</param>
/// <returns>Returns self.</returns>
public DynamicColumn LessOrEqual(object value)
{
return SetOperatorAndValue(CompareOperator.Lte, value);
}
/// <summary>Helper method setting <see cref="DynamicColumn.Operator"/>
/// to <see cref="CompareOperator.Between"/> and
/// <see cref="DynamicColumn.Value"/> to provided values.</summary>
/// <param name="from">Value of from parameter to set.</param>
/// <param name="to">Value of to parameter to set.</param>
/// <returns>Returns self.</returns>
public DynamicColumn Between(object from, object to)
{
return SetOperatorAndValue(CompareOperator.Between, new[] { from, to });
}
/// <summary>Helper method setting <see cref="DynamicColumn.Operator"/>
/// to <see cref="CompareOperator.In"/> and
/// <see cref="DynamicColumn.Value"/> to provided values.</summary>
/// <param name="values">Values of parameters to set.</param>
/// <returns>Returns self.</returns>
public DynamicColumn In(IEnumerable<object> values)
{
return SetOperatorAndValue(CompareOperator.In, values);
}
/// <summary>Helper method setting <see cref="DynamicColumn.Operator"/>
/// to <see cref="CompareOperator.In"/> and
/// <see cref="DynamicColumn.Value"/> to provided values.</summary>
/// <param name="values">Values of parameters to set.</param>
/// <returns>Returns self.</returns>
public DynamicColumn In(params object[] values)
{
if (values.Length == 1 && (values[0].GetType().IsCollection() || values[0] is IEnumerable<object>))
return SetOperatorAndValue(CompareOperator.In, values[0]);
return SetOperatorAndValue(CompareOperator.In, values);
}
#endregion Operators
#region Order
/// <summary>Helper method setting <see cref="DynamicColumn.Order"/>
/// to <see cref="SortOrder.Asc"/>..</summary>
/// <returns>Returns self.</returns>
public DynamicColumn Asc()
{
Order = SortOrder.Asc;
return this;
}
/// <summary>Helper method setting <see cref="DynamicColumn.Order"/>
/// to <see cref="SortOrder.Desc"/>..</summary>
/// <returns>Returns self.</returns>
public DynamicColumn Desc()
{
Order = SortOrder.Desc;
return this;
}
#endregion Order
/// <summary>Helper method setting
/// <see cref="DynamicColumn.ColumnName"/>
/// to provided <c>name</c>.</summary>
/// <param name="name">Name to set.</param>
/// <returns>Returns self.</returns>
public DynamicColumn SetName(string name)
{
ColumnName = name;
return this;
}
/// <summary>Helper method setting
/// <see cref="DynamicColumn.Alias"/>
/// to provided <c>alias</c>.</summary>
/// <param name="alias">Alias to set.</param>
/// <returns>Returns self.</returns>
public DynamicColumn SetAlias(string alias)
{
Alias = alias;
return this;
}
/// <summary>Helper method setting
/// <see cref="DynamicColumn.Aggregate"/>
/// to provided <c>aggregate</c>.</summary>
/// <param name="aggregate">Aggregate to set.</param>
/// <returns>Returns self.</returns>
public DynamicColumn SetAggregate(string aggregate)
{
Aggregate = aggregate;
return this;
}
#endregion Query creation helpers
#region Parsing
/// <summary>Parse column for select query.</summary>
/// <remarks>Column format consist of <c>Column Name</c>, <c>Alias</c> and
/// <c>Aggregate function</c> in this order separated by '<c>:</c>'.</remarks>
/// <param name="column">Column string.</param>
/// <returns>Instance of <see cref="DynamicColumn"/>.</returns>
public static DynamicColumn ParseSelectColumn(string column)
{
// Split column description
var parts = column.Split(':');
if (parts.Length > 0)
{
DynamicColumn ret = new DynamicColumn() { ColumnName = parts[0] };
if (parts.Length > 1)
ret.Alias = parts[1];
if (parts.Length > 2)
ret.Aggregate = parts[2];
return ret;
}
return null;
}
/// <summary>Parse column for order by in query.</summary>
/// <remarks>Column format consist of <c>Column Name</c> and
/// <c>Direction</c> in this order separated by '<c>:</c>'.</remarks>
/// <param name="column">Column string.</param>
/// <returns>Instance of <see cref="DynamicColumn"/>.</returns>
public static DynamicColumn ParseOrderByColumn(string column)
{
// Split column description
var parts = column.Split(':');
if (parts.Length > 0)
{
DynamicColumn ret = new DynamicColumn() { ColumnName = parts[0] };
if (parts.Length > 1)
ret.Order = parts[1].ToLower() == "d" || parts[1].ToLower() == "desc" ? SortOrder.Desc : SortOrder.Asc;
if (parts.Length > 2)
ret.Alias = parts[2];
return ret;
}
return null;
}
#endregion Parsing
#region ToSQL
internal void ToSQLSelectColumn(DynamicDatabase db, StringBuilder sb)
{
string column = ColumnName == "*" ? "*" : ColumnName;
if (column != "*" &&
(column.IndexOf(db.LeftDecorator) == -1 || column.IndexOf(db.LeftDecorator) == -1) &&
(column.IndexOf('(') == -1 || column.IndexOf(')') == -1))
column = db.DecorateName(column);
string alias = Alias;
if (!string.IsNullOrEmpty(Aggregate))
{
sb.AppendFormat("{0}({1})", Aggregate, column);
alias = string.IsNullOrEmpty(alias) ?
ColumnName == "*" ? Guid.NewGuid().ToString() : ColumnName :
alias;
}
else
sb.Append(column);
if (!string.IsNullOrEmpty(alias))
sb.AppendFormat(" AS {0}", alias);
}
internal void ToSQLGroupByColumn(DynamicDatabase db, StringBuilder sb)
{
sb.Append(db.DecorateName(ColumnName));
}
internal void ToSQLOrderByColumn(DynamicDatabase db, StringBuilder sb)
{
if (!string.IsNullOrEmpty(Alias))
sb.Append(Alias);
else
sb.Append(db.DecorateName(ColumnName));
sb.AppendFormat(" {0}", Order.ToString().ToUpper());
}
#endregion ToSQL
}
}

205
DynamORM/DynamicCommand.cs Normal file
View File

@@ -0,0 +1,205 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Data;
namespace DynamORM
{
/// <summary>Helper class to easy manage command.</summary>
public class DynamicCommand : IDbCommand
{
private IDbCommand _command;
private int? _commandTimeout = null;
private DynamicConnection _con;
private DynamicDatabase _db;
private long _poolStamp = 0;
/// <summary>Initializes a new instance of the <see cref="DynamicCommand"/> class.</summary>
/// <param name="con">The connection.</param>
/// <param name="db">The databas manager.</param>
internal DynamicCommand(DynamicConnection con, DynamicDatabase db)
{
_con = con;
_db = db;
lock (_db.SyncLock)
{
if (!_db.CommandsPool.ContainsKey(_con.Connection))
throw new InvalidOperationException("Can't create transaction using disposed connection.");
else
{
_command = _con.Connection.CreateCommand();
_db.CommandsPool[_con.Connection].Add(_command);
}
}
}
/// <summary>Prepare command for execution.</summary>
/// <returns>Returns edited <see cref="System.Data.IDbCommand"/> instance.</returns>
private IDbCommand PrepareForExecution()
{
if (_poolStamp < _db.PoolStamp)
{
_command.CommandTimeout = _commandTimeout ?? _db.CommandTimeout ?? _command.CommandTimeout;
if (_db.TransactionPool[_command.Connection].Count > 0)
_command.Transaction = _db.TransactionPool[_command.Connection].Peek();
_poolStamp = _db.PoolStamp;
}
return _db.DumpCommands ? _command.Dump(Console.Out) : _command;
}
#region IDbCommand Members
/// <summary>
/// Attempts to cancels the execution of an <see cref="T:System.Data.IDbCommand"/>.
/// </summary>
public void Cancel()
{
_command.Cancel();
}
/// <summary>
/// Gets or sets the text command to run against the data source.
/// </summary>
/// <returns>The text command to execute. The default value is an empty string ("").</returns>
public string CommandText { get { return _command.CommandText; } set { _command.CommandText = value; } }
/// <summary>
/// Gets or sets the wait time before terminating the attempt to execute a command and generating an error.
/// </summary>
/// <returns>The time (in seconds) to wait for the command to execute. The default value is 30 seconds.</returns>
/// <exception cref="T:System.ArgumentException">The property value assigned is less than 0. </exception>
public int CommandTimeout { get { return _commandTimeout ?? _command.CommandTimeout; } set { _commandTimeout = value; } }
/// <summary>Gets or sets how the <see cref="P:System.Data.IDbCommand.CommandText"/> property is interpreted.</summary>
public CommandType CommandType { get { return _command.CommandType; } set { _command.CommandType = value; } }
/// <summary>Gets or sets the <see cref="T:System.Data.IDbConnection"/>
/// used by this instance of the <see cref="T:System.Data.IDbCommand"/>.</summary>
/// <returns>The connection to the data source.</returns>
public IDbConnection Connection { get { return _con; } set { _con = (DynamicConnection)value; } }
/// <summary>Creates a new instance of an
/// <see cref="T:System.Data.IDbDataParameter"/> object.</summary>
/// <returns>An <see cref="IDbDataParameter"/> object.</returns>
public IDbDataParameter CreateParameter()
{
return _command.CreateParameter();
}
/// <summary>Executes an SQL statement against the Connection object of a
/// data provider, and returns the number of rows affected.</summary>
/// <returns>The number of rows affected.</returns>
public int ExecuteNonQuery()
{
return PrepareForExecution().ExecuteNonQuery();
}
/// <summary>Executes the <see cref="P:System.Data.IDbCommand.CommandText"/>
/// against the <see cref="P:System.Data.IDbCommand.Connection"/>,
/// and builds an <see cref="T:System.Data.IDataReader"/> using one
/// of the <see cref="T:System.Data.CommandBehavior"/> values.
/// </summary><param name="behavior">One of the
/// <see cref="T:System.Data.CommandBehavior"/> values.</param>
/// <returns>An <see cref="T:System.Data.IDataReader"/> object.</returns>
public IDataReader ExecuteReader(CommandBehavior behavior)
{
return PrepareForExecution().ExecuteReader(behavior);
}
/// <summary>Executes the <see cref="P:System.Data.IDbCommand.CommandText"/>
/// against the <see cref="P:System.Data.IDbCommand.Connection"/> and
/// builds an <see cref="T:System.Data.IDataReader"/>.</summary>
/// <returns>An <see cref="T:System.Data.IDataReader"/> object.</returns>
public IDataReader ExecuteReader()
{
return PrepareForExecution().ExecuteReader();
}
/// <summary>Executes the query, and returns the first column of the
/// first row in the resultset returned by the query. Extra columns or
/// rows are ignored.</summary>
/// <returns>The first column of the first row in the resultset.</returns>
public object ExecuteScalar()
{
return PrepareForExecution().ExecuteScalar();
}
/// <summary>Gets the <see cref="T:System.Data.IDataParameterCollection"/>.</summary>
public IDataParameterCollection Parameters
{
get { return _command.Parameters; }
}
/// <summary>Creates a prepared (or compiled) version of the command on the data source.</summary>
public void Prepare()
{
_command.Prepare();
}
/// <summary>Gets or sets the transaction within which the Command
/// object of a data provider executes.</summary>
/// <remarks>It's does nothing, transaction is peeked from transaction
/// pool of a connection.</remarks>
public IDbTransaction Transaction { get { return null; } set { } }
/// <summary>Gets or sets how command results are applied to the <see cref="T:System.Data.DataRow"/>
/// when used by the <see cref="M:System.Data.IDataAdapter.Update(System.Data.DataSet)"/>
/// method of a <see cref="T:System.Data.Common.DbDataAdapter"/>.</summary>
/// <returns>One of the <see cref="T:System.Data.UpdateRowSource"/> values. The default is
/// Both unless the command is automatically generated. Then the default is None.</returns>
/// <exception cref="T:System.ArgumentException">The value entered was not one of the
/// <see cref="T:System.Data.UpdateRowSource"/> values. </exception>
public UpdateRowSource UpdatedRowSource { get { return _command.UpdatedRowSource; } set { _command.UpdatedRowSource = value; } }
#endregion IDbCommand Members
#region IDisposable Members
/// <summary>Performs application-defined tasks associated with
/// freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
lock (_db.SyncLock)
{
var pool = _db.CommandsPool.TryGetValue(_con.Connection);
if (pool != null)
pool.Remove(_command);
_command.Dispose();
}
}
#endregion IDisposable Members
}
}

View File

@@ -0,0 +1,153 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Data;
namespace DynamORM
{
/// <summary>Connection wrapper.</summary>
/// <remarks>This class is only connection holder conection is managed by
/// <see cref="DynamicDatabase"/> instance.</remarks>
public class DynamicConnection : IDbConnection, IDisposable
{
private DynamicDatabase _db;
private bool _singleTransaction;
/// <summary>Gets underlaying connection.</summary>
internal IDbConnection Connection { get; private set; }
/// <summary>Initializes a new instance of the <see cref="DynamicConnection" /> class.</summary>
/// <param name="db">Database connection manager.</param>
/// <param name="con">Active connection.</param>
/// <param name="singleTransaction">Are we using single transaction mode? I so... act correctly.</param>
internal DynamicConnection(DynamicDatabase db, IDbConnection con, bool singleTransaction)
{
_db = db;
Connection = con;
_singleTransaction = singleTransaction;
}
/// <summary>Begins a database transaction.</summary>
/// <param name="il">One of the <see cref="System.Data.IsolationLevel"/> values.</param>
/// <param name="disposed">This action is invoked when transaction is disposed.</param>
/// <returns>Returns <see cref="DynamicTransaction"/> representation.</returns>
internal DynamicTransaction BeginTransaction(IsolationLevel? il, Action disposed)
{
return new DynamicTransaction(_db, this, _singleTransaction, il, disposed);
}
#region IDbConnection Members
/// <summary>Creates and returns a Command object associated with the connection.</summary>
/// <returns>A Command object associated with the connection.</returns>
public IDbCommand CreateCommand()
{
return new DynamicCommand(this, _db);
}
/// <summary>Begins a database transaction.</summary>
/// <returns>Returns <see cref="DynamicTransaction"/> representation.</returns>
public IDbTransaction BeginTransaction()
{
return BeginTransaction(null, null);
}
/// <summary>Begins a database transaction with the specified
/// <see cref="System.Data.IsolationLevel"/> value.</summary>
/// <param name="il">One of the <see cref="System.Data.IsolationLevel"/> values.</param>
/// <returns>Returns <see cref="DynamicTransaction"/> representation.</returns>
public IDbTransaction BeginTransaction(IsolationLevel il)
{
return BeginTransaction(il, null);
}
/// <summary>Changes the current database for an open Connection object.</summary>
/// <param name="databaseName">The name of the database to use in place of the current database.</param>
/// <remarks>This operation is not supported in DynamORM. and will throw <see cref="NotSupportedException"/>.</remarks>
/// <exception cref="NotSupportedException">Thrown always.</exception>
public void ChangeDatabase(string databaseName)
{
throw new NotSupportedException("This operation is not supported in DynamORM.");
}
/// <summary>Opens a database connection with the settings specified by
/// the ConnectionString property of the provider-specific
/// Connection object.</summary>
/// <remarks>Does nothing. <see cref="DynamicDatabase"/> handles
/// opening connections.</remarks>
public void Open() { }
/// <summary>Closes the connection to the database.</summary>
/// <remarks>Does nothing. <see cref="DynamicDatabase"/> handles
/// closing connections. Only way to close it is to dispose connection.
/// It will close if this is multi connection configuration, otherwise
/// it will stay open untill <see cref="DynamicDatabase"/> is not
/// disposed.</remarks>
public void Close() { }
/// <summary>Gets or sets the string used to open a database.</summary>
/// <remarks>Changing connection string operation is not supported in DynamORM.
/// and will throw <see cref="NotSupportedException"/>.</remarks>
/// <exception cref="NotSupportedException">Thrown always when set is attempted.</exception>
public string ConnectionString
{
get { return Connection.ConnectionString; }
set { throw new NotSupportedException("This operation is not supported in DynamORM."); }
}
/// <summary>Gets the time to wait while trying to establish a connection
/// before terminating the attempt and generating an error.</summary>
public int ConnectionTimeout
{
get { return Connection.ConnectionTimeout; }
}
/// <summary>Gets the name of the current database or the database
/// to be used after a connection is opened.</summary>
public string Database
{
get { throw new NotImplementedException(); }
}
/// <summary>Gets the current state of the connection.</summary>
public ConnectionState State
{
get { return Connection.State; }
}
#endregion IDbConnection Members
/// <summary>Performs application-defined tasks associated with freeing,
/// releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
_db.Close(Connection);
}
}
}

566
DynamORM/DynamicDatabase.cs Normal file
View File

@@ -0,0 +1,566 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Text;
using DynamORM.Mapper;
namespace DynamORM
{
/// <summary>Dynamic database is a class responsible for managing database.</summary>
public class DynamicDatabase : IDisposable
{
#region Internal fields and properties
private DbProviderFactory _provider;
private string _connectionString;
private bool _singleConnection;
private bool _singleTransaction;
private string _leftDecorator = "\"";
private string _rightDecorator = "\"";
private string _parameterFormat = "@{0}";
private int? _commandTimeout = null;
private long _poolStamp = 0;
private DynamicConnection _tempConn = null;
/// <summary>Provides lock object for this database instance.</summary>
internal readonly object SyncLock = new object();
/// <summary>Gets or sets timestamp of last transaction pool or configuration change.</summary>
/// <remarks>This property is used to allow commands to determine if
/// they need to update transaction object or not.</remarks>
internal long PoolStamp { get { return _poolStamp; } set { lock (SyncLock) _poolStamp = value; } }
/// <summary>Gets pool of connections and transactions.</summary>
internal Dictionary<IDbConnection, Stack<IDbTransaction>> TransactionPool { get; private set; }
/// <summary>Gets pool of connections and commands.</summary>
internal Dictionary<IDbConnection, List<IDbCommand>> CommandsPool { get; private set; }
/// <summary>Gets schema columns cache.</summary>
internal Dictionary<string, Dictionary<string, DynamicSchemaColumn>> Schema { get; private set; }
/// <summary>Gets tables cache for this database instance.</summary>
internal Dictionary<string, DynamicTable> TablesCache { get; private set; }
#endregion Internal fields and properties
#region Properties and Constructors
/// <summary>Gets database options.</summary>
public DynamicDatabaseOptions Options { get; private set; }
/// <summary>Gets or sets command timeout.</summary>
public int? CommandTimeout { get { return _commandTimeout; } set { _commandTimeout = value; _poolStamp = DateTime.Now.Ticks; } }
/// <summary>Gets or sets a value indicating whether
/// dump commands to console or not.</summary>
public bool DumpCommands { get; set; }
/// <summary>Initializes a new instance of the <see cref="DynamicDatabase" /> class.</summary>
/// <param name="provider">Database proider by name.</param>
/// <param name="connectionString">Connection string to provided database.</param>
/// <param name="options">Connection options.</param>
public DynamicDatabase(string provider, string connectionString, DynamicDatabaseOptions options)
: this(DbProviderFactories.GetFactory(provider), connectionString, options)
{
}
/// <summary>Initializes a new instance of the <see cref="DynamicDatabase" /> class.</summary>
/// <param name="provider">Database proider.</param>
/// <param name="connectionString">Connection string to provided database.</param>
/// <param name="options">Connection options.</param>
public DynamicDatabase(DbProviderFactory provider, string connectionString, DynamicDatabaseOptions options)
{
_provider = provider;
InitCommon(connectionString, options);
}
/// <summary>Initializes a new instance of the <see cref="DynamicDatabase" /> class.</summary>
/// <param name="connection">Active database connection.</param>
/// <param name="options">Connection options. <see cref="DynamicDatabaseOptions.SingleConnection"/> required.</param>
public DynamicDatabase(IDbConnection connection, DynamicDatabaseOptions options)
{
InitCommon(connection.ConnectionString, options);
TransactionPool.Add(connection, new Stack<IDbTransaction>());
if (!_singleConnection)
throw new InvalidOperationException("This constructor accepts only connections with DynamicDatabaseOptions.SingleConnection option.");
}
private void InitCommon(string connectionString, DynamicDatabaseOptions options)
{
_connectionString = connectionString;
Options = options;
_singleConnection = (options & DynamicDatabaseOptions.SingleConnection) == DynamicDatabaseOptions.SingleConnection;
_singleTransaction = (options & DynamicDatabaseOptions.SingleTransaction) == DynamicDatabaseOptions.SingleTransaction;
TransactionPool = new Dictionary<IDbConnection, Stack<IDbTransaction>>();
CommandsPool = new Dictionary<IDbConnection, List<IDbCommand>>();
Schema = new Dictionary<string, Dictionary<string, DynamicSchemaColumn>>();
TablesCache = new Dictionary<string, DynamicTable>();
}
#endregion Properties and Constructors
#region Table
/// <summary>Gets dynamic table which is a simple ORM using dynamic objects.</summary>
/// <param name="table">Table name.</param>
/// <param name="keys">Override keys in schema.</param>
/// <returns>Instance of <see cref="DynamicTable"/>.</returns>
public dynamic Table(string table = "", string[] keys = null)
{
string key = string.Concat(
table == null ? string.Empty : table,
keys == null ? string.Empty : string.Join("_|_", keys));
DynamicTable dt = null;
lock (SyncLock)
dt = TablesCache.TryGetValue(key) ??
TablesCache.AddAndPassValue(key,
new DynamicTable(this, table, keys));
return dt;
}
/// <summary>Gets dynamic table which is a simple ORM using dynamic objects.</summary>
/// <typeparam name="T">Type used to determine table name.</typeparam>
/// <param name="keys">Override keys in schema.</param>
/// <returns>Instance of <see cref="DynamicTable"/>.</returns>
public dynamic Table<T>(string[] keys = null)
{
Type table = typeof(T);
string key = string.Concat(
table.FullName,
keys == null ? string.Empty : string.Join("_|_", keys));
DynamicTable dt = null;
lock (SyncLock)
dt = TablesCache.TryGetValue(key) ??
TablesCache.AddAndPassValue(key,
new DynamicTable(this, table, keys));
return dt;
}
/// <summary>Removes cached table.</summary>
/// <param name="dynamicTable">Disposed dynamic table.</param>
internal void RemoveFromCache(DynamicTable dynamicTable)
{
foreach (var item in TablesCache.Where(kvp => kvp.Value == dynamicTable).ToList())
TablesCache.Remove(item.Key);
}
#endregion Table
#region Schema
/// <summary>Builds table cache if nessesary and returns it.</summary>
/// <param name="table">Name of table for which build schema.</param>
/// <returns>Table chema.</returns>
public Dictionary<string, DynamicSchemaColumn> GetSchema(string table)
{
Dictionary<string, DynamicSchemaColumn> schema = null;
lock (SyncLock)
schema = Schema.TryGetValue(table.GetType().FullName) ??
BuildAndCacheSchema(table, null);
return schema;
}
/// <summary>Builds table cache if nessesary and returns it.</summary>
/// <typeparam name="T">Type of table for which build schema.</typeparam>
/// <returns>Table schema or null if type was anonymous.</returns>
public Dictionary<string, DynamicSchemaColumn> GetSchema<T>()
{
if (typeof(T).IsAnonymous())
return null;
Dictionary<string, DynamicSchemaColumn> schema = null;
lock (SyncLock)
schema = Schema.TryGetValue(typeof(T).GetType().FullName) ??
BuildAndCacheSchema(null, DynamicMapperCache.GetMapper<T>());
return schema;
}
/// <summary>Builds table cache if nessesary and returns it.</summary>
/// <param name="table">Type of table for which build schema.</param>
/// <returns>Table schema or null if type was anonymous.</returns>
public Dictionary<string, DynamicSchemaColumn> GetSchema(Type table)
{
if (table == null || table.IsAnonymous() || table.IsValueType)
return null;
Dictionary<string, DynamicSchemaColumn> schema = null;
lock (SyncLock)
schema = Schema.TryGetValue(table.FullName) ??
BuildAndCacheSchema(null, DynamicMapperCache.GetMapper(table));
return schema;
}
/// <summary>Get schema describing objects from reader.</summary>
/// <param name="table">Table from which extract column info.</param>
/// <returns>List of <see cref="DynamicSchemaColumn"/> objects .
/// If your database doesn't get those values in upper case (like most of the databases) you should override this method.</returns>
protected virtual IEnumerable<DynamicSchemaColumn> ReadSchema(string table)
{
using (var con = Open())
using (var cmd = con.CreateCommand())
{
using (var rdr = cmd
.SetCommand(string.Format("SELECT * FROM {0} WHERE 1 = 0", DecorateName(table)))
.ExecuteReader(CommandBehavior.SchemaOnly))
foreach (DataRow col in rdr.GetSchemaTable().Rows)
{
var c = col.RowToDynamicUpper();
yield return new DynamicSchemaColumn
{
Name = c.COLUMNNAME,
Type = DynamicExtensions.TypeMap.TryGetNullable((Type)c.DATATYPE) ?? DbType.String,
IsKey = c.ISKEY ?? false,
IsUnique = c.ISUNIQUE ?? false,
Size = (int)(c.COLUMNSIZE ?? 0),
Precision = (byte)(c.NUMERICPRECISION ?? 0),
Scale = (byte)(c.NUMERICSCALE ?? 0)
};
}
}
}
private Dictionary<string, DynamicSchemaColumn> BuildAndCacheSchema(string tableName, DynamicTypeMap mapper)
{
Dictionary<string, DynamicSchemaColumn> schema = null;
if (mapper != null)
tableName = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ?
mapper.Type.Name : mapper.Table.Name;
bool databaseSchemaSupport = !string.IsNullOrEmpty(tableName) &&
(Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema;
bool mapperSchema = mapper != null && mapper.Table != null && (mapper.Table.Override || !databaseSchemaSupport);
#region Database schema
if (databaseSchemaSupport && !Schema.ContainsKey(tableName.ToLower()))
{
schema = ReadSchema(tableName)
.ToDictionary(k => k.Name.ToLower(), k => k);
Schema[tableName.ToLower()] = schema;
}
#endregion Database schema
#region Type schema
if (mapperSchema && !Schema.ContainsKey(mapper.Type.FullName))
{
// TODO: Ged rid of this monster below...
if (databaseSchemaSupport)
{
#region Merge with db schema
schema = mapper.ColumnsMap.ToDictionary(k => k.Key, (v) =>
{
DynamicSchemaColumn? col = Schema[tableName.ToLower()].TryGetNullable(v.Key);
return new DynamicSchemaColumn
{
Name = DynamicExtensions.Coalesce<string>(
v.Value.Column == null || string.IsNullOrEmpty(v.Value.Column.Name) ? null : v.Value.Column.Name,
col.HasValue && !string.IsNullOrEmpty(col.Value.Name) ? col.Value.Name : null,
v.Value.Name),
IsKey = DynamicExtensions.CoalesceNullable<bool>(
v.Value.Column != null ? v.Value.Column.IsKey : false,
col.HasValue ? col.Value.IsKey : false).Value,
Type = DynamicExtensions.CoalesceNullable<DbType>(
v.Value.Column != null ? v.Value.Column.Type : null,
col.HasValue ? col.Value.Type : DbType.String).Value,
IsUnique = DynamicExtensions.CoalesceNullable<bool>(
v.Value.Column != null ? v.Value.Column.IsUnique : null,
col.HasValue ? col.Value.IsUnique : false).Value,
Size = DynamicExtensions.CoalesceNullable<int>(
v.Value.Column != null ? v.Value.Column.Size : null,
col.HasValue ? col.Value.Size : 0).Value,
Precision = DynamicExtensions.CoalesceNullable<byte>(
v.Value.Column != null ? v.Value.Column.Precision : null,
col.HasValue ? col.Value.Precision : (byte)0).Value,
Scale = DynamicExtensions.CoalesceNullable<byte>(
v.Value.Column != null ? v.Value.Column.Scale : null,
col.HasValue ? col.Value.Scale : (byte)0).Value,
};
});
#endregion Merge with db schema
}
else
{
#region MapEnumerable based only on type
schema = mapper.ColumnsMap.ToDictionary(k => k.Key,
v => new DynamicSchemaColumn
{
Name = DynamicExtensions.Coalesce<string>(v.Value.Column == null || string.IsNullOrEmpty(v.Value.Column.Name) ? null : v.Value.Column.Name, v.Value.Name),
IsKey = DynamicExtensions.CoalesceNullable<bool>(v.Value.Column != null ? v.Value.Column.IsKey : false, false).Value,
Type = DynamicExtensions.CoalesceNullable<DbType>(v.Value.Column != null ? v.Value.Column.Type : null, DbType.String).Value,
IsUnique = DynamicExtensions.CoalesceNullable<bool>(v.Value.Column != null ? v.Value.Column.IsUnique : null, false).Value,
Size = DynamicExtensions.CoalesceNullable<int>(v.Value.Column != null ? v.Value.Column.Size : null, 0).Value,
Precision = DynamicExtensions.CoalesceNullable<byte>(v.Value.Column != null ? v.Value.Column.Precision : null, 0).Value,
Scale = DynamicExtensions.CoalesceNullable<byte>(v.Value.Column != null ? v.Value.Column.Scale : null, 0).Value,
});
#endregion MapEnumerable based only on type
}
}
if (mapper != null && schema != null)
Schema[mapper.Type.FullName] = schema;
#endregion Type schema
return schema;
}
#endregion Schema
#region Decorators
/// <summary>Gets or sets left side decorator for database objects.</summary>
public string LeftDecorator { get { return _leftDecorator; } set { _leftDecorator = value; } }
/// <summary>Gets or sets right side decorator for database objects.</summary>
public string RightDecorator { get { return _rightDecorator; } set { _rightDecorator = value; } }
/// <summary>Gets or sets parameter name format.</summary>
public string ParameterFormat { get { return _parameterFormat; } set { _parameterFormat = value; } }
/// <summary>Decorate string representing name of database object.</summary>
/// <param name="name">Name of database object.</param>
/// <returns>Decorated name of database object.</returns>
public string DecorateName(string name)
{
return String.Concat(_leftDecorator, name, _rightDecorator);
}
/// <summary>Decorate string representing name of database object.</summary>
/// <param name="sb">String builder to which add decorated name.</param>
/// <param name="name">Name of database object.</param>
public void DecorateName(StringBuilder sb, string name)
{
sb.Append(_leftDecorator);
sb.Append(name);
sb.Append(_rightDecorator);
}
/// <summary>Get database parameter name.</summary>
/// <param name="parameter">Friendly parameter name or number.</param>
/// <returns>Formatted parameter name.</returns>
public string GetParameterName(object parameter)
{
return String.Format(_parameterFormat, parameter).Replace(" ", "_");
}
/// <summary>Get database parameter name.</summary>
/// <param name="sb">String builder to which add parameter name.</param>
/// <param name="parameter">Friendly parameter name or number.</param>
public void GetParameterName(StringBuilder sb, object parameter)
{
sb.AppendFormat(_parameterFormat, parameter.ToString().Replace(" ", "_"));
}
#endregion Decorators
#region Connection
/// <summary>Open managed connection.</summary>
/// <returns>Opened connection.</returns>
public IDbConnection Open()
{
IDbConnection conn = null;
DynamicConnection ret = null;
lock (SyncLock)
{
if (_tempConn == null)
{
if (TransactionPool.Count == 0 || !_singleConnection)
{
conn = _provider.CreateConnection();
conn.ConnectionString = _connectionString;
conn.Open();
TransactionPool.Add(conn, new Stack<IDbTransaction>());
CommandsPool.Add(conn, new List<IDbCommand>());
}
else
{
conn = TransactionPool.Keys.First();
if (conn.State != ConnectionState.Open)
conn.Open();
}
ret = new DynamicConnection(this, conn, _singleTransaction);
}
else
ret = _tempConn;
}
return ret;
}
/// <summary>Close connection if we are allowed to.</summary>
/// <param name="connection">Connection to manage.</param>
internal void Close(IDbConnection connection)
{
if (connection == null)
return;
lock (SyncLock)
{
if (!_singleConnection && connection != null && TransactionPool.ContainsKey(connection))
{
// Close all commands
if (CommandsPool.ContainsKey(connection))
{
CommandsPool[connection].ForEach(cmd => cmd.Dispose());
CommandsPool[connection].Clear();
}
// Rollback remaining transactions
while (TransactionPool[connection].Count > 0)
{
IDbTransaction trans = TransactionPool[connection].Pop();
trans.Rollback();
trans.Dispose();
}
// Close connection
if (connection.State == ConnectionState.Open)
connection.Close();
// remove from pools
TransactionPool.Remove(connection);
CommandsPool.Remove(connection);
// Set stamp
_poolStamp = DateTime.Now.Ticks;
// Dispose the corpse
connection.Dispose();
}
}
}
#endregion Connection
#region Transaction
/// <summary>Begins a global database transaction.</summary>
/// <remarks>Using this method connection is set to single open
/// connection untill all transactions are finished.</remarks>
/// <returns>Returns <see cref="DynamicTransaction"/> representation.</returns>
public IDbTransaction BeginTransaction()
{
_tempConn = Open() as DynamicConnection;
return _tempConn.BeginTransaction(null, () =>
{
var t = TransactionPool.TryGetValue(_tempConn.Connection);
if (t == null | t.Count == 0)
{
_tempConn.Dispose();
_tempConn = null;
}
});
}
#endregion Transaction
#region IDisposable Members
/// <summary>Performs application-defined tasks associated with freeing,
/// releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
lock (SyncLock)
{
var tables = TablesCache.Values.ToList();
TablesCache.Clear();
tables.ForEach(t => t.Dispose());
foreach (var con in TransactionPool)
{
// Close all commands
if (CommandsPool.ContainsKey(con.Key))
{
CommandsPool[con.Key].ForEach(cmd => cmd.Dispose());
CommandsPool[con.Key].Clear();
}
// Rollback remaining transactions
while (con.Value.Count > 0)
{
IDbTransaction trans = con.Value.Pop();
trans.Rollback();
trans.Dispose();
}
// Close connection
if (con.Key.State == ConnectionState.Open)
con.Key.Close();
// Dispose it
con.Key.Dispose();
}
// Clear pools
TransactionPool.Clear();
CommandsPool.Clear();
}
}
#endregion IDisposable Members
}
}

View File

@@ -0,0 +1,58 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
namespace DynamORM
{
/// <summary>Represents database connection options.</summary>
[Flags]
public enum DynamicDatabaseOptions
{
/// <summary>No specific options.</summary>
None,
/// <summary>Only single presistent database connection.</summary>
SingleConnection,
/// <summary>Only one transaction.</summary>
SingleTransaction,
/// <summary>Database supports top syntax.</summary>
SupportTop,
/// <summary>Database supports limit offset syntax.</summary>
SupportLimitOffset,
/// <summary>Database support standard schema.</summary>
SupportSchema,
/// <summary>Database support stored procedures.</summary>
SupportStoredProcedures
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System.Data;
namespace DynamORM
{
/// <summary>Stores information about column from database schema.</summary>
public struct DynamicSchemaColumn
{
/// <summary>Gets or sets column name.</summary>
public string Name { get; set; }
/// <summary>Gets or sets column type.</summary>
public DbType Type { get; set; }
/// <summary>Gets or sets a value indicating whether column is a key.</summary>
public bool IsKey { get; set; }
/// <summary>Gets or sets a value indicating whether column should have unique value.</summary>
public bool IsUnique { get; set; }
/// <summary>Gets or sets column size.</summary>
public int Size { get; set; }
/// <summary>Gets or sets column precision.</summary>
public byte Precision { get; set; }
/// <summary>Gets or sets column scale.</summary>
public byte Scale { get; set; }
}
}

786
DynamORM/DynamicTable.cs Normal file
View File

@@ -0,0 +1,786 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using DynamORM.Builders;
using DynamORM.Helpers;
using DynamORM.Mapper;
namespace DynamORM
{
/// <summary>Dynamic table is a simple ORM using dynamic objects.</summary>
/// <example>
/// <para>Assume that we have a table representing Users class.</para>
/// <para>
/// <para>Let's take a look at <c>Query</c> posibilities. Assume we want
/// to get enumerator for all records in database, mapped to our class
/// instead of dynamic type we can use following syntax.</para>
/// <para>Approach first. Use dynamic <c>Query</c> method and just set type
/// then just cast it to user class. Remember that you must cast result
/// of <c>Query</c>to <c>IEnumerable&lt;object&gt;</c>. because from
/// point of view of runtime you are operating on <c>object</c> type.</para>
/// <code>(db.Table&lt;User&gt;().Query(type: typeof(User)) as IEnumerable&lt;object&gt;).Cast&lt;User&gt;();</code>
/// <para>Second approach is similar. We ask database using dynamic
/// <c>Query</c> method. The difference is that we use extension method of
/// <c>IEnumerable&lt;object&gt;</c> (to which we must cast to) to map
/// object.</para>
/// <code>(db.Table&lt;User&gt;().Query(columns: "*") as IEnumerable&lt;object&gt;).MapEnumerable&lt;User&gt;();</code>
/// You can also use generic approach. But be careful this method is currently avaliable thanks to framework hack.
/// <code>(db.Table&lt;User&gt;().Query&lt;User&gt;() as IEnumerable&lt;object&gt;).Cast&lt;User&gt;()</code>
/// <para>Another approach uses existing methods, but still requires a
/// cast, because <c>Query</c> also returns dynamic object enumerator.</para>
/// <code>(db.Table&lt;User&gt;().Query().Execute() as IEnumerable&lt;object&gt;).MapEnumerable&lt;User&gt;();</code>
/// </para>
/// </example>
public class DynamicTable : DynamicObject, IDisposable, ICloneable
{
private static HashSet<string> _allowedCommands = new HashSet<string>
{
"Insert", "Update", "Delete",
"Query", "Single", "Where",
"First", "Last", "Get",
"Count", "Sum", "Avg",
"Min", "Max", "Scalar"
};
/// <summary>Gets dynamic database.</summary>
internal DynamicDatabase Database { get; private set; }
/// <summary>Gets type of table (for coning and schema building).</summary>
internal Type TableType { get; private set; }
/// <summary>Gets name of table.</summary>
public virtual string TableName { get; private set; }
/// <summary>Gets table schema.</summary>
/// <remarks>If database doesn't support schema, only key columns are listed here.</remarks>
public virtual Dictionary<string, DynamicSchemaColumn> Schema { get; private set; }
private DynamicTable() { }
/// <summary>Initializes a new instance of the <see cref="DynamicTable" /> class.</summary>
/// <param name="database">Database and connection management.</param>
/// <param name="table">Table name.</param>
/// <param name="keys">Override keys in schema.</param>
public DynamicTable(DynamicDatabase database, string table = "", string[] keys = null)
{
Database = database;
TableName = table;
TableType = null;
BuildAndCacheSchema(keys);
}
/// <summary>Initializes a new instance of the <see cref="DynamicTable" /> class.</summary>
/// <param name="database">Database and connection management.</param>
/// <param name="type">Type describing table.</param>
/// <param name="keys">Override keys in schema.</param>
public DynamicTable(DynamicDatabase database, Type type, string[] keys = null)
{
if (type == null)
throw new ArgumentNullException("type", "Type can't be null.");
Database = database;
TableType = type;
var mapper = DynamicMapperCache.GetMapper(type);
if (mapper != null)
TableName = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ?
type.Name : mapper.Table.Name;
BuildAndCacheSchema(keys);
}
#region Schema
private void BuildAndCacheSchema(string[] keys)
{
Dictionary<string, DynamicSchemaColumn> schema = null;
schema = Database.GetSchema(TableType) ??
Database.GetSchema(TableName);
#region Fill currrent table schema
if (keys == null && TableType != null)
{
var mapper = DynamicMapperCache.GetMapper(TableType);
if (mapper != null)
{
var k = mapper.ColumnsMap.Where(p => p.Value.Column != null && p.Value.Column.IsKey).Select(p => p.Key);
if (k.Count() > 0)
keys = k.ToArray();
}
}
if (schema != null)
{
if (keys == null)
Schema = new Dictionary<string, DynamicSchemaColumn>(schema);
else
{
// TODO: Make this.... nicer
List<string> ks = keys.Select(k => k.ToLower()).ToList();
Schema = schema.ToDictionary(k => k.Key, (v) =>
{
DynamicSchemaColumn dsc = v.Value;
dsc.IsKey = ks.Contains(v.Key);
return dsc;
});
}
}
#endregion Fill currrent table schema
#region Build ad-hock schema
if (keys != null && Schema == null)
{
Schema = keys.Select(k => k.ToLower()).ToList()
.ToDictionary(k => k, k => new DynamicSchemaColumn { Name = k, IsKey = true });
}
#endregion Build ad-hock schema
}
#endregion Schema
#region Basic Queries
/// <summary>Enumerate the reader and yield the result.</summary>
/// <param name="sql">Sql query containing numered parameters in format provided by
/// <see cref="DynamicDatabase.GetParameterName"/> methods. Also names should be formated with
/// <see cref="DecorateName.GetParameterName"/> method.</param>
/// <param name="args">Arguments (parameters).</param>
/// <returns>Enumerator of objects expanded from query.</returns>
public virtual IEnumerable<dynamic> Query(string sql, params object[] args)
{
using (var con = Database.Open())
using (var cmd = con.CreateCommand())
{
using (var rdr = cmd
.SetCommand(sql)
.AddParameters(Database, args)
.ExecuteReader())
while (rdr.Read())
yield return rdr.RowToDynamic();
}
}
/// <summary>Enumerate the reader and yield the result.</summary>
/// <param name="builder">Command builder.</param>
/// <returns>Enumerator of objects expanded from query.</returns>
public virtual IEnumerable<dynamic> Query(IDynamicQueryBuilder builder)
{
using (var con = Database.Open())
using (var cmd = con.CreateCommand())
{
using (var rdr = cmd
.SetCommand(builder)
.ExecuteReader())
while (rdr.Read())
yield return rdr.RowToDynamic();
}
}
/// <summary>Create new <see cref="DynamicSelectQueryBuilder"/>.</summary>
/// <returns>New <see cref="DynamicSelectQueryBuilder"/> instance.</returns>
public virtual DynamicSelectQueryBuilder Query()
{
return new DynamicSelectQueryBuilder(this);
}
/// <summary>Returns a single result.</summary>
/// <param name="sql">Sql query containing numered parameters in format provided by
/// <see cref="DynamicDatabase.GetParameterName"/> methods. Also names should be formated with
/// <see cref="DecorateName.GetParameterName"/> method.</param>
/// <param name="args">Arguments (parameters).</param>
/// <returns>Result of a query.</returns>
public virtual object Scalar(string sql, params object[] args)
{
using (var con = Database.Open())
using (var cmd = con.CreateCommand())
{
return cmd
.SetCommand(sql).AddParameters(Database, args)
.ExecuteScalar();
}
}
/// <summary>Returns a single result.</summary>
/// <param name="builder">Command builder.</param>
/// <returns>Result of a query.</returns>
public virtual object Scalar(IDynamicQueryBuilder builder)
{
using (var con = Database.Open())
using (var cmd = con.CreateCommand())
{
return cmd
.SetCommand(builder)
.ExecuteScalar();
}
}
/// <summary>Execute non query.</summary>
/// <param name="sql">Sql query containing numered parameters in format provided by
/// <see cref="DynamicDatabase.GetParameterName"/> methods. Also names should be formated with
/// <see cref="DecorateName.GetParameterName"/> method.</param>
/// <param name="args">Arguments (parameters).</param>
/// <returns>Number of affected rows.</returns>
public virtual int Execute(string sql, params object[] args)
{
using (var con = Database.Open())
using (var cmd = con.CreateCommand())
{
return cmd
.SetCommand(sql).AddParameters(Database, args)
.ExecuteNonQuery();
}
}
/// <summary>Execute non query.</summary>
/// <param name="builder">Command builder.</param>
/// <returns>Number of affected rows.</returns>
public virtual int Execute(IDynamicQueryBuilder builder)
{
using (var con = Database.Open())
using (var cmd = con.CreateCommand())
{
return cmd
.SetCommand(builder)
.ExecuteNonQuery();
}
}
/// <summary>Execute non query.</summary>
/// <param name="builers">Command builders.</param>
/// <returns>Number of affected rows.</returns>
public virtual int Execute(IDynamicQueryBuilder[] builers)
{
int ret = 0;
using (var con = Database.Open())
{
using (var trans = con.BeginTransaction())
{
foreach (var builder in builers)
{
using (var cmd = con.CreateCommand())
{
ret += cmd
.SetCommand(builder)
.ExecuteNonQuery();
}
}
trans.Commit();
}
}
return ret;
}
#endregion Basic Queries
#region Insert
/// <summary>Create new <see cref="DynamicInsertQueryBuilder"/>.</summary>
/// <returns>New <see cref="DynamicInsertQueryBuilder"/> instance.</returns>
public DynamicInsertQueryBuilder Insert()
{
return new DynamicInsertQueryBuilder(this);
}
/// <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>
/// <param name="o">Anonymous object, an ExpandoObject, a regular old POCO, or a NameValueCollection
/// from a Request.Form or Request.QueryString, containing fields to update.</param>
/// <returns>Number of updated rows.</returns>
public virtual int Insert(object o)
{
return Insert()
.Insert(o)
.Execute();
}
#endregion Insert
#region Update
/// <summary>Create new <see cref="DynamicUpdateQueryBuilder"/>.</summary>
/// <returns>New <see cref="DynamicUpdateQueryBuilder"/> instance.</returns>
public DynamicUpdateQueryBuilder Update()
{
return new DynamicUpdateQueryBuilder(this);
}
/// <summary>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>
/// <param name="o">Anonymous object, an ExpandoObject, a regular old POCO, or a NameValueCollection
/// from a Request.Form or Request.QueryString, containing fields to update.</param>
/// <param name="key">Anonymous object, an ExpandoObject, a regular old POCO, or a NameValueCollection
/// from a Request.Form or Request.QueryString, containing fields with conditions.</param>
/// <returns>Number of updated rows.</returns>
public virtual int Update(object o, object key)
{
return Update()
.Values(o)
.Where(key)
.Execute();
}
/// <summary>Updates a record in the database using schema. You can pass in an Anonymous object, an ExpandoObject,
/// a regular old POCO, or a NameValueCollection from a Request.Form or Request.QueryString.</summary>
/// <param name="o">Anonymous object, an ExpandoObject, a regular old POCO, or a NameValueCollection
/// from a Request.Form or Request.QueryString, containing fields to update and conditions.</param>
/// <returns>Number of updated rows.</returns>
public virtual int Update(object o)
{
return Update()
.Update(o)
.Execute();
}
#endregion Update
#region Delete
/// <summary>Create new <see cref="DynamicDeleteQueryBuilder"/>.</summary>
/// <returns>New <see cref="DynamicDeleteQueryBuilder"/> instance.</returns>
public DynamicDeleteQueryBuilder Delete()
{
return new DynamicDeleteQueryBuilder(this);
}
/// <summary>Removes a record from 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>
/// <param name="o">Anonymous object, an ExpandoObject, a regular old POCO, or a NameValueCollection
/// from a Request.Form or Request.QueryString, containing fields with where conditions.</param>
/// <param name="schema">If <c>true</c> use schema to determine key columns and ignore those which
/// aren't keys.</param>
/// <returns>Number of updated rows.</returns>
public virtual int Delete(object o, bool schema = true)
{
return Delete()
.Where(o, schema)
.Execute();
}
#endregion Delete
#region Universal Dynamic Invoker
/// <summary>This is where the magic begins.</summary>
/// <param name="binder">Binder to invoke.</param>
/// <param name="args">Binder arguments.</param>
/// <param name="result">Binder invoke result.</param>
/// <returns>Returns <c>true</c> if invoke was performed.</returns>
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
// parse the method
var info = binder.CallInfo;
// Get generic types
var types = binder.GetGenericTypeArguments();
// accepting named args only... SKEET!
if (info.ArgumentNames.Count != args.Length)
throw new InvalidOperationException("Please use named arguments for this type of query - the column name, orderby, columns, etc");
var op = binder.Name;
// Avoid strange things
if (!_allowedCommands.Contains(op))
throw new InvalidOperationException(string.Format("Dynamic method '{0}' is not supported.", op));
switch (op)
{
case "Insert":
result = DynamicInsert(args, info, types);
break;
case "Update":
result = DynamicUpdate(args, info, types);
break;
case "Delete":
result = DynamicDelete(args, info, types);
break;
default:
result = DynamicQuery(args, info, op, types);
break;
}
return true;
}
private object DynamicInsert(object[] args, CallInfo info, IList<Type> types)
{
var builder = new DynamicInsertQueryBuilder(this);
if (types != null && types.Count == 1)
HandleTypeArgument<DynamicInsertQueryBuilder>(null, info, ref types, builder, 0);
// loop the named args - see if we have order, columns and constraints
if (info.ArgumentNames.Count > 0)
{
for (int i = 0; i < args.Length; i++)
{
var name = info.ArgumentNames[i].ToLower();
switch (name)
{
case "table":
if (args[i] is string)
builder.Table(args[i].ToString());
else goto default;
break;
case "values":
builder.Insert(args[i]);
break;
case "type":
if (types == null || types.Count == 0)
HandleTypeArgument<DynamicInsertQueryBuilder>(args, info, ref types, builder, i);
else goto default;
break;
default:
builder.Insert(name, args[i]);
break;
}
}
}
// Execute
return Execute(builder);
}
private object DynamicUpdate(object[] args, CallInfo info, IList<Type> types)
{
var builder = new DynamicUpdateQueryBuilder(this);
if (types != null && types.Count == 1)
HandleTypeArgument<DynamicUpdateQueryBuilder>(null, info, ref types, builder, 0);
// loop the named args - see if we have order, columns and constraints
if (info.ArgumentNames.Count > 0)
{
for (int i = 0; i < args.Length; i++)
{
var name = info.ArgumentNames[i].ToLower();
switch (name)
{
case "table":
if (args[i] is string)
builder.Table(args[i].ToString());
else goto default;
break;
case "update":
builder.Update(args[i]);
break;
case "values":
builder.Values(args[i]);
break;
case "where":
builder.Where(args[i]);
break;
case "type":
if (types == null || types.Count == 0)
HandleTypeArgument(args, info, ref types, builder, i);
else goto default;
break;
default:
builder.Update(name, args[i]);
break;
}
}
}
// Execute
return Execute(builder);
}
private object DynamicDelete(object[] args, CallInfo info, IList<Type> types)
{
var builder = new DynamicDeleteQueryBuilder(this);
if (types != null && types.Count == 1)
HandleTypeArgument<DynamicDeleteQueryBuilder>(null, info, ref types, builder, 0);
// loop the named args - see if we have order, columns and constraints
if (info.ArgumentNames.Count > 0)
{
for (int i = 0; i < args.Length; i++)
{
var name = info.ArgumentNames[i].ToLower();
switch (name)
{
case "table":
if (args[i] is string)
builder.Table(args[i].ToString());
else goto default;
break;
case "where":
builder.Where(args[i], false);
break;
case "delete":
builder.Where(args[i], true);
break;
case "type":
if (types == null || types.Count == 0)
HandleTypeArgument<DynamicDeleteQueryBuilder>(args, info, ref types, builder, i);
else goto default;
break;
default:
builder.Where(name, args[i]);
break;
}
}
}
// Execute
return Execute(builder);
}
private object DynamicQuery(object[] args, CallInfo info, string op, IList<Type> types)
{
object result;
var builder = new DynamicSelectQueryBuilder(this);
if (types != null && types.Count == 1)
HandleTypeArgument<DynamicSelectQueryBuilder>(null, info, ref types, builder, 0);
// loop the named args - see if we have order, columns and constraints
if (info.ArgumentNames.Count > 0)
{
for (int i = 0; i < args.Length; i++)
{
var name = info.ArgumentNames[i].ToLower();
// TODO: Make this nicer
switch (name)
{
case "order":
if (args[i] is string)
builder.OrderBy(((string)args[i]).Split(','));
else if (args[i] is string[])
builder.OrderBy(args[i] as string);
else if (args[i] is DynamicColumn[])
builder.OrderBy((DynamicColumn[])args[i]);
else if (args[i] is DynamicColumn)
builder.OrderBy((DynamicColumn)args[i]);
else goto default;
break;
case "group":
if (args[i] is string)
builder.GroupBy(((string)args[i]).Split(','));
else if (args[i] is string[])
builder.GroupBy(args[i] as string);
else if (args[i] is DynamicColumn[])
builder.GroupBy((DynamicColumn[])args[i]);
else if (args[i] is DynamicColumn)
builder.GroupBy((DynamicColumn)args[i]);
else goto default;
break;
case "columns":
if (args[i] is string)
builder.Select(((string)args[i]).Split(','));
else if (args[i] is string[])
builder.Select(args[i] as string);
else if (args[i] is DynamicColumn[])
builder.Select((DynamicColumn[])args[i]);
else if (args[i] is DynamicColumn)
builder.Select((DynamicColumn)args[i]);
else goto default;
break;
case "where":
builder.Where(args[i]);
break;
case "table":
if (args[i] is string)
builder.Table(args[i].ToString());
else goto default;
break;
case "type":
if (types == null || types.Count == 0)
HandleTypeArgument<DynamicSelectQueryBuilder>(args, info, ref types, builder, i);
else goto default;
break;
default:
builder.Where(name, args[i]);
break;
}
}
}
if (op == "Count" && builder.Columns.Count == 0)
{
result = Scalar(builder.Select(new DynamicColumn
{
ColumnName = "*",
Aggregate = op.ToUpper(),
Alias = "Count"
}));
if (result is long)
result = (int)(long)result;
}
else if (op == "Sum" || op == "Max" ||
op == "Min" || op == "Avg" || op == "Count")
{
if (builder.Columns.Count == 0)
throw new InvalidOperationException("You must select at least one column to agregate.");
foreach (var o in builder.Columns)
o.Aggregate = op.ToUpper();
if (builder.Columns.Count == 1)
{
result = Scalar(builder);
if (op == "Count" && result is long)
result = (int)(long)result;
}
else
{
result = Query(builder).FirstOrDefault(); // return lots
}
}
else
{
// build the SQL
var justOne = op == "First" || op == "Last" || op == "Get" || op == "Single";
// Be sure to sort by DESC on selected columns
if (op == "Last")
{
if (builder.Order.Count > 0)
foreach (var o in builder.Order)
o.Order = o.Order == DynamicColumn.SortOrder.Desc ?
DynamicColumn.SortOrder.Asc : DynamicColumn.SortOrder.Desc;
}
if (justOne && !(op == "Last" && builder.Order.Count == 0))
{
if ((Database.Options & DynamicDatabaseOptions.SupportLimitOffset) == DynamicDatabaseOptions.SupportLimitOffset)
builder.Limit(1);
else if ((Database.Options & DynamicDatabaseOptions.SupportTop) == DynamicDatabaseOptions.SupportTop)
builder.Top(1);
}
if (op == "Scalar")
{
if (builder.Columns.Count != 1)
throw new InvalidOperationException("You must select one column in scalar steatement.");
result = Scalar(builder);
}
else
{
if (justOne)
{
if (op == "Last" && builder.Order.Count == 0)
result = Query(builder).LastOrDefault(); // Last record fallback
else
result = Query(builder).FirstOrDefault(); // return a single record
}
else
result = Query(builder); // return lots
// MapEnumerable to specified result (still needs to be casted after that)
if (types != null)
{
if (types.Count == 1)
result = justOne ?
result.Map(types[0]) :
((IEnumerable<object>)result).MapEnumerable(types[0]);
// TODO: Dictionaries
}
}
}
return result;
}
private void HandleTypeArgument<T>(object[] args, CallInfo info, ref IList<Type> types, DynamicQueryBuilder<T> builder, int i) where T : class
{
if (args != null)
{
if (args[i] is Type[])
types = new List<Type>((Type[])args[i]);
else if (args[i] is Type)
types = new List<Type>(new Type[] { (Type)args[i] });
}
if (types != null && types.Count == 1 && !info.ArgumentNames.Any(a => a.ToLower() == "table"))
builder.Table(types[0]);
}
#endregion Universal Dynamic Invoker
#region IDisposable Members
/// <summary>Performs application-defined tasks associated with
/// freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
Database.RemoveFromCache(this);
// Lose reference but don't kill it.
if (Database != null)
Database = null;
}
#endregion IDisposable Members
#region ICloneable Members
/// <summary>Creates a new object that is a copy of the current
/// instance.</summary>
/// <returns>A new object that is a copy of this instance.</returns>
public object Clone()
{
return new DynamicTable()
{
Database = this.Database,
Schema = this.Schema,
TableName = this.TableName,
TableType = this.TableType
};
}
#endregion ICloneable Members
}
}

View File

@@ -0,0 +1,138 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Data;
namespace DynamORM
{
/// <summary>Helper class to easy manage transaction.</summary>
public class DynamicTransaction : IDbTransaction, IDisposable
{
private DynamicDatabase _db;
private DynamicConnection _con;
private bool _singleTransaction;
private Action _disposed;
private bool _operational = false;
/// <summary>Initializes a new instance of the <see cref="DynamicTransaction" /> class.</summary>
/// <param name="db">Database connection manager.</param>
/// <param name="con">Active connection.</param>
/// <param name="singleTransaction">Are we using single transaction mode? I so... act correctly.</param>
/// <param name="il">One of the <see cref="System.Data.IsolationLevel"/> values.</param>
/// <param name="disposed">This action is invoked when transaction is disposed.</param>
internal DynamicTransaction(DynamicDatabase db, DynamicConnection con, bool singleTransaction, IsolationLevel? il, Action disposed)
{
_db = db;
_con = con;
_singleTransaction = singleTransaction;
_disposed = disposed;
lock (_db.SyncLock)
{
if (!_db.TransactionPool.ContainsKey(_con.Connection))
throw new InvalidOperationException("Can't create transaction using disposed connection.");
else if (_singleTransaction && _db.TransactionPool[_con.Connection].Count > 0)
_operational = false;
else
{
_db.TransactionPool[_con.Connection].Push(_con.Connection.BeginTransaction());
_db.PoolStamp = DateTime.Now.Ticks;
_operational = true;
}
}
}
/// <summary>Commits the database transaction.</summary>
public void Commit()
{
lock (_db.SyncLock)
{
if (_operational)
{
var t = _db.TransactionPool.TryGetValue(_con.Connection);
if (t != null && t.Count > 0)
{
IDbTransaction trans = t.Pop();
_db.PoolStamp = DateTime.Now.Ticks;
trans.Commit();
trans.Dispose();
}
_operational = false;
}
}
}
/// <summary>Rolls back a transaction from a pending state.</summary>
public void Rollback()
{
lock (_db.SyncLock)
{
if (_operational)
{
var t = _db.TransactionPool.TryGetValue(_con.Connection);
if (t != null && t.Count > 0)
{
IDbTransaction trans = t.Pop();
_db.PoolStamp = DateTime.Now.Ticks;
trans.Rollback();
trans.Dispose();
}
_operational = false;
}
}
}
/// <summary>Gets connection object to associate with the transaction.</summary>
public IDbConnection Connection
{
get { return _con; }
}
/// <summary>Gets <see cref="System.Data.IsolationLevel"/> for this transaction.</summary>
public IsolationLevel IsolationLevel { get; private set; }
/// <summary>Performs application-defined tasks associated with
/// freeing, releasing, or resetting unmanaged resources.</summary>
public void Dispose()
{
Rollback();
if (_disposed != null)
_disposed();
}
}
}

View File

@@ -0,0 +1,126 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System.Collections.Generic;
using System.Linq;
namespace DynamORM.Helpers
{
/// <summary>Defines methods to support the comparison of collections for equality.</summary>
/// <typeparam name="T">The type of collection to compare.</typeparam>
public class CollectionComparer<T> : IEqualityComparer<IEnumerable<T>>
{
/// <summary>Determines whether the specified objects are equal.</summary>
/// <param name="first">The first object of type T to compare.</param>
/// <param name="second">The second object of type T to compare.</param>
/// <returns>Returns <c>true</c> if the specified objects are equal; otherwise, <c>false</c>.</returns>
bool IEqualityComparer<IEnumerable<T>>.Equals(IEnumerable<T> first, IEnumerable<T> second)
{
return Equals(first, second);
}
/// <summary>Returns a hash code for the specified object.</summary>
/// <param name="enumerable">The enumerable for which a hash code is to be returned.</param>
/// <returns>A hash code for the specified object.</returns>
int IEqualityComparer<IEnumerable<T>>.GetHashCode(IEnumerable<T> enumerable)
{
return GetHashCode(enumerable);
}
/// <summary>Returns a hash code for the specified object.</summary>
/// <param name="enumerable">The enumerable for which a hash code is to be returned.</param>
/// <returns>A hash code for the specified object.</returns>
public static int GetHashCode(IEnumerable<T> enumerable)
{
int hash = 17;
foreach (T val in enumerable.OrderBy(x => x))
hash = (hash * 23) + val.GetHashCode();
return hash;
}
/// <summary>Determines whether the specified objects are equal.</summary>
/// <param name="first">The first object of type T to compare.</param>
/// <param name="second">The second object of type T to compare.</param>
/// <returns>Returns <c>true</c> if the specified objects are equal; otherwise, <c>false</c>.</returns>
public static bool Equals(IEnumerable<T> first, IEnumerable<T> second)
{
if ((first == null) != (second == null))
return false;
if (!object.ReferenceEquals(first, second) && (first != null))
{
if (first.Count() != second.Count())
return false;
if ((first.Count() != 0) && HaveMismatchedElement(first, second))
return false;
}
return true;
}
private static bool HaveMismatchedElement(IEnumerable<T> first, IEnumerable<T> second)
{
int firstCount;
int secondCount;
var firstElementCounts = GetElementCounts(first, out firstCount);
var secondElementCounts = GetElementCounts(second, out secondCount);
if (firstCount != secondCount)
return true;
foreach (var kvp in firstElementCounts)
if (kvp.Value != (secondElementCounts.TryGetNullable(kvp.Key) ?? 0))
return true;
return false;
}
private static Dictionary<T, int> GetElementCounts(IEnumerable<T> enumerable, out int nullCount)
{
var dictionary = new Dictionary<T, int>();
nullCount = 0;
foreach (T element in enumerable)
{
if (element == null)
nullCount++;
else
{
int count = dictionary.TryGetNullable(element) ?? 0;
dictionary[element] = ++count;
}
}
return dictionary;
}
}
}

View File

@@ -0,0 +1,156 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq.Expressions;
using System.Reflection;
namespace DynamORM.Helpers
{
/// <summary>Framework detection and specific implementations.</summary>
public static class FrameworkTools
{
#region Mono or .NET Framework detection
/// <summary>This is preaty simple trick.</summary>
private static bool _isMono = Type.GetType("Mono.Runtime") != null;
/// <summary>Gets a value indicating whether application is running under mono runtime.</summary>
public static bool IsMono { get { return _isMono; } }
#endregion Mono or .NET Framework detection
static FrameworkTools()
{
_frameworkTypeArgumentsGetter = CreateTypeArgumentsGetter();
}
#region GetGenericTypeArguments
private static Func<InvokeMemberBinder, IList<Type>> _frameworkTypeArgumentsGetter = null;
private static Func<InvokeMemberBinder, IList<Type>> CreateTypeArgumentsGetter()
{
// HACK: Creating binders assuming types are correct... this may fail.
if (IsMono)
{
var binderType = typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.GetType("Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder");
if (binderType != null)
{
ParameterExpression param = Expression.Parameter(typeof(InvokeMemberBinder), "o");
return Expression.Lambda<Func<InvokeMemberBinder, IList<Type>>>(
Expression.TypeAs(
Expression.Field(
Expression.TypeAs(param, binderType), "typeArguments"),
typeof(IList<Type>)), param).Compile();
}
}
else
{
var inter = typeof(Microsoft.CSharp.RuntimeBinder.RuntimeBinderException).Assembly.GetType("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder");
if (inter != null)
{
var prop = inter.GetProperty("TypeArguments");
if (!prop.CanRead)
return null;
var objParm = Expression.Parameter(typeof(InvokeMemberBinder), "o");
return Expression.Lambda<Func<InvokeMemberBinder, IList<Type>>>(
Expression.TypeAs(
Expression.Property(
Expression.TypeAs(objParm, inter), prop.Name),
typeof(IList<Type>)), objParm).Compile();
}
}
return null;
}
/// <summary>Extension method allowing to easyly extract generic type
/// arguments from <see cref="InvokeMemberBinder"/> assuming that it
/// inherits from
/// <c>Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder</c>
/// in .NET Framework or
/// <c>Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder</c>
/// under Mono.</summary>
/// <param name="binder">Binder from which get type arguments.</param>
/// <remarks>This is generally a bad solution, but there is no other
/// currently so we have to go with it.</remarks>
/// <returns>List of types passed as generic parameters.</returns>
public static IList<Type> GetGenericTypeArguments(this InvokeMemberBinder binder)
{
// First try to use delegate if exist
if (_frameworkTypeArgumentsGetter != null)
return _frameworkTypeArgumentsGetter(binder);
if (_isMono)
{
// HACK: Using Reflection
// In mono this is trivial.
// First we get field info.
var field = binder.GetType().GetField("typeArguments", BindingFlags.Instance |
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
// If this was a success get and return it's value
if (field != null)
return field.GetValue(binder) as IList<Type>;
}
else
{
// HACK: Using Reflection
// In this case, we need more aerobic :D
// First, get the interface
var inter = binder.GetType().GetInterface("Microsoft.CSharp.RuntimeBinder.ICSharpInvokeOrInvokeMemberBinder");
if (inter != null)
{
// Now get property.
var prop = inter.GetProperty("TypeArguments");
// If we have a property, return it's value
if (prop != null)
return prop.GetValue(binder, null) as IList<Type>;
}
}
// Sadly return null if failed.
return null;
}
#endregion GetGenericTypeArguments
}
}

View File

@@ -0,0 +1,148 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Data;
namespace DynamORM.Mapper
{
/// <summary>Allows to add table name to class.</summary>
[AttributeUsage(AttributeTargets.Property)]
public class ColumnAttribute : Attribute
{
/// <summary>Gets or sets name.</summary>
public string Name { get; set; }
/// <summary>Gets or sets column type.</summary>
/// <remarks>Used when overriding schema.</remarks>
public DbType? Type { get; set; }
/// <summary>Gets or sets a value indicating whether column is a key.</summary>
public bool IsKey { get; set; }
/// <summary>Gets or sets a value indicating whether column should have unique value.</summary>
/// <remarks>Used when overriding schema.</remarks>
public bool? IsUnique { get; set; }
/// <summary>Gets or sets column size.</summary>
/// <remarks>Used when overriding schema.</remarks>
public int? Size { get; set; }
/// <summary>Gets or sets column precision.</summary>
/// <remarks>Used when overriding schema.</remarks>
public byte? Precision { get; set; }
/// <summary>Gets or sets column scale.</summary>
/// <remarks>Used when overriding schema.</remarks>
public byte? Scale { get; set; }
#region Constructors
/// <summary>Initializes a new instance of the <see cref="ColumnAttribute" /> class.</summary>
public ColumnAttribute() { }
/// <summary>Initializes a new instance of the <see cref="ColumnAttribute" /> class.</summary>
/// <param name="name">Name of column.</param>
public ColumnAttribute(string name)
{
Name = name;
}
/// <summary>Initializes a new instance of the <see cref="ColumnAttribute" /> class.</summary>
/// <param name="name">Name of column.</param>
/// <param name="isKey">Set column as a key column.</param>
public ColumnAttribute(string name, bool isKey)
: this(name)
{
IsKey = isKey;
}
/// <summary>Initializes a new instance of the <see cref="ColumnAttribute" /> class.</summary>
/// <param name="name">Name of column.</param>
/// <param name="isKey">Set column as a key column.</param>
/// <param name="type">Set column type.</param>
public ColumnAttribute(string name, bool isKey, DbType type)
: this(name, isKey)
{
Type = type;
}
/// <summary>Initializes a new instance of the <see cref="ColumnAttribute" /> class.</summary>
/// <param name="name">Name of column.</param>
/// <param name="isKey">Set column as a key column.</param>
/// <param name="type">Set column type.</param>
/// <param name="size">Set column value size.</param>
public ColumnAttribute(string name, bool isKey, DbType type, int size)
: this(name, isKey, type)
{
Size = size;
}
/// <summary>Initializes a new instance of the <see cref="ColumnAttribute" /> class.</summary>
/// <param name="name">Name of column.</param>
/// <param name="isKey">Set column as a key column.</param>
/// <param name="type">Set column type.</param>
/// <param name="precision">Set column value precision.</param>
/// <param name="scale">Set column value scale.</param>
public ColumnAttribute(string name, bool isKey, DbType type, byte precision, byte scale)
: this(name, isKey, type)
{
Precision = precision;
Scale = scale;
}
/// <summary>Initializes a new instance of the <see cref="ColumnAttribute" /> class.</summary>
/// <param name="name">Name of column.</param>
/// <param name="isKey">Set column as a key column.</param>
/// <param name="type">Set column type.</param>
/// <param name="size">Set column value size.</param>
/// <param name="precision">Set column value precision.</param>
/// <param name="scale">Set column value scale.</param>
public ColumnAttribute(string name, bool isKey, DbType type, int size, byte precision, byte scale)
: this(name, isKey, type, precision, scale)
{
Size = size;
}
/// <summary>Initializes a new instance of the <see cref="ColumnAttribute" /> class.</summary>
/// <param name="name">Name of column.</param>
/// <param name="isKey">Set column as a key column.</param>
/// <param name="isUnique">Set column has unique value.</param>
/// <param name="type">Set column type.</param>
/// <param name="size">Set column value size.</param>
/// <param name="precision">Set column value precision.</param>
/// <param name="scale">Set column value scale.</param>
public ColumnAttribute(string name, bool isKey, bool isUnique, DbType type, int size, byte precision, byte scale)
: this(name, isKey, type, size, precision, scale)
{
IsUnique = isUnique;
}
#endregion Constructors
}
}

View File

@@ -0,0 +1,74 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
namespace DynamORM.Mapper
{
/// <summary>Class with maper cache.</summary>
public static class DynamicMapperCache
{
private static readonly object SyncLock = new object();
private static Dictionary<Type, DynamicTypeMap> _cache = new Dictionary<Type, DynamicTypeMap>();
/// <summary>Get type mapper.</summary>
/// <typeparam name="T">Type of mapper.</typeparam>
/// <returns>Type mapper.</returns>
public static DynamicTypeMap GetMapper<T>()
{
return GetMapper(typeof(T));
}
/// <summary>Get type mapper.</summary>
/// <param name="type">Type of mapper.</param>
/// <returns>Type mapper.</returns>
public static DynamicTypeMap GetMapper(Type type)
{
if (type == null)
return null;
/*if (type.IsAnonymous())
return null;*/
DynamicTypeMap mapper = null;
lock (SyncLock)
{
if (!_cache.TryGetValue(type, out mapper))
{
mapper = new DynamicTypeMap(type);
if (mapper != null)
_cache.Add(type, mapper);
}
}
return mapper;
}
}
}

View File

@@ -0,0 +1,102 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Linq.Expressions;
using System.Reflection;
namespace DynamORM.Mapper
{
/// <summary>Dynamic property invoker.</summary>
public class DynamicPropertyInvoker
{
/// <summary>Gets value getter.</summary>
public Func<object, object> Get { get; private set; }
/// <summary>Gets value setter.</summary>
public Action<object, object> Set { get; private set; }
/// <summary>Gets name of property.</summary>
public string Name { get; private set; }
/// <summary>Gets type column description.</summary>
public ColumnAttribute Column { get; private set; }
/// <summary>Gets a value indicating whether this <see cref="DynamicPropertyInvoker"/> is ignored in some cases.</summary>
public bool Ignore { get; private set; }
/// <summary>Initializes a new instance of the <see cref="DynamicPropertyInvoker" /> class.</summary>
/// <param name="property">Property info to be invoked in the future.</param>
/// <param name="attr">Column attribute if exist.</param>
public DynamicPropertyInvoker(PropertyInfo property, ColumnAttribute attr)
{
Name = property.Name;
var ignore = property.GetCustomAttributes(typeof(IgnoreAttribute), false);
Ignore = ignore != null && ignore.Length > 0;
Column = attr;
Get = CreateGetter(property);
Set = CreateSetter(property);
}
private Func<object, object> CreateGetter(PropertyInfo property)
{
if (!property.CanRead)
return null;
var objParm = Expression.Parameter(typeof(object), "o");
return Expression.Lambda<Func<object, object>>(
Expression.Convert(
Expression.Property(
Expression.TypeAs(objParm, property.DeclaringType),
property.Name),
typeof(object)), objParm).Compile();
}
private Action<object, object> CreateSetter(PropertyInfo property)
{
if (!property.CanWrite)
return null;
var objParm = Expression.Parameter(typeof(object), "o");
var valueParm = Expression.Parameter(typeof(object), "value");
return Expression.Lambda<Action<object, object>>(
Expression.Assign(
Expression.Property(
Expression.Convert(objParm, property.DeclaringType),
property.Name),
Expression.Convert(valueParm, property.PropertyType)),
objParm, valueParm).Compile();
}
}
}

View File

@@ -0,0 +1,134 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace DynamORM.Mapper
{
/// <summary>Represents type columnMap.</summary>
public class DynamicTypeMap
{
/// <summary>Gets mapper destination type creator.</summary>
public Type Type { get; private set; }
/// <summary>Gets type table description.</summary>
public TableAttribute Table { get; private set; }
/// <summary>Gets object creator.</summary>
public Func<object> Creator { get; private set; }
/// <summary>Gets map of columns to properties.</summary>
public Dictionary<string, DynamicPropertyInvoker> ColumnsMap { get; private set; }
/// <summary>Gets map of properties to column.</summary>
public Dictionary<string, string> PropertyMap { get; private set; }
/// <summary>Gets list of ignored properties.</summary>
public List<string> Ignored { get; private set; }
/// <summary>Initializes a new instance of the <see cref="DynamicTypeMap" /> class.</summary>
/// <param name="type">Type to which columnMap objects.</param>
public DynamicTypeMap(Type type)
{
Type = type;
var attr = type.GetCustomAttributes(typeof(TableAttribute), false);
if (attr != null && attr.Length > 0)
Table = (TableAttribute)attr[0];
Creator = CreateCreator();
CreateColumnAndPropertyMap();
}
private void CreateColumnAndPropertyMap()
{
var columnMap = new Dictionary<string, DynamicPropertyInvoker>();
var propertyMap = new Dictionary<string, string>();
foreach (var pi in Type.GetProperties())
{
ColumnAttribute attr = null;
var attrs = pi.GetCustomAttributes(typeof(ColumnAttribute), true);
if (attrs != null && attrs.Length > 0)
attr = (ColumnAttribute)attrs[0];
string col = attr == null || string.IsNullOrEmpty(attr.Name) ? pi.Name : attr.Name;
var val = new DynamicPropertyInvoker(pi, attr);
columnMap.Add(col.ToLower(), val);
propertyMap.Add(pi.Name, col);
}
ColumnsMap = columnMap;
PropertyMap = propertyMap;
Ignored = columnMap.Where(i => i.Value.Ignore).Select(i => i.Value.Name).ToList();
}
private Func<object> CreateCreator()
{
if (Type.GetConstructor(Type.EmptyTypes) != null)
return Expression.Lambda<Func<object>>(Expression.New(Type)).Compile();
return null;
}
/// <summary>Create object of <see cref="DynamicTypeMap.Type"/> type and fill values from <c>source</c>.</summary>
/// <param name="source">Object containing values that will be mapped to newy created object.</param>
/// <returns>New object of <see cref="DynamicTypeMap.Type"/> type with matching values from <c>source</c>.</returns>
public object Create(object source)
{
return Map(source, Creator());
}
/// <summary>Fill values from <c>source</c> to <see cref="DynamicTypeMap.Type"/> object in <c>destination</c>.</summary>
/// <param name="source">Object containing values that will be mapped to newy created object.</param>
/// <param name="destination">Object of <see cref="DynamicTypeMap.Type"/> type to which copy values from <c>source</c>.</param>
/// <returns>Object of <see cref="DynamicTypeMap.Type"/> type with matching values from <c>source</c>.</returns>
public object Map(object source, object destination)
{
DynamicPropertyInvoker dpi = null;
foreach (var item in source.ToDictionary())
{
if (ColumnsMap.TryGetValue(item.Key.ToLower(), out dpi) && item.Value != null)
if (dpi.Set != null)
dpi.Set(destination, item.Value);
}
return destination;
}
}
}

View File

@@ -0,0 +1,39 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
namespace DynamORM.Mapper
{
/// <summary>Allows to add ignore action to property.</summary>
/// <remarks>Property still get's mapped from output.</remarks>
[AttributeUsage(AttributeTargets.Property)]
public class IgnoreAttribute : Attribute
{
}
}

View File

@@ -0,0 +1,46 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
namespace DynamORM.Mapper
{
/// <summary>Allows to add table name to class.</summary>
[AttributeUsage(AttributeTargets.Class)]
public class TableAttribute : Attribute
{
/// <summary>Gets or sets name.</summary>
public string Name { get; set; }
/// <summary>Gets or sets a value indicating whether overide database
/// schema values.</summary>
/// <remarks>If database doeesn't support schema, you still have to
/// set this to true to get schema from type.</remarks>
public bool Override { get; set; }
}
}

View File

@@ -0,0 +1,65 @@
/*
* DynamORM - Dynamic Object-Relational Mapping library.
* Copyright (c) 2012, Grzegorz Russek (grzegorz.russek@gmail.com)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*
* See: http://opensource.org/licenses/bsd-license.php
*/
using System.Reflection;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("DynamORM")]
[assembly: AssemblyDescription("Dynamic Object-Relational Mapping library.")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("RUSSEK Software")]
[assembly: AssemblyProduct("DynamORM")]
[assembly: AssemblyCopyright("Copyright © RUSSEK Software 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("0a42480b-bba7-4b01-807f-9962a76e4e47")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.1")]
[assembly: AssemblyFileVersion("1.0.0.1")]