closes #6
closes #7
fixes #9
This commit is contained in:
grzegorz.russek
2013-06-06 21:46:04 +00:00
parent b12a838a4f
commit f9684f484e
18 changed files with 821 additions and 190 deletions

View File

@@ -99,6 +99,42 @@ namespace DynamORM.Tests.Select
Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE ((u.\"Deleted\" = [${0}]) OR (u.\"IsActive\" = [${1}]))", cmd.Parameters.Keys.First(), cmd.Parameters.Keys.Last()), cmd.CommandText());
}
/// <summary>
/// Tests the where expression equal with brackets.
/// </summary>
[Test]
public void TestWhereBracketsOrEq2()
{
IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database);
cmd.From(x => x.dbo.Users.As(x.u))
.Where(new DynamicColumn("u.Id_User").Greater(1))
.Where(new DynamicColumn("u.Deleted").Eq(0).SetBeginBlock())
.Where(new DynamicColumn("u.IsActive").Eq(1).SetOr().SetEndBlock());
Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Id_User\" > [${0}]) AND ((u.\"Deleted\" = [${1}]) OR (u.\"IsActive\" = [${2}]))",
cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText());
}
/// <summary>
/// Tests the where expression equal with brackets.
/// </summary>
[Test]
public void TestWhereBracketsOrEqForgotToEnd()
{
IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database);
cmd.From(x => x.dbo.Users.As(x.u))
.Where(new DynamicColumn("u.Id_User").Greater(1))
.Where(new DynamicColumn("u.Deleted").Eq(0).SetBeginBlock())
.Where(new DynamicColumn("u.IsActive").Eq(1).SetOr());
using (var con = Database.Open())
using (var c = con.CreateCommand())
Assert.AreEqual(string.Format("SELECT * FROM \"dbo\".\"Users\" AS u WHERE (u.\"Id_User\" > @0) AND ((u.\"Deleted\" = @1) OR (u.\"IsActive\" = @2))"),
c.SetCommand(cmd).CommandText);
}
/// <summary>
/// Tests the where expression not equal.
/// </summary>

View File

@@ -616,6 +616,21 @@ namespace DynamORM.Tests.Select
cmd.Parameters.Keys.ToArray()[0], cmd.Parameters.Keys.ToArray()[1], cmd.Parameters.Keys.ToArray()[2]), cmd.CommandText());
}
/// <summary>
/// Tests select escaped case.
/// </summary>
[Test]
public void TestCoalesceEscaped()
{
IDynamicSelectQueryBuilder cmd = new DynamicSelectQueryBuilder(Database);
cmd.From(u => u.dbo.Users.As(u.c))
.Select(u => u("COALESCE(", Database.DecorateName("ServerHash"), ", ", new byte[16] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, ")").As(u.Hash));
Assert.AreEqual(string.Format("SELECT COALESCE(\"ServerHash\", [${0}]) AS \"Hash\" FROM \"dbo\".\"Users\" AS c",
cmd.Parameters.Keys.ToArray()[0]), cmd.CommandText());
}
/// <summary>
/// Tests select escaped case with sub query.
/// </summary>

View File

