From 33cadaf05ba01b2292495be039641ae9f33af07a Mon Sep 17 00:00:00 2001 From: root Date: Thu, 26 Feb 2026 18:35:49 +0100 Subject: [PATCH] Add typed insert/update/delete builders and typed join variants --- AmalgamationTool/DynamORM.Amalgamation.cs | 449 ++++++++++++++---- .../Modify/TypedModifyExtensionsTests.cs | 40 ++ .../Select/TypedFluentBuilderTests.cs | 35 +- DynamORM/Builders/DynamicJoinType.cs | 17 + .../IDynamicTypedDeleteQueryBuilder.cs | 21 + .../IDynamicTypedInsertQueryBuilder.cs | 28 ++ .../IDynamicTypedSelectQueryBuilder.cs | 20 + .../IDynamicTypedUpdateQueryBuilder.cs | 33 ++ .../DynamicTypedDeleteQueryBuilder.cs | 57 +++ .../DynamicTypedInsertQueryBuilder.cs | 51 ++ .../DynamicTypedSelectQueryBuilder.cs | 102 ++++ .../DynamicTypedUpdateQueryBuilder.cs | 99 ++++ DynamORM/Builders/TypedJoinExtensions.cs | 94 ++-- DynamORM/DynamicDatabase.cs | 30 +- 14 files changed, 930 insertions(+), 146 deletions(-) create mode 100644 DynamORM/Builders/DynamicJoinType.cs create mode 100644 DynamORM/Builders/IDynamicTypedDeleteQueryBuilder.cs create mode 100644 DynamORM/Builders/IDynamicTypedInsertQueryBuilder.cs create mode 100644 DynamORM/Builders/IDynamicTypedUpdateQueryBuilder.cs create mode 100644 DynamORM/Builders/Implementation/DynamicTypedDeleteQueryBuilder.cs create mode 100644 DynamORM/Builders/Implementation/DynamicTypedInsertQueryBuilder.cs create mode 100644 DynamORM/Builders/Implementation/DynamicTypedUpdateQueryBuilder.cs diff --git a/AmalgamationTool/DynamORM.Amalgamation.cs b/AmalgamationTool/DynamORM.Amalgamation.cs index 528d5f2..767f058 100644 --- a/AmalgamationTool/DynamORM.Amalgamation.cs +++ b/AmalgamationTool/DynamORM.Amalgamation.cs @@ -2,31 +2,6 @@ * DynamORM - Dynamic Object-Relational Mapping library. * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) * All rights reserved. - * - * Some of methods in this code file is based on Kerosene ORM solution - * for parsing dynamic lambda expressions by Moisés Barba Cebeira - * - * 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 DynamORM.Builders.Extensions; @@ -2003,9 +1978,11 @@ namespace DynamORM /// Adds to the INSERT INTO clause using . /// Type which can be represented in database. /// This instance to permit chaining. - public virtual IDynamicInsertQueryBuilder Insert() + public virtual IDynamicTypedInsertQueryBuilder Insert() { - return new DynamicInsertQueryBuilder(this).Table(typeof(T)); + DynamicTypedInsertQueryBuilder builder = new DynamicTypedInsertQueryBuilder(this); + builder.Table(typeof(T)); + return builder; } /// Adds to the INSERT INTO clause using . /// Type which can be represented in database. @@ -2103,9 +2080,11 @@ namespace DynamORM /// Adds to the UPDATE clause using . /// Type which can be represented in database. /// This instance to permit chaining. - public virtual IDynamicUpdateQueryBuilder Update() + public virtual IDynamicTypedUpdateQueryBuilder Update() { - return new DynamicUpdateQueryBuilder(this).Table(typeof(T)); + DynamicTypedUpdateQueryBuilder builder = new DynamicTypedUpdateQueryBuilder(this); + builder.Table(typeof(T)); + return builder; } /// Adds to the UPDATE clause using . /// Type which can be represented in database. @@ -2312,9 +2291,11 @@ namespace DynamORM /// Adds to the DELETE FROM clause using . /// Type which can be represented in database. /// This instance to permit chaining. - public virtual IDynamicDeleteQueryBuilder Delete() + public virtual IDynamicTypedDeleteQueryBuilder Delete() { - return new DynamicDeleteQueryBuilder(this).Table(typeof(T)); + DynamicTypedDeleteQueryBuilder builder = new DynamicTypedDeleteQueryBuilder(this); + builder.Table(typeof(T)); + return builder; } /// Adds to the DELETE FROM clause using . /// Type which can be represented in database. @@ -6797,6 +6778,14 @@ namespace DynamORM } namespace Builders { + /// Typed join kind used by typed fluent builder APIs. + public enum DynamicJoinType + { + Inner = 0, + Left, + Right, + Full + } /// Dynamic delete query builder interface. /// This interface it publicly available. Implementation should be hidden. public interface IDynamicDeleteQueryBuilder : IDynamicQueryBuilder @@ -7164,10 +7153,55 @@ namespace DynamORM #endregion Top/Limit/Offset/Distinct } + /// Typed delete query builder for mapped entities. + /// Mapped entity type. + public interface IDynamicTypedDeleteQueryBuilder : IDynamicDeleteQueryBuilder + { + /// Add typed where predicate using mapped properties. + /// Predicate to parse. + /// Builder instance. + IDynamicTypedDeleteQueryBuilder Where(Expression> predicate); + } + /// Typed insert query builder for mapped entities. + /// Mapped entity type. + public interface IDynamicTypedInsertQueryBuilder : IDynamicInsertQueryBuilder + { + /// Add typed insert assignment using mapped property selector. + /// Property type. + /// Property selector. + /// Value to insert. + /// Builder instance. + IDynamicTypedInsertQueryBuilder Insert(Expression> selector, object value); + + /// Add values from mapped object. + /// Mapped object value. + /// Builder instance. + IDynamicTypedInsertQueryBuilder Insert(T value); + } /// Typed select query builder for mapped entities. /// Mapped entity type. public interface IDynamicTypedSelectQueryBuilder : IDynamicSelectQueryBuilder { + /// Add typed join to mapped table. + /// Joined mapped entity type. + /// Join ON predicate. + /// Optional alias for joined table. + /// Join type. + /// Builder instance. + IDynamicTypedSelectQueryBuilder Join(Expression> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner); + + /// Add INNER JOIN using typed ON predicate. + IDynamicTypedSelectQueryBuilder InnerJoin(Expression> on, string alias = null); + + /// Add LEFT JOIN using typed ON predicate. + IDynamicTypedSelectQueryBuilder LeftJoin(Expression> on, string alias = null); + + /// Add RIGHT JOIN using typed ON predicate. + IDynamicTypedSelectQueryBuilder RightJoin(Expression> on, string alias = null); + + /// Add FULL JOIN using typed ON predicate. + IDynamicTypedSelectQueryBuilder FullJoin(Expression> on, string alias = null); + /// Add typed where predicate using mapped properties. /// Predicate to parse. /// Builder instance. @@ -7199,6 +7233,27 @@ namespace DynamORM /// Builder instance. IDynamicTypedSelectQueryBuilder OrderBy(Expression> selector, params Expression>[] selectors); } + /// Typed update query builder for mapped entities. + /// Mapped entity type. + public interface IDynamicTypedUpdateQueryBuilder : IDynamicUpdateQueryBuilder + { + /// Add typed where predicate using mapped properties. + /// Predicate to parse. + /// Builder instance. + IDynamicTypedUpdateQueryBuilder Where(Expression> predicate); + + /// Add typed assignment using mapped property selector. + /// Property type. + /// Property selector. + /// Assigned value. + /// Builder instance. + IDynamicTypedUpdateQueryBuilder Set(Expression> selector, object value); + + /// Add update values from mapped object. + /// Mapped object value. + /// Builder instance. + IDynamicTypedUpdateQueryBuilder Values(T value); + } /// Dynamic update query builder interface. /// This interface it publicly available. Implementation should be hidden. public interface IDynamicUpdateQueryBuilder : IDynamicQueryBuilder @@ -7371,10 +7426,12 @@ namespace DynamORM return source; } } - /// Typed join helpers for typed select builder. + /// Compatibility and convenience extensions for typed joins. public static class TypedJoinExtensions { - /// Add typed join on mapped members. Supports simple equality join expression only. + /// + /// Legacy compatibility helper. Prefer . + /// public static IDynamicTypedSelectQueryBuilder JoinTyped( this IDynamicTypedSelectQueryBuilder builder, string alias, @@ -7384,60 +7441,49 @@ namespace DynamORM if (builder == null) throw new ArgumentNullException("builder"); if (on == null) throw new ArgumentNullException("on"); - var rightMapper = DynamicMapperCache.GetMapper(typeof(TRight)); - if (rightMapper == null) - throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}).", typeof(TRight).FullName)); + DynamicJoinType jt = DynamicJoinType.Inner; + string normalized = (joinType ?? string.Empty).Trim().ToUpperInvariant(); - string rightTable = string.IsNullOrEmpty(rightMapper.Table.NullOr(t => t.Name)) - ? typeof(TRight).Name - : rightMapper.Table.Name; + if (normalized == "LEFT JOIN" || normalized == "LEFT") + jt = DynamicJoinType.Left; + else if (normalized == "RIGHT JOIN" || normalized == "RIGHT") + jt = DynamicJoinType.Right; + else if (normalized == "FULL JOIN" || normalized == "FULL") + jt = DynamicJoinType.Full; - string rightOwner = rightMapper.Table.NullOr(t => t.Owner); - string rightAlias = string.IsNullOrEmpty(alias) ? "t" + (builder.Tables.Count + 1).ToString() : alias; - - var be = on.Body as BinaryExpression; - if (be == null || be.NodeType != ExpressionType.Equal) - throw new NotSupportedException("JoinTyped currently supports only equality join expressions."); - - string leftPrefix = builder.Tables.FirstOrDefault().NullOr(t => string.IsNullOrEmpty(t.Alias) ? t.Name : t.Alias, null); - if (string.IsNullOrEmpty(leftPrefix)) - throw new InvalidOperationException("JoinTyped requires source table to be present."); - - string leftExpr = ResolveMappedSide(be.Left, typeof(TLeft), leftPrefix, builder.Database); - string rightExpr = ResolveMappedSide(be.Right, typeof(TRight), rightAlias, builder.Database); - - string ownerPrefix = string.IsNullOrEmpty(rightOwner) ? string.Empty : builder.Database.DecorateName(rightOwner) + "."; - string rightTableExpr = ownerPrefix + builder.Database.DecorateName(rightTable); - string joinExpr = string.Format("{0} {1} AS {2} ON ({3} = {4})", joinType, rightTableExpr, rightAlias, leftExpr, rightExpr); - - builder.Join(x => joinExpr); - return builder; + return builder.Join(on, alias, jt); } - private static string ResolveMappedSide(Expression expression, Type modelType, string prefix, DynamicDatabase db) + /// Convenience typed INNER JOIN extension. + public static IDynamicTypedSelectQueryBuilder InnerJoinTyped( + this IDynamicTypedSelectQueryBuilder builder, + Expression> on, + string alias = null) { - expression = UnwrapConvert(expression); - var member = expression as MemberExpression; - if (member == null) - throw new NotSupportedException("Join side must be mapped member access."); - - var mapper = DynamicMapperCache.GetMapper(modelType); - string col = mapper.PropertyMap.TryGetValue(member.Member.Name) - ?? mapper.PropertyMap - .Where(x => string.Equals(x.Key, member.Member.Name, StringComparison.OrdinalIgnoreCase)) - .Select(x => x.Value) - .FirstOrDefault() - ?? member.Member.Name; - - return string.Format("{0}.{1}", prefix, db.DecorateName(col)); + return builder.Join(on, alias, DynamicJoinType.Inner); } - private static Expression UnwrapConvert(Expression expression) + /// Convenience typed LEFT JOIN extension. + public static IDynamicTypedSelectQueryBuilder LeftJoinTyped( + this IDynamicTypedSelectQueryBuilder builder, + Expression> on, + string alias = null) { - while (expression is UnaryExpression && - (((UnaryExpression)expression).NodeType == ExpressionType.Convert || - ((UnaryExpression)expression).NodeType == ExpressionType.ConvertChecked)) - expression = ((UnaryExpression)expression).Operand; - - return expression; + return builder.Join(on, alias, DynamicJoinType.Left); + } + /// Convenience typed RIGHT JOIN extension. + public static IDynamicTypedSelectQueryBuilder RightJoinTyped( + this IDynamicTypedSelectQueryBuilder builder, + Expression> on, + string alias = null) + { + return builder.Join(on, alias, DynamicJoinType.Right); + } + /// Convenience typed FULL JOIN extension. + public static IDynamicTypedSelectQueryBuilder FullJoinTyped( + this IDynamicTypedSelectQueryBuilder builder, + Expression> on, + string alias = null) + { + return builder.Join(on, alias, DynamicJoinType.Full); } } /// Typed helper extensions for update/insert/delete fluent APIs. @@ -10583,6 +10629,79 @@ namespace DynamORM } #endregion IExtendedDisposable } + /// Typed wrapper over with property-to-column translation. + /// Mapped entity type. + internal class DynamicTypedDeleteQueryBuilder : DynamicDeleteQueryBuilder, IDynamicTypedDeleteQueryBuilder + { + internal DynamicTypedDeleteQueryBuilder(DynamicDatabase db) + : base(db) + { + } + public IDynamicTypedDeleteQueryBuilder Where(Expression> predicate) + { + this.WhereTyped(predicate); + return this; + } + public new IDynamicTypedDeleteQueryBuilder Where(Func func) + { + base.Where(func); + return this; + } + public new IDynamicTypedDeleteQueryBuilder Where(DynamicColumn column) + { + base.Where(column); + return this; + } + public new IDynamicTypedDeleteQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value) + { + base.Where(column, op, value); + return this; + } + public new IDynamicTypedDeleteQueryBuilder Where(string column, object value) + { + base.Where(column, value); + return this; + } + public new IDynamicTypedDeleteQueryBuilder Where(object conditions, bool schema = false) + { + base.Where(conditions, schema); + return this; + } + } + /// Typed wrapper over with property-to-column translation. + /// Mapped entity type. + internal class DynamicTypedInsertQueryBuilder : DynamicInsertQueryBuilder, IDynamicTypedInsertQueryBuilder + { + internal DynamicTypedInsertQueryBuilder(DynamicDatabase db) + : base(db) + { + } + public IDynamicTypedInsertQueryBuilder Insert(Expression> selector, object value) + { + this.InsertTyped(selector, value); + return this; + } + public IDynamicTypedInsertQueryBuilder Insert(T value) + { + base.Insert(value); + return this; + } + public new IDynamicTypedInsertQueryBuilder Values(Func fn, params Func[] func) + { + base.Values(fn, func); + return this; + } + public new IDynamicTypedInsertQueryBuilder Insert(string column, object value) + { + base.Insert(column, value); + return this; + } + public new IDynamicTypedInsertQueryBuilder Insert(object o) + { + base.Insert(o); + return this; + } + } /// Typed wrapper over with property-to-column translation. /// Mapped entity type. internal class DynamicTypedSelectQueryBuilder : DynamicSelectQueryBuilder, IDynamicTypedSelectQueryBuilder @@ -10609,6 +10728,60 @@ namespace DynamORM return this; } + public IDynamicTypedSelectQueryBuilder Join(Expression> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner) + { + if (on == null) + throw new ArgumentNullException("on"); + + DynamicTypeMap rightMapper = DynamicMapperCache.GetMapper(typeof(TRight)); + if (rightMapper == null) + throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}).", typeof(TRight).FullName)); + + string rightTable = rightMapper.Table == null || string.IsNullOrEmpty(rightMapper.Table.Name) + ? typeof(TRight).Name + : rightMapper.Table.Name; + string rightOwner = rightMapper.Table == null ? null : rightMapper.Table.Owner; + string rightAlias = string.IsNullOrEmpty(alias) ? "t" + (Tables.Count + 1).ToString() : alias; + + BinaryExpression be = on.Body as BinaryExpression; + if (be == null || be.NodeType != ExpressionType.Equal) + throw new NotSupportedException("Typed join expression is currently limited to equality comparisons."); + + string leftPrefix = GetRootAliasOrTableName(); + if (string.IsNullOrEmpty(leftPrefix)) + throw new InvalidOperationException("Join requires source table to be present."); + + string leftExpr = ParseTypedJoinMember(be.Left, leftPrefix, _mapper); + string rightExpr = ParseTypedJoinMember(be.Right, rightAlias, rightMapper); + + string ownerPrefix = string.IsNullOrEmpty(rightOwner) ? string.Empty : Database.DecorateName(rightOwner) + "."; + string rightTableExpr = ownerPrefix + Database.DecorateName(rightTable); + string joinExpr = string.Format("{0} {1} AS {2} ON ({3} = {4})", + GetJoinKeyword(joinType), + rightTableExpr, + rightAlias, + leftExpr, + rightExpr); + + base.Join(x => joinExpr); + return this; + } + public IDynamicTypedSelectQueryBuilder InnerJoin(Expression> on, string alias = null) + { + return Join(on, alias, DynamicJoinType.Inner); + } + public IDynamicTypedSelectQueryBuilder LeftJoin(Expression> on, string alias = null) + { + return Join(on, alias, DynamicJoinType.Left); + } + public IDynamicTypedSelectQueryBuilder RightJoin(Expression> on, string alias = null) + { + return Join(on, alias, DynamicJoinType.Right); + } + public IDynamicTypedSelectQueryBuilder FullJoin(Expression> on, string alias = null) + { + return Join(on, alias, DynamicJoinType.Full); + } public new IDynamicTypedSelectQueryBuilder Join(params Func[] func) { base.Join(func); @@ -10960,6 +11133,46 @@ namespace DynamORM var parameter = member.Expression as ParameterExpression; return parameter != null && parameter.Type == typeof(T); } + private static string GetJoinKeyword(DynamicJoinType joinType) + { + switch (joinType) + { + case DynamicJoinType.Left: + return "LEFT JOIN"; + case DynamicJoinType.Right: + return "RIGHT JOIN"; + case DynamicJoinType.Full: + return "FULL JOIN"; + default: + return "INNER JOIN"; + } + } + private string ParseTypedJoinMember(Expression expression, string tablePrefix, DynamicTypeMap mapper) + { + expression = UnwrapConvert(expression); + MemberExpression member = expression as MemberExpression; + if (member == null || !(member.Expression is ParameterExpression) || ((ParameterExpression)member.Expression).Type != typeof(TModel)) + throw new NotSupportedException(string.Format("Typed join member access is not supported: {0}", expression)); + + string mappedColumn = null; + PropertyInfo property = member.Member as PropertyInfo; + if (property != null) + { + var attrs = property.GetCustomAttributes(typeof(ColumnAttribute), true); + ColumnAttribute colAttr = attrs == null ? null : attrs.Cast().FirstOrDefault(); + if (colAttr != null && !string.IsNullOrEmpty(colAttr.Name)) + mappedColumn = colAttr.Name; + } + if (string.IsNullOrEmpty(mappedColumn)) + mappedColumn = mapper.PropertyMap.TryGetValue(member.Member.Name) + ?? mapper.PropertyMap + .Where(x => string.Equals(x.Key, member.Member.Name, StringComparison.OrdinalIgnoreCase)) + .Select(x => x.Value) + .FirstOrDefault() + ?? member.Member.Name; + + return string.Format("{0}.{1}", tablePrefix, Database.DecorateName(mappedColumn)); + } private static Expression UnwrapConvert(Expression expression) { while (expression is UnaryExpression unary && @@ -11014,6 +11227,80 @@ namespace DynamORM return getter(); } } + /// Typed wrapper over with property-to-column translation. + /// Mapped entity type. + internal class DynamicTypedUpdateQueryBuilder : DynamicUpdateQueryBuilder, IDynamicTypedUpdateQueryBuilder + { + internal DynamicTypedUpdateQueryBuilder(DynamicDatabase db) + : base(db) + { + } + public IDynamicTypedUpdateQueryBuilder Where(Expression> predicate) + { + this.WhereTyped(predicate); + return this; + } + public IDynamicTypedUpdateQueryBuilder Set(Expression> selector, object value) + { + this.SetTyped(selector, value); + return this; + } + public IDynamicTypedUpdateQueryBuilder Values(T value) + { + base.Values(value); + return this; + } + public new IDynamicTypedUpdateQueryBuilder Update(string column, object value) + { + base.Update(column, value); + return this; + } + public new IDynamicTypedUpdateQueryBuilder Update(object conditions) + { + base.Update(conditions); + return this; + } + public new IDynamicTypedUpdateQueryBuilder Set(params Func[] func) + { + base.Set(func); + return this; + } + public new IDynamicTypedUpdateQueryBuilder Values(string column, object value) + { + base.Values(column, value); + return this; + } + public new IDynamicTypedUpdateQueryBuilder Values(object o) + { + base.Values(o); + return this; + } + public new IDynamicTypedUpdateQueryBuilder Where(Func func) + { + base.Where(func); + return this; + } + public new IDynamicTypedUpdateQueryBuilder Where(DynamicColumn column) + { + base.Where(column); + return this; + } + public new IDynamicTypedUpdateQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value) + { + base.Where(column, op, value); + return this; + } + public new IDynamicTypedUpdateQueryBuilder Where(string column, object value) + { + base.Where(column, value); + return this; + } + public new IDynamicTypedUpdateQueryBuilder Where(object conditions, bool schema = false) + { + base.Where(conditions, schema); + return this; + } + } /// Update query builder. internal class DynamicUpdateQueryBuilder : DynamicModifyBuilder, IDynamicUpdateQueryBuilder, DynamicQueryBuilder.IQueryWithWhere { diff --git a/DynamORM.Tests/Modify/TypedModifyExtensionsTests.cs b/DynamORM.Tests/Modify/TypedModifyExtensionsTests.cs index a5390fa..0fb768e 100644 --- a/DynamORM.Tests/Modify/TypedModifyExtensionsTests.cs +++ b/DynamORM.Tests/Modify/TypedModifyExtensionsTests.cs @@ -47,6 +47,21 @@ namespace DynamORM.Tests.Modify cmd.CommandText()); } + [Test] + public void TestTypedUpdateBuilderSetAndWhere() + { + var cmd = Database.Update() + .Set(u => u.Code, "778") + .Where(u => u.Id == 1 && u.Code == "1"); + + Assert.AreEqual( + string.Format("UPDATE \"sample_users\" SET \"code\" = [${0}] WHERE (\"id\" = [${1}]) AND (\"code\" = [${2}])", + cmd.Parameters.Keys.ElementAt(0), + cmd.Parameters.Keys.ElementAt(1), + cmd.Parameters.Keys.ElementAt(2)), + cmd.CommandText()); + } + [Test] public void TestTypedDeleteWhere() { @@ -58,6 +73,17 @@ namespace DynamORM.Tests.Modify cmd.CommandText()); } + [Test] + public void TestTypedDeleteBuilderWhere() + { + var cmd = Database.Delete() + .Where(u => u.Id == 3); + + Assert.AreEqual( + string.Format("DELETE FROM \"sample_users\" WHERE (\"id\" = [${0}])", cmd.Parameters.Keys.First()), + cmd.CommandText()); + } + [Test] public void TestTypedInsertColumns() { @@ -71,5 +97,19 @@ namespace DynamORM.Tests.Modify cmd.Parameters.Keys.ElementAt(1)), cmd.CommandText()); } + + [Test] + public void TestTypedInsertBuilderColumns() + { + var cmd = Database.Insert() + .Insert(u => u.Code, "901") + .Insert(u => u.First, "TypedB"); + + Assert.AreEqual( + string.Format("INSERT INTO \"sample_users\" (\"code\", \"first\") VALUES ([${0}], [${1}])", + cmd.Parameters.Keys.ElementAt(0), + cmd.Parameters.Keys.ElementAt(1)), + cmd.CommandText()); + } } } diff --git a/DynamORM.Tests/Select/TypedFluentBuilderTests.cs b/DynamORM.Tests/Select/TypedFluentBuilderTests.cs index 6528378..ded6e1f 100644 --- a/DynamORM.Tests/Select/TypedFluentBuilderTests.cs +++ b/DynamORM.Tests/Select/TypedFluentBuilderTests.cs @@ -100,11 +100,44 @@ namespace DynamORM.Tests.Select public void TestTypedJoin() { var cmd = Database.From("u") - .JoinTyped("x", (l, r) => l.Id == r.Id) + .Join((l, r) => l.Id == r.Id, "x") .SelectTyped(u => u.Id); Assert.AreEqual("SELECT u.\"id_user\" FROM \"sample_users\" AS u INNER JOIN \"sample_users\" AS x ON (u.\"id_user\" = x.\"id_user\")", cmd.CommandText()); } + + [Test] + public void TestTypedLeftJoin() + { + var cmd = Database.From("u") + .LeftJoin((l, r) => l.Id == r.Id, "x") + .SelectTyped(u => u.Id); + + Assert.AreEqual("SELECT u.\"id_user\" FROM \"sample_users\" AS u LEFT JOIN \"sample_users\" AS x ON (u.\"id_user\" = x.\"id_user\")", + cmd.CommandText()); + } + + [Test] + public void TestTypedRightJoin() + { + var cmd = Database.From("u") + .RightJoin((l, r) => l.Id == r.Id, "x") + .SelectTyped(u => u.Id); + + Assert.AreEqual("SELECT u.\"id_user\" FROM \"sample_users\" AS u RIGHT JOIN \"sample_users\" AS x ON (u.\"id_user\" = x.\"id_user\")", + cmd.CommandText()); + } + + [Test] + public void TestTypedFullJoin() + { + var cmd = Database.From("u") + .FullJoin((l, r) => l.Id == r.Id, "x") + .SelectTyped(u => u.Id); + + Assert.AreEqual("SELECT u.\"id_user\" FROM \"sample_users\" AS u FULL JOIN \"sample_users\" AS x ON (u.\"id_user\" = x.\"id_user\")", + cmd.CommandText()); + } } } diff --git a/DynamORM/Builders/DynamicJoinType.cs b/DynamORM/Builders/DynamicJoinType.cs new file mode 100644 index 0000000..a4da19f --- /dev/null +++ b/DynamORM/Builders/DynamicJoinType.cs @@ -0,0 +1,17 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + */ + +namespace DynamORM.Builders +{ + /// Typed join kind used by typed fluent builder APIs. + public enum DynamicJoinType + { + Inner = 0, + Left, + Right, + Full + } +} diff --git a/DynamORM/Builders/IDynamicTypedDeleteQueryBuilder.cs b/DynamORM/Builders/IDynamicTypedDeleteQueryBuilder.cs new file mode 100644 index 0000000..3ce0b86 --- /dev/null +++ b/DynamORM/Builders/IDynamicTypedDeleteQueryBuilder.cs @@ -0,0 +1,21 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + */ + +using System; +using System.Linq.Expressions; + +namespace DynamORM.Builders +{ + /// Typed delete query builder for mapped entities. + /// Mapped entity type. + public interface IDynamicTypedDeleteQueryBuilder : IDynamicDeleteQueryBuilder + { + /// Add typed where predicate using mapped properties. + /// Predicate to parse. + /// Builder instance. + IDynamicTypedDeleteQueryBuilder Where(Expression> predicate); + } +} diff --git a/DynamORM/Builders/IDynamicTypedInsertQueryBuilder.cs b/DynamORM/Builders/IDynamicTypedInsertQueryBuilder.cs new file mode 100644 index 0000000..2a1f8d4 --- /dev/null +++ b/DynamORM/Builders/IDynamicTypedInsertQueryBuilder.cs @@ -0,0 +1,28 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + */ + +using System; +using System.Linq.Expressions; + +namespace DynamORM.Builders +{ + /// Typed insert query builder for mapped entities. + /// Mapped entity type. + public interface IDynamicTypedInsertQueryBuilder : IDynamicInsertQueryBuilder + { + /// Add typed insert assignment using mapped property selector. + /// Property type. + /// Property selector. + /// Value to insert. + /// Builder instance. + IDynamicTypedInsertQueryBuilder Insert(Expression> selector, object value); + + /// Add values from mapped object. + /// Mapped object value. + /// Builder instance. + IDynamicTypedInsertQueryBuilder Insert(T value); + } +} diff --git a/DynamORM/Builders/IDynamicTypedSelectQueryBuilder.cs b/DynamORM/Builders/IDynamicTypedSelectQueryBuilder.cs index 2a7be96..0672ebe 100644 --- a/DynamORM/Builders/IDynamicTypedSelectQueryBuilder.cs +++ b/DynamORM/Builders/IDynamicTypedSelectQueryBuilder.cs @@ -35,6 +35,26 @@ namespace DynamORM.Builders /// Mapped entity type. public interface IDynamicTypedSelectQueryBuilder : IDynamicSelectQueryBuilder { + /// Add typed join to mapped table. + /// Joined mapped entity type. + /// Join ON predicate. + /// Optional alias for joined table. + /// Join type. + /// Builder instance. + IDynamicTypedSelectQueryBuilder Join(Expression> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner); + + /// Add INNER JOIN using typed ON predicate. + IDynamicTypedSelectQueryBuilder InnerJoin(Expression> on, string alias = null); + + /// Add LEFT JOIN using typed ON predicate. + IDynamicTypedSelectQueryBuilder LeftJoin(Expression> on, string alias = null); + + /// Add RIGHT JOIN using typed ON predicate. + IDynamicTypedSelectQueryBuilder RightJoin(Expression> on, string alias = null); + + /// Add FULL JOIN using typed ON predicate. + IDynamicTypedSelectQueryBuilder FullJoin(Expression> on, string alias = null); + /// Add typed where predicate using mapped properties. /// Predicate to parse. /// Builder instance. diff --git a/DynamORM/Builders/IDynamicTypedUpdateQueryBuilder.cs b/DynamORM/Builders/IDynamicTypedUpdateQueryBuilder.cs new file mode 100644 index 0000000..6285f12 --- /dev/null +++ b/DynamORM/Builders/IDynamicTypedUpdateQueryBuilder.cs @@ -0,0 +1,33 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + */ + +using System; +using System.Linq.Expressions; + +namespace DynamORM.Builders +{ + /// Typed update query builder for mapped entities. + /// Mapped entity type. + public interface IDynamicTypedUpdateQueryBuilder : IDynamicUpdateQueryBuilder + { + /// Add typed where predicate using mapped properties. + /// Predicate to parse. + /// Builder instance. + IDynamicTypedUpdateQueryBuilder Where(Expression> predicate); + + /// Add typed assignment using mapped property selector. + /// Property type. + /// Property selector. + /// Assigned value. + /// Builder instance. + IDynamicTypedUpdateQueryBuilder Set(Expression> selector, object value); + + /// Add update values from mapped object. + /// Mapped object value. + /// Builder instance. + IDynamicTypedUpdateQueryBuilder Values(T value); + } +} diff --git a/DynamORM/Builders/Implementation/DynamicTypedDeleteQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicTypedDeleteQueryBuilder.cs new file mode 100644 index 0000000..65412de --- /dev/null +++ b/DynamORM/Builders/Implementation/DynamicTypedDeleteQueryBuilder.cs @@ -0,0 +1,57 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + */ + +using System; +using System.Linq.Expressions; + +namespace DynamORM.Builders.Implementation +{ + /// Typed wrapper over with property-to-column translation. + /// Mapped entity type. + internal class DynamicTypedDeleteQueryBuilder : DynamicDeleteQueryBuilder, IDynamicTypedDeleteQueryBuilder + { + internal DynamicTypedDeleteQueryBuilder(DynamicDatabase db) + : base(db) + { + } + + public IDynamicTypedDeleteQueryBuilder Where(Expression> predicate) + { + this.WhereTyped(predicate); + return this; + } + + public new IDynamicTypedDeleteQueryBuilder Where(Func func) + { + base.Where(func); + return this; + } + + public new IDynamicTypedDeleteQueryBuilder Where(DynamicColumn column) + { + base.Where(column); + return this; + } + + public new IDynamicTypedDeleteQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value) + { + base.Where(column, op, value); + return this; + } + + public new IDynamicTypedDeleteQueryBuilder Where(string column, object value) + { + base.Where(column, value); + return this; + } + + public new IDynamicTypedDeleteQueryBuilder Where(object conditions, bool schema = false) + { + base.Where(conditions, schema); + return this; + } + } +} diff --git a/DynamORM/Builders/Implementation/DynamicTypedInsertQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicTypedInsertQueryBuilder.cs new file mode 100644 index 0000000..8b072bf --- /dev/null +++ b/DynamORM/Builders/Implementation/DynamicTypedInsertQueryBuilder.cs @@ -0,0 +1,51 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + */ + +using System; +using System.Linq.Expressions; + +namespace DynamORM.Builders.Implementation +{ + /// Typed wrapper over with property-to-column translation. + /// Mapped entity type. + internal class DynamicTypedInsertQueryBuilder : DynamicInsertQueryBuilder, IDynamicTypedInsertQueryBuilder + { + internal DynamicTypedInsertQueryBuilder(DynamicDatabase db) + : base(db) + { + } + + public IDynamicTypedInsertQueryBuilder Insert(Expression> selector, object value) + { + this.InsertTyped(selector, value); + return this; + } + + public IDynamicTypedInsertQueryBuilder Insert(T value) + { + base.Insert(value); + return this; + } + + public new IDynamicTypedInsertQueryBuilder Values(Func fn, params Func[] func) + { + base.Values(fn, func); + return this; + } + + public new IDynamicTypedInsertQueryBuilder Insert(string column, object value) + { + base.Insert(column, value); + return this; + } + + public new IDynamicTypedInsertQueryBuilder Insert(object o) + { + base.Insert(o); + return this; + } + } +} diff --git a/DynamORM/Builders/Implementation/DynamicTypedSelectQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicTypedSelectQueryBuilder.cs index b615a93..35cca6e 100644 --- a/DynamORM/Builders/Implementation/DynamicTypedSelectQueryBuilder.cs +++ b/DynamORM/Builders/Implementation/DynamicTypedSelectQueryBuilder.cs @@ -65,6 +65,65 @@ namespace DynamORM.Builders.Implementation return this; } + public IDynamicTypedSelectQueryBuilder Join(Expression> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner) + { + if (on == null) + throw new ArgumentNullException("on"); + + DynamicTypeMap rightMapper = DynamicMapperCache.GetMapper(typeof(TRight)); + if (rightMapper == null) + throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}).", typeof(TRight).FullName)); + + string rightTable = rightMapper.Table == null || string.IsNullOrEmpty(rightMapper.Table.Name) + ? typeof(TRight).Name + : rightMapper.Table.Name; + string rightOwner = rightMapper.Table == null ? null : rightMapper.Table.Owner; + string rightAlias = string.IsNullOrEmpty(alias) ? "t" + (Tables.Count + 1).ToString() : alias; + + BinaryExpression be = on.Body as BinaryExpression; + if (be == null || be.NodeType != ExpressionType.Equal) + throw new NotSupportedException("Typed join expression is currently limited to equality comparisons."); + + string leftPrefix = GetRootAliasOrTableName(); + if (string.IsNullOrEmpty(leftPrefix)) + throw new InvalidOperationException("Join requires source table to be present."); + + string leftExpr = ParseTypedJoinMember(be.Left, leftPrefix, _mapper); + string rightExpr = ParseTypedJoinMember(be.Right, rightAlias, rightMapper); + + string ownerPrefix = string.IsNullOrEmpty(rightOwner) ? string.Empty : Database.DecorateName(rightOwner) + "."; + string rightTableExpr = ownerPrefix + Database.DecorateName(rightTable); + string joinExpr = string.Format("{0} {1} AS {2} ON ({3} = {4})", + GetJoinKeyword(joinType), + rightTableExpr, + rightAlias, + leftExpr, + rightExpr); + + base.Join(x => joinExpr); + return this; + } + + public IDynamicTypedSelectQueryBuilder InnerJoin(Expression> on, string alias = null) + { + return Join(on, alias, DynamicJoinType.Inner); + } + + public IDynamicTypedSelectQueryBuilder LeftJoin(Expression> on, string alias = null) + { + return Join(on, alias, DynamicJoinType.Left); + } + + public IDynamicTypedSelectQueryBuilder RightJoin(Expression> on, string alias = null) + { + return Join(on, alias, DynamicJoinType.Right); + } + + public IDynamicTypedSelectQueryBuilder FullJoin(Expression> on, string alias = null) + { + return Join(on, alias, DynamicJoinType.Full); + } + public new IDynamicTypedSelectQueryBuilder Join(params Func[] func) { base.Join(func); @@ -454,6 +513,49 @@ namespace DynamORM.Builders.Implementation return parameter != null && parameter.Type == typeof(T); } + private static string GetJoinKeyword(DynamicJoinType joinType) + { + switch (joinType) + { + case DynamicJoinType.Left: + return "LEFT JOIN"; + case DynamicJoinType.Right: + return "RIGHT JOIN"; + case DynamicJoinType.Full: + return "FULL JOIN"; + default: + return "INNER JOIN"; + } + } + + private string ParseTypedJoinMember(Expression expression, string tablePrefix, DynamicTypeMap mapper) + { + expression = UnwrapConvert(expression); + MemberExpression member = expression as MemberExpression; + if (member == null || !(member.Expression is ParameterExpression) || ((ParameterExpression)member.Expression).Type != typeof(TModel)) + throw new NotSupportedException(string.Format("Typed join member access is not supported: {0}", expression)); + + string mappedColumn = null; + PropertyInfo property = member.Member as PropertyInfo; + if (property != null) + { + var attrs = property.GetCustomAttributes(typeof(ColumnAttribute), true); + ColumnAttribute colAttr = attrs == null ? null : attrs.Cast().FirstOrDefault(); + if (colAttr != null && !string.IsNullOrEmpty(colAttr.Name)) + mappedColumn = colAttr.Name; + } + + if (string.IsNullOrEmpty(mappedColumn)) + mappedColumn = mapper.PropertyMap.TryGetValue(member.Member.Name) + ?? mapper.PropertyMap + .Where(x => string.Equals(x.Key, member.Member.Name, StringComparison.OrdinalIgnoreCase)) + .Select(x => x.Value) + .FirstOrDefault() + ?? member.Member.Name; + + return string.Format("{0}.{1}", tablePrefix, Database.DecorateName(mappedColumn)); + } + private static Expression UnwrapConvert(Expression expression) { while (expression is UnaryExpression unary && diff --git a/DynamORM/Builders/Implementation/DynamicTypedUpdateQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicTypedUpdateQueryBuilder.cs new file mode 100644 index 0000000..a2227f6 --- /dev/null +++ b/DynamORM/Builders/Implementation/DynamicTypedUpdateQueryBuilder.cs @@ -0,0 +1,99 @@ +/* + * DynamORM - Dynamic Object-Relational Mapping library. + * Copyright (c) 2012-2026, Grzegorz Russek (grzegorz.russek@gmail.com) + * All rights reserved. + */ + +using System; +using System.Linq.Expressions; + +namespace DynamORM.Builders.Implementation +{ + /// Typed wrapper over with property-to-column translation. + /// Mapped entity type. + internal class DynamicTypedUpdateQueryBuilder : DynamicUpdateQueryBuilder, IDynamicTypedUpdateQueryBuilder + { + internal DynamicTypedUpdateQueryBuilder(DynamicDatabase db) + : base(db) + { + } + + public IDynamicTypedUpdateQueryBuilder Where(Expression> predicate) + { + this.WhereTyped(predicate); + return this; + } + + public IDynamicTypedUpdateQueryBuilder Set(Expression> selector, object value) + { + this.SetTyped(selector, value); + return this; + } + + public IDynamicTypedUpdateQueryBuilder Values(T value) + { + base.Values(value); + return this; + } + + public new IDynamicTypedUpdateQueryBuilder Update(string column, object value) + { + base.Update(column, value); + return this; + } + + public new IDynamicTypedUpdateQueryBuilder Update(object conditions) + { + base.Update(conditions); + return this; + } + + public new IDynamicTypedUpdateQueryBuilder Set(params Func[] func) + { + base.Set(func); + return this; + } + + public new IDynamicTypedUpdateQueryBuilder Values(string column, object value) + { + base.Values(column, value); + return this; + } + + public new IDynamicTypedUpdateQueryBuilder Values(object o) + { + base.Values(o); + return this; + } + + public new IDynamicTypedUpdateQueryBuilder Where(Func func) + { + base.Where(func); + return this; + } + + public new IDynamicTypedUpdateQueryBuilder Where(DynamicColumn column) + { + base.Where(column); + return this; + } + + public new IDynamicTypedUpdateQueryBuilder Where(string column, DynamicColumn.CompareOperator op, object value) + { + base.Where(column, op, value); + return this; + } + + public new IDynamicTypedUpdateQueryBuilder Where(string column, object value) + { + base.Where(column, value); + return this; + } + + public new IDynamicTypedUpdateQueryBuilder Where(object conditions, bool schema = false) + { + base.Where(conditions, schema); + return this; + } + } +} diff --git a/DynamORM/Builders/TypedJoinExtensions.cs b/DynamORM/Builders/TypedJoinExtensions.cs index 6ce4e27..c3b5d07 100644 --- a/DynamORM/Builders/TypedJoinExtensions.cs +++ b/DynamORM/Builders/TypedJoinExtensions.cs @@ -5,17 +5,16 @@ */ using System; -using System.Linq; using System.Linq.Expressions; -using DynamORM.Helpers; -using DynamORM.Mapper; namespace DynamORM.Builders { - /// Typed join helpers for typed select builder. + /// Compatibility and convenience extensions for typed joins. public static class TypedJoinExtensions { - /// Add typed join on mapped members. Supports simple equality join expression only. + /// + /// Legacy compatibility helper. Prefer . + /// public static IDynamicTypedSelectQueryBuilder JoinTyped( this IDynamicTypedSelectQueryBuilder builder, string alias, @@ -25,62 +24,53 @@ namespace DynamORM.Builders if (builder == null) throw new ArgumentNullException("builder"); if (on == null) throw new ArgumentNullException("on"); - var rightMapper = DynamicMapperCache.GetMapper(typeof(TRight)); - if (rightMapper == null) - throw new InvalidOperationException(string.Format("Cant assign unmapable type as a table ({0}).", typeof(TRight).FullName)); + DynamicJoinType jt = DynamicJoinType.Inner; + string normalized = (joinType ?? string.Empty).Trim().ToUpperInvariant(); - string rightTable = string.IsNullOrEmpty(rightMapper.Table.NullOr(t => t.Name)) - ? typeof(TRight).Name - : rightMapper.Table.Name; + if (normalized == "LEFT JOIN" || normalized == "LEFT") + jt = DynamicJoinType.Left; + else if (normalized == "RIGHT JOIN" || normalized == "RIGHT") + jt = DynamicJoinType.Right; + else if (normalized == "FULL JOIN" || normalized == "FULL") + jt = DynamicJoinType.Full; - string rightOwner = rightMapper.Table.NullOr(t => t.Owner); - string rightAlias = string.IsNullOrEmpty(alias) ? "t" + (builder.Tables.Count + 1).ToString() : alias; - - var be = on.Body as BinaryExpression; - if (be == null || be.NodeType != ExpressionType.Equal) - throw new NotSupportedException("JoinTyped currently supports only equality join expressions."); - - string leftPrefix = builder.Tables.FirstOrDefault().NullOr(t => string.IsNullOrEmpty(t.Alias) ? t.Name : t.Alias, null); - if (string.IsNullOrEmpty(leftPrefix)) - throw new InvalidOperationException("JoinTyped requires source table to be present."); - - string leftExpr = ResolveMappedSide(be.Left, typeof(TLeft), leftPrefix, builder.Database); - string rightExpr = ResolveMappedSide(be.Right, typeof(TRight), rightAlias, builder.Database); - - string ownerPrefix = string.IsNullOrEmpty(rightOwner) ? string.Empty : builder.Database.DecorateName(rightOwner) + "."; - string rightTableExpr = ownerPrefix + builder.Database.DecorateName(rightTable); - string joinExpr = string.Format("{0} {1} AS {2} ON ({3} = {4})", joinType, rightTableExpr, rightAlias, leftExpr, rightExpr); - - builder.Join(x => joinExpr); - return builder; + return builder.Join(on, alias, jt); } - private static string ResolveMappedSide(Expression expression, Type modelType, string prefix, DynamicDatabase db) + /// Convenience typed INNER JOIN extension. + public static IDynamicTypedSelectQueryBuilder InnerJoinTyped( + this IDynamicTypedSelectQueryBuilder builder, + Expression> on, + string alias = null) { - expression = UnwrapConvert(expression); - var member = expression as MemberExpression; - if (member == null) - throw new NotSupportedException("Join side must be mapped member access."); - - var mapper = DynamicMapperCache.GetMapper(modelType); - string col = mapper.PropertyMap.TryGetValue(member.Member.Name) - ?? mapper.PropertyMap - .Where(x => string.Equals(x.Key, member.Member.Name, StringComparison.OrdinalIgnoreCase)) - .Select(x => x.Value) - .FirstOrDefault() - ?? member.Member.Name; - - return string.Format("{0}.{1}", prefix, db.DecorateName(col)); + return builder.Join(on, alias, DynamicJoinType.Inner); } - private static Expression UnwrapConvert(Expression expression) + /// Convenience typed LEFT JOIN extension. + public static IDynamicTypedSelectQueryBuilder LeftJoinTyped( + this IDynamicTypedSelectQueryBuilder builder, + Expression> on, + string alias = null) { - while (expression is UnaryExpression && - (((UnaryExpression)expression).NodeType == ExpressionType.Convert || - ((UnaryExpression)expression).NodeType == ExpressionType.ConvertChecked)) - expression = ((UnaryExpression)expression).Operand; + return builder.Join(on, alias, DynamicJoinType.Left); + } - return expression; + /// Convenience typed RIGHT JOIN extension. + public static IDynamicTypedSelectQueryBuilder RightJoinTyped( + this IDynamicTypedSelectQueryBuilder builder, + Expression> on, + string alias = null) + { + return builder.Join(on, alias, DynamicJoinType.Right); + } + + /// Convenience typed FULL JOIN extension. + public static IDynamicTypedSelectQueryBuilder FullJoinTyped( + this IDynamicTypedSelectQueryBuilder builder, + Expression> on, + string alias = null) + { + return builder.Join(on, alias, DynamicJoinType.Full); } } } diff --git a/DynamORM/DynamicDatabase.cs b/DynamORM/DynamicDatabase.cs index 39695e1..a329123 100644 --- a/DynamORM/DynamicDatabase.cs +++ b/DynamORM/DynamicDatabase.cs @@ -503,10 +503,12 @@ namespace DynamORM /// 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)); - } + public virtual IDynamicTypedInsertQueryBuilder Insert() + { + DynamicTypedInsertQueryBuilder builder = new DynamicTypedInsertQueryBuilder(this); + builder.Table(typeof(T)); + return builder; + } /// Adds to the INSERT INTO clause using . /// Type which can be represented in database. @@ -610,10 +612,12 @@ namespace DynamORM /// 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)); - } + public virtual IDynamicTypedUpdateQueryBuilder Update() + { + DynamicTypedUpdateQueryBuilder builder = new DynamicTypedUpdateQueryBuilder(this); + builder.Table(typeof(T)); + return builder; + } /// Adds to the UPDATE clause using . /// Type which can be represented in database. @@ -831,10 +835,12 @@ namespace DynamORM /// 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)); - } + public virtual IDynamicTypedDeleteQueryBuilder Delete() + { + DynamicTypedDeleteQueryBuilder builder = new DynamicTypedDeleteQueryBuilder(this); + builder.Table(typeof(T)); + return builder; + } /// Adds to the DELETE FROM clause using . /// Type which can be represented in database.