diff --git a/DynamORM.Tests/Select/LegacyParserTests.cs b/DynamORM.Tests/Select/LegacyParserTests.cs
index 70d0242..17bdcdb 100644
--- a/DynamORM.Tests/Select/LegacyParserTests.cs
+++ b/DynamORM.Tests/Select/LegacyParserTests.cs
@@ -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());
}
+ ///
+ /// Tests the where expression equal with brackets.
+ ///
+ [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());
+ }
+
+ ///
+ /// Tests the where expression equal with brackets.
+ ///
+ [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);
+ }
+
///
/// Tests the where expression not equal.
///
diff --git a/DynamORM.Tests/Select/ParserTests.cs b/DynamORM.Tests/Select/ParserTests.cs
index 9e18c4f..d3af0e8 100644
--- a/DynamORM.Tests/Select/ParserTests.cs
+++ b/DynamORM.Tests/Select/ParserTests.cs
@@ -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());
}
+ ///
+ /// Tests select escaped case.
+ ///
+ [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());
+ }
+
///
/// Tests select escaped case with sub query.
///
diff --git a/DynamORM/Builders/Extensions/DynamicModifyBuilderExtensions.cs b/DynamORM/Builders/Extensions/DynamicModifyBuilderExtensions.cs
index 24b784e..acc957c 100644
--- a/DynamORM/Builders/Extensions/DynamicModifyBuilderExtensions.cs
+++ b/DynamORM/Builders/Extensions/DynamicModifyBuilderExtensions.cs
@@ -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(this T builder, Func 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(this T builder, string tableName, Dictionary 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(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)
diff --git a/DynamORM/Builders/Extensions/DynamicWhereQueryExtensions.cs b/DynamORM/Builders/Extensions/DynamicWhereQueryExtensions.cs
index 1ed4975..89a0f46 100644
--- a/DynamORM/Builders/Extensions/DynamicWhereQueryExtensions.cs
+++ b/DynamORM/Builders/Extensions/DynamicWhereQueryExtensions.cs
@@ -43,6 +43,11 @@ namespace DynamORM.Builders.Extensions
#region Where
internal static T InternalWhere(this T builder, Func func) where T : DynamicQueryBuilder, DynamicQueryBuilder.IQueryWithWhere
+ {
+ return builder.InternalWhere(false, false, func);
+ }
+
+ internal static T InternalWhere(this T builder, bool addBeginBrace, bool addEndBrace, Func 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(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;
}
diff --git a/DynamORM/Builders/IDynamicQueryBuilder.cs b/DynamORM/Builders/IDynamicQueryBuilder.cs
index 19e031b..324bc91 100644
--- a/DynamORM/Builders/IDynamicQueryBuilder.cs
+++ b/DynamORM/Builders/IDynamicQueryBuilder.cs
@@ -45,6 +45,9 @@ namespace DynamORM.Builders
/// Gets the tables used in this builder.
IDictionary Parameters { get; }
+ /// Gets or sets a value indicating whether add virtual parameters.
+ bool VirtualMode { get; set; }
+
/// Gets a value indicating whether database supports standard schema.
bool SupportSchema { get; }
@@ -60,6 +63,14 @@ namespace DynamORM.Builders
/// This method must be override by derived classes.
string CommandText();
+ /// Gets or sets the on create temporary parameter action.
+ /// This is exposed to allow setting schema of column.
+ Action OnCreateTemporaryParameter { get; set; }
+
+ /// Gets or sets the on create real parameter action.
+ /// This is exposed to allow modification of parameter.
+ Action OnCreateParameter { get; set; }
+
/// Creates sub query.
/// Sub query builder.
IDynamicSelectQueryBuilder SubQuery();
diff --git a/DynamORM/Builders/IParameter.cs b/DynamORM/Builders/IParameter.cs
index 9e4a31a..1def8cf 100644
--- a/DynamORM/Builders/IParameter.cs
+++ b/DynamORM/Builders/IParameter.cs
@@ -31,16 +31,23 @@ namespace DynamORM.Builders
/// Interface describing parameter info.
public interface IParameter
{
+ /// Gets the parameter position in command.
+ /// Available after filling the command.
+ int Ordinal { get; }
+
/// Gets the parameter temporary name.
string Name { get; }
/// Gets or sets the parameter value.
object Value { get; set; }
+ /// Gets or sets a value indicating whether name of temporary parameter is well known.
+ bool WellKnown { get; set; }
+
/// Gets or sets a value indicating whether this is virtual.
bool Virtual { get; set; }
- /// Gets the parameter schema information.
- DynamicSchemaColumn? Schema { get; }
+ /// Gets or sets the parameter schema information.
+ DynamicSchemaColumn? Schema { get; set; }
}
}
\ No newline at end of file
diff --git a/DynamORM/Builders/Implementation/DynamicDeleteQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicDeleteQueryBuilder.cs
index 0c9f71e..881dfa6 100644
--- a/DynamORM/Builders/Implementation/DynamicDeleteQueryBuilder.cs
+++ b/DynamORM/Builders/Implementation/DynamicDeleteQueryBuilder.cs
@@ -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
diff --git a/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs
index a653f75..58ec668 100644
--- a/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs
+++ b/DynamORM/Builders/Implementation/DynamicQueryBuilder.cs
@@ -45,13 +45,17 @@ namespace DynamORM.Builders.Implementation
internal abstract class DynamicQueryBuilder : IDynamicQueryBuilder
{
/// Empty interface to allow where query builder implementation use universal approach.
- internal interface IQueryWithWhere { }
+ internal interface IQueryWithWhere
+ {
+ /// Gets or sets the where condition.
+ string WhereCondition { get; set; }
+
+ /// Gets or sets the amount of not closed brackets in where statement.
+ int OpenBracketsCount { get; set; }
+ }
private DynamicQueryBuilder _parent = null;
- /// Gets or sets a value indicating whether add virtual.
- internal bool VirtualMode { get; set; }
-
#region TableInfo
/// Table information.
@@ -137,34 +141,29 @@ namespace DynamORM.Builders.Implementation
/// Interface describing parameter info.
internal class Parameter : IParameter
{
+ /// Gets or sets the parameter position in command.
+ /// Available after filling the command.
+ public int Ordinal { get; internal set; }
+
/// Gets or sets the parameter temporary name.
public string Name { get; internal set; }
/// Gets or sets the parameter value.
public object Value { get; set; }
+ /// Gets or sets a value indicating whether name of temporary parameter is well known.
+ public bool WellKnown { get; set; }
+
/// Gets or sets a value indicating whether this is virtual.
public bool Virtual { get; set; }
/// Gets or sets the parameter schema information.
- public DynamicSchemaColumn? Schema { get; internal set; }
+ public DynamicSchemaColumn? Schema { get; set; }
}
#endregion Parameter
- internal string WhereCondition { get; set; }
-
- /// Gets instance.
- public DynamicDatabase Database { get; private set; }
-
- /// Gets the tables used in this builder.
- public IList Tables { get; private set; }
-
- /// Gets the tables used in this builder.
- public IDictionary Parameters { get; private set; }
-
- /// Gets a value indicating whether database supports standard schema.
- public bool SupportSchema { get; private set; }
+ #region Constructor
///
/// Initializes a new instance of the class.
@@ -176,6 +175,9 @@ namespace DynamORM.Builders.Implementation
Tables = new List();
Parameters = new Dictionary();
+ 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
+
+ /// Gets or sets the where condition.
+ public string WhereCondition { get; set; }
+
+ /// Gets or sets the amount of not closed brackets in where statement.
+ public int OpenBracketsCount { get; set; }
+
+ #endregion IQueryWithWhere
+
+ #region IDynamicQueryBuilder
+
+ /// Gets instance.
+ public DynamicDatabase Database { get; private set; }
+
+ /// Gets the tables used in this builder.
+ public IList Tables { get; private set; }
+
+ /// Gets the tables used in this builder.
+ public IDictionary Parameters { get; private set; }
+
+ /// Gets or sets a value indicating whether add virtual parameters.
+ public bool VirtualMode { get; set; }
+
+ /// Gets or sets the on create temporary parameter action.
+ /// This is exposed to allow setting schema of column.
+ public Action OnCreateTemporaryParameter { get; set; }
+
+ /// Gets or sets the on create real parameter action.
+ /// This is exposed to allow modification of parameter.
+ public Action OnCreateParameter { get; set; }
+
+ /// Gets a value indicating whether database supports standard schema.
+ public bool SupportSchema { get; private set; }
+
+ ///
+ /// Generates the text this command will execute against the underlying database.
+ ///
+ /// The text to execute against the underlying database.
+ /// This method must be override by derived classes.
+ public abstract string CommandText();
+
+ /// Fill command with query.
+ /// Command to fill.
+ /// Filled instance of .
+ 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
/// Creates sub query.
/// Sub query builder.
@@ -242,71 +292,9 @@ namespace DynamORM.Builders.Implementation
return SubQuery().From(func);
}
- ///
- /// Generates the text this command will execute against the underlying database.
- ///
- /// The text to execute against the underlying database.
- /// This method must be override by derived classes.
- public abstract string CommandText();
+ #endregion SubQuery
- /// Fill command with query.
- /// Command to fill.
- /// Filled instance of .
- 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 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
}
}
\ No newline at end of file
diff --git a/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs
index 74a994d..41a0603 100644
--- a/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs
+++ b/DynamORM/Builders/Implementation/DynamicSelectQueryBuilder.cs
@@ -244,7 +244,8 @@ namespace DynamORM.Builders.Implementation
/// This instance to permit chaining.
public virtual IDynamicSelectQueryBuilder From(params Func[] 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;
@@ -1235,4 +1252,4 @@ namespace DynamORM.Builders.Implementation
#endregion Helpers
}
-}
+}
\ No newline at end of file
diff --git a/DynamORM/Builders/Implementation/DynamicUpdateQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicUpdateQueryBuilder.cs
index 7b41d50..2c8c913 100644
--- a/DynamORM/Builders/Implementation/DynamicUpdateQueryBuilder.cs
+++ b/DynamORM/Builders/Implementation/DynamicUpdateQueryBuilder.cs
@@ -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
diff --git a/DynamORM/DynamORM.csproj b/DynamORM/DynamORM.csproj
index 24f374c..2946c7f 100644
--- a/DynamORM/DynamORM.csproj
+++ b/DynamORM/DynamORM.csproj
@@ -72,6 +72,7 @@
+
diff --git a/DynamORM/DynamicColumn.cs b/DynamORM/DynamicColumn.cs
index 885b0a8..794329f 100644
--- a/DynamORM/DynamicColumn.cs
+++ b/DynamORM/DynamicColumn.cs
@@ -145,7 +145,7 @@ namespace DynamORM
public bool EndBlock { get; set; }
/// Gets or sets a value indicating whether set parameters for null values.
- public bool VirtualColumn { get; set; }
+ public bool? VirtualColumn { get; set; }
/// Gets or sets schema representation of a column.
/// Workaround to providers issues which sometimes pass wrong
@@ -370,7 +370,7 @@ namespace DynamORM
/// Sets the virtual column.
/// Set virtual column value.
/// Returns self.
- public DynamicColumn SetVirtualColumn(bool virt)
+ public DynamicColumn SetVirtualColumn(bool? virt)
{
VirtualColumn = virt;
return this;
diff --git a/DynamORM/DynamicDatabase.cs b/DynamORM/DynamicDatabase.cs
index 56ad524..784467d 100644
--- a/DynamORM/DynamicDatabase.cs
+++ b/DynamORM/DynamicDatabase.cs
@@ -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
///
- /// Adds to the 'From' clause the contents obtained by parsing the dynamic lambda expressions given. The supported
+ /// Adds to the FROM clause the contents obtained by parsing the dynamic lambda expressions given. The supported
/// formats are:
- /// - Resolve to a string: 'x => "Table AS Alias', where the alias part is optional.
- /// - Resolve to an expression: 'x => x.Table.As( x.Alias )', where the alias part is optional.
- /// - Generic expression: 'x => x( expression ).As( x.Alias )', where the alias part is mandatory. In this
+ /// - Resolve to a string: x => "owner.Table AS Alias", where the alias part is optional.
+ /// - Resolve to an expression: x => x.owner.Table.As( x.Alias ), where the alias part is optional.
+ /// - Generic expression: x => x( expression ).As( x.Alias ), where the alias part is mandatory. In this
/// case the alias is not annotated.
///
/// The specification.
@@ -254,7 +255,87 @@ namespace DynamORM
return new DynamicSelectQueryBuilder(this).From(func);
}
- #endregion From
+ /// Adds to the FROM clause using .
+ /// Type which can be represented in database.
+ /// This instance to permit chaining.
+ public virtual IDynamicSelectQueryBuilder From()
+ {
+ return new DynamicSelectQueryBuilder(this).From(x => x(typeof(T)));
+ }
+
+ ///
+ /// Adds to the INSERT INTO clause the contents obtained by parsing the dynamic lambda expressions given. The supported
+ /// formats are:
+ /// - Resolve to a string: x => "owner.Table".
+ /// - Resolve to a type: x => typeof(SomeClass).
+ /// - Resolve to an expression: x => x.owner.Table.
+ /// - Generic expression: x => x( expression ). Expression can
+ /// be or .
+ ///
+ /// The specification.
+ /// This instance to permit chaining.
+ public virtual IDynamicInsertQueryBuilder Insert(Func func)
+ {
+ return new DynamicInsertQueryBuilder(this).Table(func);
+ }
+
+ /// Adds to the INSERT INTO clause using .
+ /// Type which can be represented in database.
+ /// This instance to permit chaining.
+ public virtual IDynamicInsertQueryBuilder Insert()
+ {
+ return new DynamicInsertQueryBuilder(this).Table(typeof(T));
+ }
+
+ ///
+ /// Adds to the UPDATE clause the contents obtained by parsing the dynamic lambda expressions given. The supported
+ /// formats are:
+ /// - Resolve to a string: x => "owner.Table".
+ /// - Resolve to a type: x => typeof(SomeClass).
+ /// - Resolve to an expression: x => x.owner.Table.
+ /// - Generic expression: x => x( expression ). Expression can
+ /// be or .
+ ///
+ /// The specification.
+ /// This instance to permit chaining.
+ public virtual IDynamicUpdateQueryBuilder Update(Func func)
+ {
+ return new DynamicUpdateQueryBuilder(this).Table(func);
+ }
+
+ /// Adds to the UPDATE clause using .
+ /// Type which can be represented in database.
+ /// This instance to permit chaining.
+ public virtual IDynamicUpdateQueryBuilder Update()
+ {
+ return new DynamicUpdateQueryBuilder(this).Table(typeof(T));
+ }
+
+ ///
+ /// Adds to the DELETE FROM clause the contents obtained by parsing the dynamic lambda expressions given. The supported
+ /// formats are:
+ /// - Resolve to a string: x => "owner.Table".
+ /// - Resolve to a type: x => typeof(SomeClass).
+ /// - Resolve to an expression: x => x.owner.Table.
+ /// - Generic expression: x => x( expression ). Expression can
+ /// be or .
+ ///
+ /// The specification.
+ /// This instance to permit chaining.
+ public virtual IDynamicDeleteQueryBuilder Delete(Func func)
+ {
+ return new DynamicDeleteQueryBuilder(this).Table(func);
+ }
+
+ /// Adds to the DELETE FROM clause using .
+ /// Type which can be represented in database.
+ /// This instance to permit chaining.
+ public virtual IDynamicDeleteQueryBuilder Delete()
+ {
+ return new DynamicDeleteQueryBuilder(this).Table(typeof(T));
+ }
+
+ #endregion From/Insert/Update/Delete
#region Schema
diff --git a/DynamORM/DynamicExtensions.cs b/DynamORM/DynamicExtensions.cs
index 15e4245..a653b0b 100644
--- a/DynamORM/DynamicExtensions.cs
+++ b/DynamORM/DynamicExtensions.cs
@@ -795,6 +795,40 @@ namespace DynamORM
#endregion Command extensions
+ #region Dynamic builders extensions
+
+ /// Turns an to a Dynamic list of things.
+ /// Ready to execute builder.
+ /// List of things.
+ public static List ToList(this IDynamicSelectQueryBuilder b)
+ {
+ return b.Execute().ToList();
+ }
+
+ /// Sets the on create temporary parameter action.
+ /// Class implementing interface.
+ /// The builder on which set delegate.
+ /// Action to invoke.
+ /// Returns instance of builder on which action is set.
+ public static T CreateTemporaryParameterAction(this T b, Action a) where T : IDynamicQueryBuilder
+ {
+ b.OnCreateTemporaryParameter = a;
+ return b;
+ }
+
+ /// Sets the on create real parameter action.
+ /// Class implementing interface.
+ /// The builder on which set delegate.
+ /// Action to invoke.
+ /// Returns instance of builder on which action is set.
+ public static T CreateParameterAction(this T b, Action a) where T : IDynamicQueryBuilder
+ {
+ b.OnCreateParameter = a;
+ return b;
+ }
+
+ #endregion Dynamic builders extensions
+
#region Dynamic extensions
/// Turns an to a Dynamic list of things.
@@ -810,14 +844,6 @@ namespace DynamORM
return result;
}
- /// Turns an to a Dynamic list of things.
- /// Ready to execute builder.
- /// List of things.
- public static List ToList(this IDynamicSelectQueryBuilder b)
- {
- return b.Execute().ToList();
- }
-
/// Turns an to a Dynamic list of things with specified type.
/// Type of object to map on.
/// Ready to execute builder.
diff --git a/DynamORM/DynamicTable.cs b/DynamORM/DynamicTable.cs
index d7fd75c..9930fa9 100644
--- a/DynamORM/DynamicTable.cs
+++ b/DynamORM/DynamicTable.cs
@@ -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
/// Create new .
/// New instance.
- public IDynamicInsertQueryBuilder Insert()
+ public dynamic Insert()
{
- return new DynamicInsertQueryBuilder(this.Database, this.TableName);
+ return new DynamicProxy(new DynamicInsertQueryBuilder(this.Database, this.FullName));
}
/// Adds a record to the database. You can pass in an Anonymous object, an ,
@@ -555,9 +557,9 @@ namespace DynamORM
/// Create new .
/// New instance.
- public IDynamicUpdateQueryBuilder Update()
+ public dynamic Update()
{
- return new DynamicUpdateQueryBuilder(this.Database, this.TableName);
+ return new DynamicProxy(new DynamicUpdateQueryBuilder(this.Database, this.FullName));
}
/// Updates a record in the database. You can pass in an Anonymous object, an ExpandoObject,
@@ -593,9 +595,9 @@ namespace DynamORM
/// Create new .
/// New instance.
- public IDynamicDeleteQueryBuilder Delete()
+ public dynamic Delete()
{
- return new DynamicDeleteQueryBuilder(this.Database, this.TableName);
+ return new DynamicProxy(new DynamicDeleteQueryBuilder(this.Database, this.FullName));
}
/// Removes a record from the database. You can pass in an Anonymous object, an ,
diff --git a/DynamORM/Helpers/Dynamics/DynamicProxy.cs b/DynamORM/Helpers/Dynamics/DynamicProxy.cs
new file mode 100644
index 0000000..07a23fd
--- /dev/null
+++ b/DynamORM/Helpers/Dynamics/DynamicProxy.cs
@@ -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
+{
+ /// Class that allows to use interfaces as dynamic objects.
+ /// Type of class to proxy.
+ /// This is temporary solution. Which allows to use builders as a dynamic type.
+ public class DynamicProxy : DynamicObject
+ {
+ private T _proxy;
+ private Type _type;
+ private Dictionary _properties;
+ private Dictionary _methods;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The object to which proxy should be created.
+ /// The object to which proxy should be created is null.
+ 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;
+ }
+ });
+ }
+
+ /// Provides implementation for type conversion operations.
+ /// Classes derived from the
+ /// class can override this method to specify dynamic behavior for
+ /// operations that convert an object from one type to another.
+ /// 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
+ /// class, binder.Type returns the 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.
+ /// The result of the type conversion operation.
+ /// Returns true if the operation is successful; otherwise, false.
+ /// 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).
+ 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);
+ }
+
+ /// Provides the implementation for operations that get member
+ /// values. Classes derived from the
+ /// class can override this method to specify dynamic behavior for
+ /// operations such as getting a value for a property.
+ /// 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 class,
+ /// binder.Name returns "SampleProperty". The binder.IgnoreCase property
+ /// specifies whether the member name is case-sensitive.
+ /// The result of the get operation. For example,
+ /// if the method is called for a property, you can assign the property
+ /// value to .
+ /// Returns true if the operation is successful; otherwise,
+ /// false. If this method returns false, the run-time binder of the
+ /// language determines the behavior. (In most cases, a run-time exception
+ /// is thrown).
+ 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);
+ }
+ }
+
+ /// Provides the implementation for operations that set member
+ /// values. Classes derived from the
+ /// class can override this method to specify dynamic behavior for operations
+ /// such as setting a value for a property.
+ /// 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
+ /// class, binder.Name returns "SampleProperty". The binder.IgnoreCase
+ /// property specifies whether the member name is case-sensitive.
+ /// The value to set to the member. For example, for
+ /// sampleObject.SampleProperty = "Test", where sampleObject is an instance
+ /// of the class derived from the
+ /// class, the is "Test".
+ /// Returns true if the operation is successful; otherwise,
+ /// false. 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).
+ 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);
+ }
+ }
+
+ /// Provides the implementation for operations that invoke a member.
+ /// Classes derived from the
+ /// class can override this method to specify dynamic behavior for
+ /// operations such as calling a method.
+ /// 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
+ /// class, binder.Name returns "SampleMethod". The binder.IgnoreCase property
+ /// specifies whether the member name is case-sensitive.
+ /// 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
+ /// class,
+ /// is equal to 100.
+ /// The result of the member invocation.
+ /// Returns true if the operation is successful; otherwise,
+ /// false. 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).
+ 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)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)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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/DynamORM/Helpers/UnclassifiedExtensions.cs b/DynamORM/Helpers/UnclassifiedExtensions.cs
index a271ff7..8540fd2 100644
--- a/DynamORM/Helpers/UnclassifiedExtensions.cs
+++ b/DynamORM/Helpers/UnclassifiedExtensions.cs
@@ -31,7 +31,7 @@ using System;
namespace DynamORM.Helpers
{
/// Class contains unclassified extensions.
- public static class UnclassifiedExtensions
+ internal static class UnclassifiedExtensions
{
/// Easy way to use conditional value.
/// Includes .
diff --git a/DynamORM/Mapper/DynamicPropertyInvoker.cs b/DynamORM/Mapper/DynamicPropertyInvoker.cs
index 7e44ccc..13fd773 100644
--- a/DynamORM/Mapper/DynamicPropertyInvoker.cs
+++ b/DynamORM/Mapper/DynamicPropertyInvoker.cs
@@ -35,6 +35,9 @@ namespace DynamORM.Mapper
/// Dynamic property invoker.
public class DynamicPropertyInvoker
{
+ /// Gets the type of property.
+ public Type Type { get; private set; }
+
/// Gets value getter.
public Func