@@ -34,12 +34,87 @@ using System.Collections.Generic;
using System.Linq;
using DynamORM.Builders.Implementation;
using DynamORM.Helpers;
using DynamORM.Helpers.Dynamics;
using DynamORM.Mapper;
namespace DynamORM.Builders.Extensions
{
internal static class DynamicModifyBuilderExtensions
{
internal static T Table<T>(this T builder, Func<dynamic, object> func) where T : DynamicModifyBuilder
{
if (func == null)
throw new ArgumentNullException("Function cannot be null.");
using (var parser = DynamicParser.Parse(func))
{
var result = parser.Result;
// If the expression result is string.
if (result is string)
return builder.Table((string)result);
else if (result is Type)
return builder.Table((Type)result);
else if (result is DynamicParser.Node)
{
// Or if it resolves to a dynamic node
var node = (DynamicParser.Node)result;
string owner = null;
string main = null;
while (true)
{
// Deny support for the AS() virtual method...
if (node is DynamicParser.Node.Method && ((DynamicParser.Node.Method)node).Name.ToUpper() == "AS")
throw new ArgumentException(string.Format("Alias is not supported on modification builders. (Parsing: {0})", result));
// Support for table specifications...
if (node is DynamicParser.Node.GetMember)
{
if (owner != null)
throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result));
if (main != null)
owner = ((DynamicParser.Node.GetMember)node).Name;
else
main = ((DynamicParser.Node.GetMember)node).Name;
node = node.Host;
continue;
}
// Support for generic sources...
if (node is DynamicParser.Node.Invoke)
{
if (owner == null && main == null)
{
var invoke = (DynamicParser.Node.Invoke)node;
if (invoke.Arguments.Length == 1 && invoke.Arguments[0] is Type)
return builder.Table((Type)invoke.Arguments[0]);
else if (invoke.Arguments.Length == 1 && invoke.Arguments[0] is String)
return builder.Table((string)invoke.Arguments[0]);
else
throw new ArgumentException(string.Format("Invalid argument count or type when parsing '{2}'. Invoke supports only one argument of type Type or String", owner, main, result));
}
else if (owner != null)
throw new ArgumentException(string.Format("Owner '{0}.{1}' is already set when parsing '{2}'.", owner, main, result));
else if (main != null)
throw new ArgumentException(string.Format("Main '{0}' is already set when parsing '{1}'.", main, result));
}
if (!string.IsNullOrEmpty(main))
return builder.Table(string.Format("{0}{1}",
string.IsNullOrEmpty(owner) ? string.Empty : string.Format("{0}.", owner),
main));
}
}
throw new ArgumentException(string.Format("Unable to set table parsing '{0}'", result));
}
}
internal static T Table<T>(this T builder, string tableName, Dictionary<string, DynamicSchemaColumn> schema = null) where T : DynamicModifyBuilder
{
var tuple = tableName.Validated("Table Name").SplitSomethingAndAlias();
@@ -49,6 +124,9 @@ namespace DynamORM.Builders.Extensions
var parts = tuple.Item1.Split('.');
if (parts.Length > 2)
throw new ArgumentException(string.Format("Table name can consist only from name or owner and name. ({0})", tableName), "tableName");
builder.Tables.Clear();
builder.Tables.Add(new DynamicQueryBuilder.TableInfo(builder.Database,
builder.Database.StripName(parts.Last()).Validated("Table"), null,
@@ -62,6 +140,9 @@ namespace DynamORM.Builders.Extensions
internal static T Table<T>(this T builder, Type type) where T : DynamicQueryBuilder
{
if (type.IsAnonymous())
throw new InvalidOperationException(string.Format("Cant assign anonymous type as a table ({0}).", type.FullName));
var mapper = DynamicMapperCache.GetMapper(type);
if (mapper == null)

View File

@@ -43,6 +43,11 @@ namespace DynamORM.Builders.Extensions
#region Where
internal static T InternalWhere<T>(this T builder, Func<dynamic, object> func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
{
return builder.InternalWhere(false, false, func);
}
internal static T InternalWhere<T>(this T builder, bool addBeginBrace, bool addEndBrace, Func<dynamic, object> func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
{
if (func == null) throw new ArgumentNullException("Array of functions cannot be null.");
@@ -88,8 +93,15 @@ namespace DynamORM.Builders.Extensions
condition = builder.Parse(result, pars: builder.Parameters).Validated("Where condition");
}
if (builder.WhereCondition == null) builder.WhereCondition = condition;
else builder.WhereCondition = string.Format("{0} {1} {2}", builder.WhereCondition, and ? "AND" : "OR", condition);
if (addBeginBrace) builder.OpenBracketsCount++;
if (addEndBrace) builder.OpenBracketsCount--;
if (builder.WhereCondition == null)
builder.WhereCondition = string.Format("{0}{1}{2}",
addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty);
else
builder.WhereCondition = string.Format("{0} {1} {2}{3}{4}", builder.WhereCondition, and ? "AND" : "OR",
addBeginBrace ? "(" : string.Empty, condition, addEndBrace ? ")" : string.Empty);
}
return builder;
@@ -97,54 +109,43 @@ namespace DynamORM.Builders.Extensions
internal static T InternalWhere<T>(this T builder, DynamicColumn column) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
{
builder.VirtualMode = column.VirtualColumn;
bool prepend = false;
if (column.BeginBlock)
{
if (string.IsNullOrEmpty(builder.WhereCondition))
prepend = true;
else
builder.WhereCondition += " (";
}
bool virt = builder.VirtualMode;
if (column.VirtualColumn.HasValue)
builder.VirtualMode = column.VirtualColumn.Value;
// It's kind of uglu, but... well it works.
if (column.Or)
switch (column.Operator)
{
default:
case DynamicColumn.CompareOperator.Eq: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)) == column.Value)); break;
case DynamicColumn.CompareOperator.Not: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)) != column.Value)); break;
case DynamicColumn.CompareOperator.Like: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)).Like(column.Value))); break;
case DynamicColumn.CompareOperator.NotLike: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value))); break;
case DynamicColumn.CompareOperator.In: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)).In(column.Value))); break;
case DynamicColumn.CompareOperator.Lt: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)) < column.Value)); break;
case DynamicColumn.CompareOperator.Lte: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)) <= column.Value)); break;
case DynamicColumn.CompareOperator.Gt: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)) > column.Value)); break;
case DynamicColumn.CompareOperator.Gte: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)) >= column.Value)); break;
case DynamicColumn.CompareOperator.Between: builder.InternalWhere(x => x.Or(x(builder.FixObjectName(column.ColumnName)).Between(column.Value))); break;
case DynamicColumn.CompareOperator.Eq: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) == column.Value)); break;
case DynamicColumn.CompareOperator.Not: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) != column.Value)); break;
case DynamicColumn.CompareOperator.Like: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).Like(column.Value))); break;
case DynamicColumn.CompareOperator.NotLike: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value))); break;
case DynamicColumn.CompareOperator.In: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).In(column.Value))); break;
case DynamicColumn.CompareOperator.Lt: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) < column.Value)); break;
case DynamicColumn.CompareOperator.Lte: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) <= column.Value)); break;
case DynamicColumn.CompareOperator.Gt: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) > column.Value)); break;
case DynamicColumn.CompareOperator.Gte: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)) >= column.Value)); break;
case DynamicColumn.CompareOperator.Between: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x.Or(x(builder.FixObjectName(column.ColumnName)).Between(column.Value))); break;
}
else
switch (column.Operator)
{
default:
case DynamicColumn.CompareOperator.Eq: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)) == column.Value); break;
case DynamicColumn.CompareOperator.Not: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)) != column.Value); break;
case DynamicColumn.CompareOperator.Like: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)).Like(column.Value)); break;
case DynamicColumn.CompareOperator.NotLike: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value)); break;
case DynamicColumn.CompareOperator.In: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)).In(column.Value)); break;
case DynamicColumn.CompareOperator.Lt: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)) < column.Value); break;
case DynamicColumn.CompareOperator.Lte: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)) <= column.Value); break;
case DynamicColumn.CompareOperator.Gt: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)) > column.Value); break;
case DynamicColumn.CompareOperator.Gte: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)) >= column.Value); break;
case DynamicColumn.CompareOperator.Between: builder.InternalWhere(x => x(builder.FixObjectName(column.ColumnName)).Between(column.Value)); break;
case DynamicColumn.CompareOperator.Eq: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) == column.Value); break;
case DynamicColumn.CompareOperator.Not: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) != column.Value); break;
case DynamicColumn.CompareOperator.Like: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Like(column.Value)); break;
case DynamicColumn.CompareOperator.NotLike: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).NotLike(column.Value)); break;
case DynamicColumn.CompareOperator.In: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).In(column.Value)); break;
case DynamicColumn.CompareOperator.Lt: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) < column.Value); break;
case DynamicColumn.CompareOperator.Lte: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) <= column.Value); break;
case DynamicColumn.CompareOperator.Gt: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) > column.Value); break;
case DynamicColumn.CompareOperator.Gte: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)) >= column.Value); break;
case DynamicColumn.CompareOperator.Between: builder.InternalWhere(column.BeginBlock, column.EndBlock, x => x(builder.FixObjectName(column.ColumnName)).Between(column.Value)); break;
}
if (prepend)
builder.WhereCondition = string.Format("({0}", builder.WhereCondition);
if (column.EndBlock)
builder.WhereCondition += ")";
builder.VirtualMode = virt;
return builder;
}

View File

@@ -45,6 +45,9 @@ namespace DynamORM.Builders
/// <summary>Gets the tables used in this builder.</summary>
IDictionary<string, IParameter> Parameters { get; }
/// <summary>Gets or sets a value indicating whether add virtual parameters.</summary>
bool VirtualMode { get; set; }
/// <summary>Gets a value indicating whether database supports standard schema.</summary>
bool SupportSchema { get; }
@@ -60,6 +63,14 @@ namespace DynamORM.Builders
/// <remarks>This method must be override by derived classes.</remarks>
string CommandText();
/// <summary>Gets or sets the on create temporary parameter action.</summary>
/// <remarks>This is exposed to allow setting schema of column.</remarks>
Action<IParameter> OnCreateTemporaryParameter { get; set; }
/// <summary>Gets or sets the on create real parameter action.</summary>
/// <remarks>This is exposed to allow modification of parameter.</remarks>
Action<IParameter, IDbDataParameter> OnCreateParameter { get; set; }
/// <summary>Creates sub query.</summary>
/// <returns>Sub query builder.</returns>
IDynamicSelectQueryBuilder SubQuery();

