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 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 CreateGetter(PropertyInfo property)