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