View File

@@ -31,16 +31,23 @@ namespace DynamORM.Builders
/// <summary>Interface describing parameter info.</summary>
public interface IParameter
{
/// <summary>Gets the parameter position in command.</summary>
/// <remarks>Available after filling the command.</remarks>
int Ordinal { get; }
/// <summary>Gets the parameter temporary name.</summary>
string Name { get; }
/// <summary>Gets or sets the parameter value.</summary>
object Value { get; set; }
/// <summary>Gets or sets a value indicating whether name of temporary parameter is well known.</summary>
bool WellKnown { get; set; }
/// <summary>Gets or sets a value indicating whether this <see cref="Parameter"/> is virtual.</summary>
bool Virtual { get; set; }
/// <summary>Gets the parameter schema information.</summary>
DynamicSchemaColumn? Schema { get; }
/// <summary>Gets or sets the parameter schema information.</summary>
DynamicSchemaColumn? Schema { get; set; }
}
}

View File

@@ -63,9 +63,11 @@ namespace DynamORM.Builders.Implementation
public override string CommandText()
{
var info = Tables.Single();
return string.Format("DELETE FROM {0}{1} WHERE {2}",
return string.Format("DELETE FROM {0}{1}{2}{3}",
string.IsNullOrEmpty(info.Owner) ? string.Empty : string.Format("{0}.", Database.DecorateName(info.Owner)),
Database.DecorateName(info.Name), WhereCondition);
Database.DecorateName(info.Name),
string.IsNullOrEmpty(WhereCondition) ? string.Empty : " WHERE ",
WhereCondition);
}
#region Where

View File

@@ -45,13 +45,17 @@ namespace DynamORM.Builders.Implementation
internal abstract class DynamicQueryBuilder : IDynamicQueryBuilder
{
/// <summary>Empty interface to allow where query builder implementation use universal approach.</summary>
internal interface IQueryWithWhere { }
internal interface IQueryWithWhere
{
/// <summary>Gets or sets the where condition.</summary>
string WhereCondition { get; set; }
/// <summary>Gets or sets the amount of not closed brackets in where statement.</summary>
int OpenBracketsCount { get; set; }
}
private DynamicQueryBuilder _parent = null;
/// <summary>Gets or sets a value indicating whether add virtual.</summary>
internal bool VirtualMode { get; set; }
#region TableInfo
/// <summary>Table information.</summary>
@@ -137,34 +141,29 @@ namespace DynamORM.Builders.Implementation
/// <summary>Interface describing parameter info.</summary>
internal class Parameter : IParameter
{
/// <summary>Gets or sets the parameter position in command.</summary>
/// <remarks>Available after filling the command.</remarks>
public int Ordinal { get; internal set; }
/// <summary>Gets or sets the parameter temporary name.</summary>
public string Name { get; internal set; }
/// <summary>Gets or sets the parameter value.</summary>
public object Value { get; set; }
/// <summary>Gets or sets a value indicating whether name of temporary parameter is well known.</summary>
public bool WellKnown { get; set; }
/// <summary>Gets or sets a value indicating whether this <see cref="Parameter"/> is virtual.</summary>
public bool Virtual { get; set; }
/// <summary>Gets or sets the parameter schema information.</summary>
public DynamicSchemaColumn? Schema { get; internal set; }
public DynamicSchemaColumn? Schema { get; set; }
}
#endregion Parameter
internal string WhereCondition { get; set; }
/// <summary>Gets <see cref="DynamicDatabase"/> instance.</summary>
public DynamicDatabase Database { get; private set; }
/// <summary>Gets the tables used in this builder.</summary>
public IList<ITableInfo> Tables { get; private set; }
/// <summary>Gets the tables used in this builder.</summary>
public IDictionary<string, IParameter> Parameters { get; private set; }
/// <summary>Gets a value indicating whether database supports standard schema.</summary>
public bool SupportSchema { get; private set; }
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="DynamicQueryBuilder"/> class.
@@ -176,6 +175,9 @@ namespace DynamORM.Builders.Implementation
Tables = new List<ITableInfo>();
Parameters = new Dictionary<string, IParameter>();
WhereCondition = null;
OpenBracketsCount = 0;
Database = db;
SupportSchema = (db.Options & DynamicDatabaseOptions.SupportSchema) == DynamicDatabaseOptions.SupportSchema;
}
@@ -189,37 +191,85 @@ namespace DynamORM.Builders.Implementation
_parent = parent;
}
internal bool IsTableAlias(string name)
#endregion Constructor
#region IQueryWithWhere
/// <summary>Gets or sets the where condition.</summary>
public string WhereCondition { get; set; }
/// <summary>Gets or sets the amount of not closed brackets in where statement.</summary>
public int OpenBracketsCount { get; set; }
#endregion IQueryWithWhere
#region IDynamicQueryBuilder
/// <summary>Gets <see cref="DynamicDatabase"/> instance.</summary>
public DynamicDatabase Database { get; private set; }
/// <summary>Gets the tables used in this builder.</summary>
public IList<ITableInfo> Tables { get; private set; }
/// <summary>Gets the tables used in this builder.</summary>
public IDictionary<string, IParameter> Parameters { get; private set; }
/// <summary>Gets or sets a value indicating whether add virtual parameters.</summary>
public bool VirtualMode { get; set; }
/// <summary>Gets or sets the on create temporary parameter action.</summary>
/// <remarks>This is exposed to allow setting schema of column.</remarks>
public Action<IParameter> OnCreateTemporaryParameter { get; set; }
/// <summary>Gets or sets the on create real parameter action.</summary>
/// <remarks>This is exposed to allow modification of parameter.</remarks>
public Action<IParameter, IDbDataParameter> OnCreateParameter { get; set; }
/// <summary>Gets a value indicating whether database supports standard schema.</summary>
public bool SupportSchema { get; private set; }
/// <summary>
/// Generates the text this command will execute against the underlying database.
/// </summary>
/// <returns>The text to execute against the underlying database.</returns>
/// <remarks>This method must be override by derived classes.</remarks>
public abstract string CommandText();
/// <summary>Fill command with query.</summary>
/// <param name="command">Command to fill.</param>
/// <returns>Filled instance of <see cref="IDbCommand"/>.</returns>
public virtual IDbCommand FillCommand(IDbCommand command)
{
DynamicQueryBuilder builder = this;
while (builder != null)
// End not ended where statement
if (this is IQueryWithWhere)
{
if (builder.Tables.Any(t => t.Alias == name))
return true;
builder = builder._parent;
while (OpenBracketsCount > 0)
{
WhereCondition += ")";
OpenBracketsCount--;
}
}
return false;
return command.SetCommand(CommandText()
.FillStringWithVariables(s =>
{
return Parameters.TryGetValue(s).NullOr(p =>
{
IDbDataParameter param = (IDbDataParameter)command
.AddParameter(this, p.Schema, p.Value)
.Parameters[command.Parameters.Count - 1];
(p as Parameter).Ordinal = command.Parameters.Count - 1;
if (OnCreateParameter != null)
OnCreateParameter(p, param);
return param.ParameterName;
}, s);
}));
}
internal bool IsTable(string name, string owner)
{
DynamicQueryBuilder builder = this;
while (builder != null)
{
if ((string.IsNullOrEmpty(owner) && builder.Tables.Any(t => t.Name.ToLower() == name.ToLower())) ||
(!string.IsNullOrEmpty(owner) && builder.Tables.Any(t => t.Name.ToLower() == name.ToLower() &&
!string.IsNullOrEmpty(t.Owner) && t.Owner.ToLower() == owner.ToLower())))
return true;
builder = builder._parent;
}
return false;
}
#region SubQuery
/// <summary>Creates sub query.</summary>
/// <returns>Sub query builder.</returns>
@@ -242,71 +292,9 @@ namespace DynamORM.Builders.Implementation
return SubQuery().From(func);
}
/// <summary>
/// Generates the text this command will execute against the underlying database.
/// </summary>
/// <returns>The text to execute against the underlying database.</returns>
/// <remarks>This method must be override by derived classes.</remarks>
public abstract string CommandText();
#endregion SubQuery
/// <summary>Fill command with query.</summary>
/// <param name="command">Command to fill.</param>
/// <returns>Filled instance of <see cref="IDbCommand"/>.</returns>
public virtual IDbCommand FillCommand(IDbCommand command)
{
return command.SetCommand(CommandText()
.FillStringWithVariables(s =>
{
return Parameters.TryGetValue(s).NullOr(p =>
{
return ((IDbDataParameter)command
.AddParameter(this, p.Schema, p.Value)
.Parameters[command.Parameters.Count - 1])
.ParameterName;
}, s);
}));
}
internal DynamicSchemaColumn? GetColumnFromSchema(string colName, DynamicTypeMap mapper = null, string table = null)
{
// This is tricky and will not always work unfortunetly.
if (colName.ContainsAny(StringExtensions.InvalidMultipartMemberChars))
return null;
// First we need to get real column name and it's owner if exist.
var parts = colName.Split('.')
.Select(c => Database.StripName(c))
.ToArray();
var columnName = parts.Last();
// Get table name from mapper
string tableName = table;
if (string.IsNullOrEmpty(tableName))
{
tableName = (mapper != null && mapper.Table != null) ? mapper.Table.Name : string.Empty;
if (parts.Length > 1 && string.IsNullOrEmpty(tableName))
{
// OK, we have a multi part identifier, that's good, we can get table name
tableName = string.Join(".", parts.Take(parts.Length - 1));
}
}
// Try to get table info from cache
var tableInfo = !string.IsNullOrEmpty(tableName) ?
Tables.FirstOrDefault(x => !string.IsNullOrEmpty(x.Alias) && x.Alias.ToLower() == tableName) ??
Tables.FirstOrDefault(x => x.Name.ToLower() == tableName.ToLower()) ?? Tables.FirstOrDefault() :
this is DynamicModifyBuilder ? Tables.FirstOrDefault() : null;
// Try to get column from schema
if (tableInfo != null && tableInfo.Schema != null)
return tableInfo.Schema.TryGetNullable(columnName.ToLower());
// Well, we failed to find a column
return null;
}
#endregion IDynamicQueryBuilder
#region Parser
@@ -333,14 +321,14 @@ namespace DynamORM.Builders.Implementation
if (!nulls)
throw new ArgumentNullException("node", "Null nodes are not accepted.");
return Dispatch(node, pars, decorate);
return Dispatch(node, pars, decorate, columnSchema: columnSchema);
}
// Nodes that are strings are parametrized or not depending the "rawstr" flag...
if (node is string)
{
if (rawstr) return (string)node;
else return Dispatch(node, pars, decorate);
else return Dispatch(node, pars, decorate, columnSchema: columnSchema);
}
// If node is a delegate, parse it to create the logical tree...
@@ -689,23 +677,30 @@ namespace DynamORM.Builders.Implementation
protected virtual string ParseConstant(object node, IDictionary<string, IParameter> pars = null, DynamicSchemaColumn? columnSchema = null)
{
if (node == null) return ParseNull();
if (node == null && !VirtualMode)
return ParseNull();
if (pars != null)
{
bool wellKnownName = VirtualMode && node is String && ((String)node).StartsWith("[$") && ((String)node).EndsWith("]") && ((String)node).Length > 4;
// If we have a list of parameters to store it, let's parametrize it
var name = Guid.NewGuid().ToString();
var par = new Parameter()
{
Name = string.Format("[${0}]", name),
Value = node,
Name = wellKnownName ? ((String)node).Substring(2, ((String)node).Length - 3) : Guid.NewGuid().ToString(),
Value = wellKnownName ? null : node,
WellKnown = wellKnownName,
Virtual = VirtualMode,
Schema = columnSchema,
};
pars.Add(name, par);
// If we are adding parameter we inform external sources about this.
if (OnCreateTemporaryParameter != null)
OnCreateTemporaryParameter(par);
return par.Name;
pars.Add(par.Name, par);
return string.Format("[${0}]", par.Name);
}
return node.ToString(); // Last resort case
@@ -716,6 +711,42 @@ namespace DynamORM.Builders.Implementation
return "NULL"; // Override if needed
}
#endregion Parser
#region Helpers
internal bool IsTableAlias(string name)
{
DynamicQueryBuilder builder = this;
while (builder != null)
{
if (builder.Tables.Any(t => t.Alias == name))
return true;
builder = builder._parent;
}
return false;
}
internal bool IsTable(string name, string owner)
{
DynamicQueryBuilder builder = this;
while (builder != null)
{
if ((string.IsNullOrEmpty(owner) && builder.Tables.Any(t => t.Name.ToLower() == name.ToLower())) ||
(!string.IsNullOrEmpty(owner) && builder.Tables.Any(t => t.Name.ToLower() == name.ToLower() &&
!string.IsNullOrEmpty(t.Owner) && t.Owner.ToLower() == owner.ToLower())))
return true;
builder = builder._parent;
}
return false;
}
internal string FixObjectName(string main, bool onlyColumn = false)
{
if (main.IndexOf("(") > 0 && main.IndexOf(")") > 0)
@@ -739,6 +770,47 @@ namespace DynamORM.Builders.Implementation
return f;
}
#endregion Parser
internal DynamicSchemaColumn? GetColumnFromSchema(string colName, DynamicTypeMap mapper = null, string table = null)
{
// This is tricky and will not always work unfortunetly.
if (colName.ContainsAny(StringExtensions.InvalidMultipartMemberChars))
return null;
// First we need to get real column name and it's owner if exist.
var parts = colName.Split('.')
.Select(c => Database.StripName(c))
.ToArray();
var columnName = parts.Last();
// Get table name from mapper
string tableName = table;
if (string.IsNullOrEmpty(tableName))
{
tableName = (mapper != null && mapper.Table != null) ? mapper.Table.Name : string.Empty;
if (parts.Length > 1 && string.IsNullOrEmpty(tableName))
{
// OK, we have a multi part identifier, that's good, we can get table name
tableName = string.Join(".", parts.Take(parts.Length - 1));
}
}
// Try to get table info from cache
var tableInfo = !string.IsNullOrEmpty(tableName) ?
Tables.FirstOrDefault(x => !string.IsNullOrEmpty(x.Alias) && x.Alias.ToLower() == tableName) ??
Tables.FirstOrDefault(x => x.Name.ToLower() == tableName.ToLower()) ?? Tables.FirstOrDefault() :
this is DynamicModifyBuilder ? Tables.FirstOrDefault() : null;
// Try to get column from schema
if (tableInfo != null && tableInfo.Schema != null)
return tableInfo.Schema.TryGetNullable(columnName.ToLower());
// Well, we failed to find a column
return null;
}
#endregion Helpers
}
}

View File

@@ -244,7 +244,8 @@ namespace DynamORM.Builders.Implementation
/// <returns>This instance to permit chaining.</returns>
public virtual IDynamicSelectQueryBuilder From(params Func<dynamic, object>[] func)
{
if (func == null) throw new ArgumentNullException("Array of functions cannot be null.");
if (func == null)
throw new ArgumentNullException("Array of functions cannot be null.");
int index = -1;
foreach (var f in func)
@@ -266,6 +267,19 @@ namespace DynamORM.Builders.Implementation
tuple.Item2.Validated("Alias", canbeNull: true),
parts.Length == 2 ? Database.StripName(parts.First()).Validated("Owner", canbeNull: true) : null);
}
else if (result is Type)
{
Type type = (Type)result;
if (type.IsAnonymous())
throw new InvalidOperationException(string.Format("Cant assign anonymous type as a table ({0}). Parsing {1}", type.FullName, result));
var mapper = DynamicMapperCache.GetMapper(type);
if (mapper == null)
throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}). Parsing {1}", type.FullName, result));
tableInfo = new TableInfo(Database, type);
}
else if (result is DynamicParser.Node)
{
// Or if it resolves to a dynamic node
@@ -327,10 +341,13 @@ namespace DynamORM.Builders.Implementation
if (invoke.Arguments.Length == 1 && invoke.Arguments[0] is Type)
{
type = (Type)invoke.Arguments[0];
if (type.IsAnonymous())
throw new InvalidOperationException(string.Format("Cant assign anonymous type as a table ({0}). Parsing {1}", type.FullName, result));
var mapper = DynamicMapperCache.GetMapper(type);
if (mapper == null)
throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}).", type.FullName));
throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}). Parsing {1}", type.FullName, result));
main = mapper.Table == null || string.IsNullOrEmpty(mapper.Table.Name) ?
mapper.Type.Name : mapper.Table.Name;

View File

@@ -58,9 +58,11 @@ namespace DynamORM.Builders.Implementation
public override string CommandText()
{
var info = Tables.Single();
return string.Format("UPDATE {0}{1} SET {2} WHERE {3}",
return string.Format("UPDATE {0}{1} SET {2}{3}{4}",
string.IsNullOrEmpty(info.Owner) ? string.Empty : string.Format("{0}.", Database.DecorateName(info.Owner)),
Database.DecorateName(info.Name), _columns, WhereCondition);
Database.DecorateName(info.Name), _columns,
string.IsNullOrEmpty(WhereCondition) ? string.Empty : " WHERE ",
WhereCondition);
}
#region Update

View File

@@ -72,6 +72,7 @@
<Compile Include="DynamicTransaction.cs" />
<Compile Include="Helpers\CollectionComparer.cs" />
<Compile Include="Helpers\Dynamics\DynamicParser.cs" />
<Compile Include="Helpers\Dynamics\DynamicProxy.cs" />
<Compile Include="Helpers\IExtendedDisposable.cs" />
<Compile Include="Helpers\FrameworkTools.cs" />
<Compile Include="Helpers\StringExtensions.cs" />

View File

@@ -145,7 +145,7 @@ namespace DynamORM
public bool EndBlock { get; set; }
/// <summary>Gets or sets a value indicating whether set parameters for null values.</summary>
public bool VirtualColumn { get; set; }
public bool? VirtualColumn { get; set; }
/// <summary>Gets or sets schema representation of a column.</summary>
/// <remarks>Workaround to providers issues which sometimes pass wrong
@@ -370,7 +370,7 @@ namespace DynamORM
/// <summary>Sets the virtual column.</summary>
/// <param name="virt">Set virtual column value.</param>
/// <returns>Returns self.</returns>
public DynamicColumn SetVirtualColumn(bool virt)
public DynamicColumn SetVirtualColumn(bool? virt)
{
VirtualColumn = virt;
return this;

View File

@@ -33,6 +33,7 @@ using System.Data.Common;
using System.Linq;
using System.Text;
using DynamORM.Builders;
using DynamORM.Builders.Extensions;
using DynamORM.Builders.Implementation;
using DynamORM.Helpers;
using DynamORM.Mapper;
@@ -237,14 +238,14 @@ namespace DynamORM
#endregion Table
#region From
#region From/Insert/Update/Delete
/// <summary>
/// Adds to the 'From' clause the contents obtained by parsing the dynamic lambda expressions given. The supported
/// Adds to the <code>FROM</code> clause the contents obtained by parsing the dynamic lambda expressions given. The supported
/// formats are:
/// <para>- Resolve to a string: 'x => "Table AS Alias', where the alias part is optional.</para>
/// <para>- Resolve to an expression: 'x => x.Table.As( x.Alias )', where the alias part is optional.</para>
/// <para>- Generic expression: 'x => x( expression ).As( x.Alias )', where the alias part is mandatory. In this
/// <para>- Resolve to a string: <code>x => "owner.Table AS Alias"</code>, where the alias part is optional.</para>
/// <para>- Resolve to an expression: <code>x => x.owner.Table.As( x.Alias )</code>, where the alias part is optional.</para>
/// <para>- Generic expression: <code>x => x( expression ).As( x.Alias )</code>, where the alias part is mandatory. In this
/// case the alias is not annotated.</para>
/// </summary>
/// <param name="func">The specification.</param>
@@ -254,7 +255,87 @@ namespace DynamORM
return new DynamicSelectQueryBuilder(this).From(func);
}
#endregion From
/// <summary>Adds to the <code>FROM</code> clause using <see cref="Type"/>.</summary>
/// <typeparam name="T">Type which can be represented in database.</typeparam>
/// <returns>This instance to permit chaining.</returns>
public virtual IDynamicSelectQueryBuilder From<T>()
{
return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)));
}
/// <summary>
/// Adds to the <code>INSERT INTO</code> clause the contents obtained by parsing the dynamic lambda expressions given. The supported
/// formats are:
/// <para>- Resolve to a string: <code>x => "owner.Table"</code>.</para>
/// <para>- Resolve to a type: <code>x => typeof(SomeClass)</code>.</para>
/// <para>- Resolve to an expression: <code>x => x.owner.Table</code>.</para>
/// <para>- Generic expression: <code>x => x( expression )</code>. Expression can
/// be <see cref="string"/> or <see cref="Type"/>.</para>
/// </summary>
/// <param name="func">The specification.</param>
/// <returns>This instance to permit chaining.</returns>
public virtual IDynamicInsertQueryBuilder Insert(Func<dynamic, object> func)
{
return new DynamicInsertQueryBuilder(this).Table(func);
}
/// <summary>Adds to the <code>INSERT INTO</code> clause using <see cref="Type"/>.</summary>
/// <typeparam name="T">Type which can be represented in database.</typeparam>
/// <returns>This instance to permit chaining.</returns>
public virtual IDynamicInsertQueryBuilder Insert<T>()
{
return new DynamicInsertQueryBuilder(this).Table(typeof(T));
}
/// <summary>
/// Adds to the <code>UPDATE</code> clause the contents obtained by parsing the dynamic lambda expressions given. The supported
/// formats are:
/// <para>- Resolve to a string: <code>x => "owner.Table"</code>.</para>
/// <para>- Resolve to a type: <code>x => typeof(SomeClass)</code>.</para>
/// <para>- Resolve to an expression: <code>x => x.owner.Table</code>.</para>
/// <para>- Generic expression: <code>x => x( expression )</code>. Expression can
/// be <see cref="string"/> or <see cref="Type"/>.</para>
/// </summary>
/// <param name="func">The specification.</param>
/// <returns>This instance to permit chaining.</returns>
public virtual IDynamicUpdateQueryBuilder Update(Func<dynamic, object> func)
{
return new DynamicUpdateQueryBuilder(this).Table(func);
}
/// <summary>Adds to the <code>UPDATE</code> clause using <see cref="Type"/>.</summary>
/// <typeparam name="T">Type which can be represented in database.</typeparam>
/// <returns>This instance to permit chaining.</returns>
public virtual IDynamicUpdateQueryBuilder Update<T>()
{
return new DynamicUpdateQueryBuilder(this).Table(typeof(T));
}
/// <summary>
/// Adds to the <code>DELETE FROM</code> clause the contents obtained by parsing the dynamic lambda expressions given. The supported
/// formats are:
/// <para>- Resolve to a string: <code>x => "owner.Table"</code>.</para>
/// <para>- Resolve to a type: <code>x => typeof(SomeClass)</code>.</para>
/// <para>- Resolve to an expression: <code>x => x.owner.Table</code>.</para>
/// <para>- Generic expression: <code>x => x( expression )</code>. Expression can
/// be <see cref="string"/> or <see cref="Type"/>.</para>
/// </summary>
/// <param name="func">The specification.</param>
/// <returns>This instance to permit chaining.</returns>
public virtual IDynamicDeleteQueryBuilder Delete(Func<dynamic, object> func)
{
return new DynamicDeleteQueryBuilder(this).Table(func);
}
/// <summary>Adds to the <code>DELETE FROM</code> clause using <see cref="Type"/>.</summary>
/// <typeparam name="T">Type which can be represented in database.</typeparam>
/// <returns>This instance to permit chaining.</returns>
public virtual IDynamicDeleteQueryBuilder Delete<T>()
{
return new DynamicDeleteQueryBuilder(this).Table(typeof(T));
}
#endregion From/Insert/Update/Delete
#region Schema

View File

@@ -795,6 +795,40 @@ namespace DynamORM
#endregion Command extensions
#region Dynamic builders extensions
/// <summary>Turns an <see cref="IDynamicSelectQueryBuilder"/> to a Dynamic list of things.</summary>
/// <param name="b">Ready to execute builder.</param>
/// <returns>List of things.</returns>
public static List<dynamic> ToList(this IDynamicSelectQueryBuilder b)
{
return b.Execute().ToList();
}
/// <summary>Sets the on create temporary parameter action.</summary>
/// <typeparam name="T">Class implementing <see cref="IDynamicQueryBuilder"/> interface.</typeparam>
/// <param name="b">The builder on which set delegate.</param>
/// <param name="a">Action to invoke.</param>
/// <returns>Returns instance of builder on which action is set.</returns>
public static T CreateTemporaryParameterAction<T>(this T b, Action<IParameter> a) where T : IDynamicQueryBuilder
{
b.OnCreateTemporaryParameter = a;
return b;
}
/// <summary>Sets the on create real parameter action.</summary>
/// <typeparam name="T">Class implementing <see cref="IDynamicQueryBuilder"/> interface.</typeparam>
/// <param name="b">The builder on which set delegate.</param>
/// <param name="a">Action to invoke.</param>
/// <returns>Returns instance of builder on which action is set.</returns>
public static T CreateParameterAction<T>(this T b, Action<IParameter, IDbDataParameter> a) where T : IDynamicQueryBuilder
{
b.OnCreateParameter = a;
return b;
}
#endregion Dynamic builders extensions
#region Dynamic extensions
/// <summary>Turns an <see cref="IDataReader"/> to a Dynamic list of things.</summary>
@@ -810,14 +844,6 @@ namespace DynamORM
return result;
}
/// <summary>Turns an <see cref="IDynamicSelectQueryBuilder"/> to a Dynamic list of things.</summary>
/// <param name="b">Ready to execute builder.</param>
/// <returns>List of things.</returns>
public static List<dynamic> ToList(this IDynamicSelectQueryBuilder b)
{
return b.Execute().ToList();
}
/// <summary>Turns an <see cref="IDynamicSelectQueryBuilder"/> to a Dynamic list of things with specified type.</summary>
/// <typeparam name="T">Type of object to map on.</typeparam>
/// <param name="b">Ready to execute builder.</param>

View File

@@ -36,6 +36,7 @@ using DynamORM.Builders;
using DynamORM.Builders.Extensions;
using DynamORM.Builders.Implementation;
using DynamORM.Helpers;
using DynamORM.Helpers.Dynamics;
using DynamORM.Mapper;
namespace DynamORM
@@ -222,7 +223,7 @@ namespace DynamORM
{
get
{
return string.IsNullOrEmpty(OwnerName) ?
return string.IsNullOrEmpty(TableName) ? null : string.IsNullOrEmpty(OwnerName) ?
Database.DecorateName(TableName) :
string.Format("{0}.{1}", Database.DecorateName(OwnerName), Database.DecorateName(TableName));
}
@@ -412,8 +413,9 @@ namespace DynamORM
{
var builder = new DynamicSelectQueryBuilder(this.Database);
if (!string.IsNullOrEmpty(this.TableName))
builder.From(x => this.TableName);
var name = this.FullName;
if (!string.IsNullOrEmpty(name))
builder.From(x => name);
return builder;
}
@@ -532,9 +534,9 @@ namespace DynamORM
/// <summary>Create new <see cref="DynamicInsertQueryBuilder"/>.</summary>
/// <returns>New <see cref="DynamicInsertQueryBuilder"/> instance.</returns>
public IDynamicInsertQueryBuilder Insert()
public dynamic Insert()
{
return new DynamicInsertQueryBuilder(this.Database, this.TableName);
return new DynamicProxy<IDynamicInsertQueryBuilder>(new DynamicInsertQueryBuilder(this.Database, this.FullName));
}
/// <summary>Adds a record to the database. You can pass in an Anonymous object, an <see cref="ExpandoObject"/>,
@@ -555,9 +557,9 @@ namespace DynamORM
/// <summary>Create new <see cref="DynamicUpdateQueryBuilder"/>.</summary>
/// <returns>New <see cref="DynamicUpdateQueryBuilder"/> instance.</returns>
public IDynamicUpdateQueryBuilder Update()
public dynamic Update()
{
return new DynamicUpdateQueryBuilder(this.Database, this.TableName);
return new DynamicProxy<IDynamicUpdateQueryBuilder>(new DynamicUpdateQueryBuilder(this.Database, this.FullName));
}
/// <summary>Updates a record in the database. You can pass in an Anonymous object, an ExpandoObject,
@@ -593,9 +595,9 @@ namespace DynamORM
/// <summary>Create new <see cref="DynamicDeleteQueryBuilder"/>.</summary>
/// <returns>New <see cref="DynamicDeleteQueryBuilder"/> instance.</returns>
public IDynamicDeleteQueryBuilder Delete()
public dynamic Delete()
{
return new DynamicDeleteQueryBuilder(this.Database, this.TableName);
return new DynamicProxy<IDynamicDeleteQueryBuilder>(new DynamicDeleteQueryBuilder(this.Database, this.FullName));
}
/// <summary>Removes a record from the database. You can pass in an Anonymous object, an <see cref="ExpandoObject"/>,

View File

@@ -0,0 +1,270 @@
/*
* 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 System.Linq.Expressions;
using System.Reflection;
using DynamORM.Mapper;
namespace DynamORM.Helpers.Dynamics
{
/// <summary>Class that allows to use interfaces as dynamic objects.</summary>
/// <typeparam name="T">Type of class to proxy.</typeparam>
/// <remarks>This is temporary solution. Which allows to use builders as a dynamic type.</remarks>
public class DynamicProxy<T> : DynamicObject
{
private T _proxy;
private Type _type;
private Dictionary<string, DynamicPropertyInvoker> _properties;
private Dictionary<MethodInfo, Delegate> _methods;
/// <summary>
/// Initializes a new instance of the <see cref="DynamicProxy{T}" /> class.
/// </summary>
/// <param name="proxiedObject">The object to which proxy should be created.</param>
/// <exception cref="System.ArgumentNullException">The object to which proxy should be created is null.</exception>
public DynamicProxy(T proxiedObject)
{
if (proxiedObject == null)
throw new ArgumentNullException("proxiedObject");
_proxy = proxiedObject;
_type = typeof(T);
_properties = _type.GetProperties()
.ToDictionary(
k => k.Name,
v => new DynamicPropertyInvoker(v, null));
_methods = _type.GetMethods()
.Where(m => !m.IsStatic && !m.IsGenericMethod)
.ToDictionary(
k => k,
v =>
{
try
{
return Delegate.CreateDelegate(Expression.GetDelegateType(v.GetParameters().Select(t => t.ParameterType).Concat(new[] { v.ReflectedType }).ToArray()), _proxy, v.Name);
}
catch (ArgumentException)
{
return null;
}
});
}
/// <summary>Provides implementation for type conversion operations.
/// Classes derived from the <see cref="T:System.Dynamic.DynamicObject" />
/// class can override this method to specify dynamic behavior for
/// operations that convert an object from one type to another.</summary>
/// <param name="binder">Provides information about the conversion operation.
/// The binder.Type property provides the type to which the object must be
/// converted. For example, for the statement (String)sampleObject in C#
/// (CType(sampleObject, Type) in Visual Basic), where sampleObject is an
/// instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject" />
/// class, binder.Type returns the <see cref="T:System.String" /> type.
/// The binder.Explicit property provides information about the kind of
/// conversion that occurs. It returns true for explicit conversion and
/// false for implicit conversion.</param>
/// <param name="result">The result of the type conversion operation.</param>
/// <returns>Returns <c>true</c> if the operation is successful; otherwise, <c>false</c>.
/// If this method returns false, the run-time binder of the language determines the
/// behavior. (In most cases, a language-specific run-time exception is thrown).</returns>
public override bool TryConvert(ConvertBinder binder, out object result)
{
if (binder.Type == typeof(T))
{
result = _proxy;
return true;
}
if (_proxy != null &&
binder.Type.IsAssignableFrom(_proxy.GetType()))
{
result = _proxy;
return true;
}
return base.TryConvert(binder, out result);
}
/// <summary>Provides the implementation for operations that get member
/// values. Classes derived from the <see cref="T:System.Dynamic.DynamicObject" />
/// class can override this method to specify dynamic behavior for
/// operations such as getting a value for a property.</summary>
/// <param name="binder">Provides information about the object that
/// called the dynamic operation. The binder.Name property provides
/// the name of the member on which the dynamic operation is performed.
/// For example, for the Console.WriteLine(sampleObject.SampleProperty)
/// statement, where sampleObject is an instance of the class derived
/// from the <see cref="T:System.Dynamic.DynamicObject" /> class,
/// binder.Name returns "SampleProperty". The binder.IgnoreCase property
/// specifies whether the member name is case-sensitive.</param>
/// <param name="result">The result of the get operation. For example,
/// if the method is called for a property, you can assign the property
/// value to <paramref name="result" />.</param>
/// <returns>Returns <c>true</c> if the operation is successful; otherwise,
/// <c>false</c>. If this method returns false, the run-time binder of the
/// language determines the behavior. (In most cases, a run-time exception
/// is thrown).</returns>
public override bool TryGetMember(GetMemberBinder binder, out object result)
{
try
{
var prop = _properties.TryGetValue(binder.Name);
result = prop.NullOr(p => p.Get.NullOr(g => g(_proxy), null), null);
return prop != null && prop.Get != null;
}
catch (Exception ex)
{
throw new InvalidOperationException(string.Format("Cannot get member {0}", binder.Name), ex);
}
}
/// <summary>Provides the implementation for operations that set member
/// values. Classes derived from the <see cref="T:System.Dynamic.DynamicObject" />
/// class can override this method to specify dynamic behavior for operations
/// such as setting a value for a property.</summary>
/// <param name="binder">Provides information about the object that called
/// the dynamic operation. The binder.Name property provides the name of
/// the member to which the value is being assigned. For example, for the
/// statement sampleObject.SampleProperty = "Test", where sampleObject is
/// an instance of the class derived from the <see cref="T:System.Dynamic.DynamicObject" />
/// class, binder.Name returns "SampleProperty". The binder.IgnoreCase
/// property specifies whether the member name is case-sensitive.</param>
/// <param name="value">The value to set to the member. For example, for
/// sampleObject.SampleProperty = "Test", where sampleObject is an instance
/// of the class derived from the <see cref="T:System.Dynamic.DynamicObject" />
/// class, the <paramref name="value" /> is "Test".</param>
/// <returns>Returns <c>true</c> if the operation is successful; otherwise,
/// <c>false</c>. If this method returns false, the run-time binder of the
/// language determines the behavior. (In most cases, a language-specific
/// run-time exception is thrown).</returns>
public override bool TrySetMember(SetMemberBinder binder, object value)
{
try
{
var prop = _properties.TryGetValue(binder.Name);
if (prop != null && prop.Set != null)
{
prop.Set(_proxy, value);
return true;
}
return false;
}
catch (Exception ex)
{
throw new InvalidOperationException(string.Format("Cannot set member {0} to '{1}'", binder.Name, value), ex);
}
}
/// <summary>Provides the implementation for operations that invoke a member.
/// Classes derived from the <see cref="T:System.Dynamic.DynamicObject" />
/// class can override this method to specify dynamic behavior for
/// operations such as calling a method.</summary>
/// <param name="binder">Provides information about the dynamic operation.
/// The binder.Name property provides the name of the member on which the
/// dynamic operation is performed. For example, for the statement
/// sampleObject.SampleMethod(100), where sampleObject is an instance of
/// the class derived from the <see cref="T:System.Dynamic.DynamicObject" />
/// class, binder.Name returns "SampleMethod". The binder.IgnoreCase property
/// specifies whether the member name is case-sensitive.</param>
/// <param name="args">The arguments that are passed to the object member
/// during the invoke operation. For example, for the statement
/// sampleObject.SampleMethod(100), where sampleObject is derived from the
/// <see cref="T:System.Dynamic.DynamicObject" /> class,
/// <paramref name="args[0]" /> is equal to 100.</param>
/// <param name="result">The result of the member invocation.</param>
/// <returns>Returns <c>true</c> if the operation is successful; otherwise,
/// <c>false</c>. If this method returns false, the run-time binder of the
/// language determines the behavior. (In most cases, a language-specific
/// run-time exception is thrown).</returns>
public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
{
return TryInvokeMethod(binder.Name, out result, args) || base.TryInvokeMember(binder, args, out result);
}
private bool TryInvokeMethod(string name, out object result, object[] args)
{
result = null;
MethodInfo mi = _methods.Keys
.Where(m => m.Name == name)
.FirstOrDefault(m =>
CompareTypes(m.GetParameters().ToArray(),
args.Select(a => a.GetType()).ToArray()));
Delegate d = _methods.TryGetValue(mi);
if (d != null)
{
result = d.DynamicInvoke(CompleteArguments(mi.GetParameters().ToArray(), args));
if (d.Method.ReturnType == _type && result is T)
result = new DynamicProxy<T>((T)result);
return true;
}
else if (mi != null)
{
result = mi.Invoke(_proxy, CompleteArguments(mi.GetParameters().ToArray(), args));
if (mi.ReturnType == _type && result is T)
result = new DynamicProxy<T>((T)result);
return true;
}
return false;
}
private bool CompareTypes(ParameterInfo[] parameters, Type[] types)
{
if (parameters.Length < types.Length || parameters.Count(p => !p.IsOptional) > types.Length)
return false;
for (int i = 0; i < types.Length; i++)
if (types[i] != parameters[i].ParameterType && !parameters[i].ParameterType.IsAssignableFrom(types[i]))
return false;
return true;
}
private object[] CompleteArguments(ParameterInfo[] parameters, object[] arguments)
{
return arguments.Concat(parameters.Skip(arguments.Length).Select(p => p.DefaultValue)).ToArray();
}
}
}

View File

@@ -31,7 +31,7 @@ using System;
namespace DynamORM.Helpers
{
/// <summary>Class contains unclassified extensions.</summary>
public static class UnclassifiedExtensions
internal static class UnclassifiedExtensions
{
/// <summary>Easy way to use conditional value.</summary>
/// <remarks>Includes <see cref="DBNull.Value"/>.</remarks>

View File

@@ -35,6 +35,9 @@ namespace DynamORM.Mapper
/// <summary>Dynamic property invoker.</summary>
public class DynamicPropertyInvoker
{
/// <summary>Gets the type of property.</summary>
public Type Type { get; private set; }
/// <summary>Gets value getter.</summary>
public Func<object, object> Get { get; private set; }
@@ -56,6 +59,7 @@ namespace DynamORM.Mapper
public DynamicPropertyInvoker(PropertyInfo property, ColumnAttribute attr)
{
Name = property.Name;
Type = property.PropertyType;
var ignore = property.GetCustomAttributes(typeof(IgnoreAttribute), false);
@@ -63,8 +67,11 @@ namespace DynamORM.Mapper
Column = attr;
Get = CreateGetter(property);
Set = CreateSetter(property);
if (property.CanRead)
Get = CreateGetter(property);
if (property.CanWrite)
Set = CreateSetter(property);
}
private Func<object, object> CreateGetter(PropertyInfo property)