diff --git a/AmalgamationTool/DynamORM.Amalgamation.cs b/AmalgamationTool/DynamORM.Amalgamation.cs
index 049ade1..ccef0e0 100644
--- a/AmalgamationTool/DynamORM.Amalgamation.cs
+++ b/AmalgamationTool/DynamORM.Amalgamation.cs
@@ -7190,6 +7190,12 @@ namespace DynamORM
/// Builder instance.
IDynamicTypedSelectQueryBuilder Join(Expression> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner);
+ /// Add typed join using join-spec builder syntax (As(), join kind and On()).
+ /// Joined mapped entity type.
+ /// Join specification builder callback.
+ /// Builder instance.
+ IDynamicTypedSelectQueryBuilder Join(Func, TypedJoinBuilder> specification);
+
/// Add INNER JOIN using typed ON predicate.
IDynamicTypedSelectQueryBuilder InnerJoin(Expression> on, string alias = null);
@@ -7402,6 +7408,64 @@ namespace DynamORM
return source;
}
}
+ /// Typed join specification builder used by typed fluent select queries.
+ /// Left side mapped type.
+ /// Right side mapped type.
+ public class TypedJoinBuilder
+ {
+ internal TypedJoinBuilder()
+ {
+ JoinType = DynamicJoinType.Inner;
+ }
+ /// Gets join alias.
+ public string Alias { get; private set; }
+
+ /// Gets join type.
+ public DynamicJoinType JoinType { get; private set; }
+
+ /// Gets ON predicate.
+ public Expression> OnPredicate { get; private set; }
+
+ /// Sets join alias.
+ public TypedJoinBuilder As(string alias)
+ {
+ Alias = alias;
+ return this;
+ }
+ /// Sets INNER JOIN.
+ public TypedJoinBuilder Inner()
+ {
+ JoinType = DynamicJoinType.Inner;
+ return this;
+ }
+ /// Sets LEFT JOIN.
+ public TypedJoinBuilder Left()
+ {
+ JoinType = DynamicJoinType.Left;
+ return this;
+ }
+ /// Sets RIGHT JOIN.
+ public TypedJoinBuilder Right()
+ {
+ JoinType = DynamicJoinType.Right;
+ return this;
+ }
+ /// Sets FULL JOIN.
+ public TypedJoinBuilder Full()
+ {
+ JoinType = DynamicJoinType.Full;
+ return this;
+ }
+ /// Sets ON predicate.
+ public TypedJoinBuilder On(Expression> predicate)
+ {
+ if (predicate == null)
+ throw new ArgumentNullException("predicate");
+
+ OnPredicate = predicate;
+ return this;
+ }
+ }
/// Compatibility and convenience extensions for typed joins.
public static class TypedJoinExtensions
{
@@ -10742,6 +10806,19 @@ namespace DynamORM
base.Join(x => joinExpr);
return this;
}
+ public IDynamicTypedSelectQueryBuilder Join(Func, TypedJoinBuilder> specification)
+ {
+ if (specification == null)
+ throw new ArgumentNullException("specification");
+
+ TypedJoinBuilder spec = specification(new TypedJoinBuilder());
+ if (spec == null)
+ throw new ArgumentException("Join specification cannot resolve to null.", "specification");
+ if (spec.OnPredicate == null)
+ throw new ArgumentException("Join specification must define ON predicate.", "specification");
+
+ return Join(spec.OnPredicate, spec.Alias, spec.JoinType);
+ }
public IDynamicTypedSelectQueryBuilder InnerJoin(Expression> on, string alias = null)
{
return Join(on, alias, DynamicJoinType.Inner);
diff --git a/DynamORM.Tests/Select/TypedFluentBuilderTests.cs b/DynamORM.Tests/Select/TypedFluentBuilderTests.cs
index acbe033..c8c16b8 100644
--- a/DynamORM.Tests/Select/TypedFluentBuilderTests.cs
+++ b/DynamORM.Tests/Select/TypedFluentBuilderTests.cs
@@ -100,7 +100,7 @@ namespace DynamORM.Tests.Select
public void TestTypedJoin()
{
var cmd = Database.From("u")
- .Join((l, r) => l.Id == r.Id, "x")
+ .Join(j => j.As("x").On((l, r) => l.Id == r.Id))
.Select(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\")",
@@ -111,7 +111,7 @@ namespace DynamORM.Tests.Select
public void TestTypedLeftJoin()
{
var cmd = Database.From("u")
- .LeftJoin((l, r) => l.Id == r.Id, "x")
+ .Join(j => j.Left().As("x").On((l, r) => l.Id == r.Id))
.Select(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\")",
diff --git a/DynamORM/Builders/IDynamicTypedSelectQueryBuilder.cs b/DynamORM/Builders/IDynamicTypedSelectQueryBuilder.cs
index 0672ebe..bf4f420 100644
--- a/DynamORM/Builders/IDynamicTypedSelectQueryBuilder.cs
+++ b/DynamORM/Builders/IDynamicTypedSelectQueryBuilder.cs
@@ -43,6 +43,12 @@ namespace DynamORM.Builders
/// Builder instance.
IDynamicTypedSelectQueryBuilder Join(Expression> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner);
+ /// Add typed join using join-spec builder syntax (As(), join kind and On()).
+ /// Joined mapped entity type.
+ /// Join specification builder callback.
+ /// Builder instance.
+ IDynamicTypedSelectQueryBuilder Join(Func, TypedJoinBuilder> specification);
+
/// Add INNER JOIN using typed ON predicate.
IDynamicTypedSelectQueryBuilder InnerJoin(Expression> on, string alias = null);
diff --git a/DynamORM/Builders/Implementation/DynamicTypedSelectQueryBuilder.cs b/DynamORM/Builders/Implementation/DynamicTypedSelectQueryBuilder.cs
index 35cca6e..1e8053a 100644
--- a/DynamORM/Builders/Implementation/DynamicTypedSelectQueryBuilder.cs
+++ b/DynamORM/Builders/Implementation/DynamicTypedSelectQueryBuilder.cs
@@ -104,6 +104,20 @@ namespace DynamORM.Builders.Implementation
return this;
}
+ public IDynamicTypedSelectQueryBuilder Join(Func, TypedJoinBuilder> specification)
+ {
+ if (specification == null)
+ throw new ArgumentNullException("specification");
+
+ TypedJoinBuilder spec = specification(new TypedJoinBuilder());
+ if (spec == null)
+ throw new ArgumentException("Join specification cannot resolve to null.", "specification");
+ if (spec.OnPredicate == null)
+ throw new ArgumentException("Join specification must define ON predicate.", "specification");
+
+ return Join(spec.OnPredicate, spec.Alias, spec.JoinType);
+ }
+
public IDynamicTypedSelectQueryBuilder InnerJoin(Expression> on, string alias = null)
{
return Join(on, alias, DynamicJoinType.Inner);
diff --git a/DynamORM/Builders/TypedJoinBuilder.cs b/DynamORM/Builders/TypedJoinBuilder.cs
new file mode 100644
index 0000000..ebc5bb3
--- /dev/null
+++ b/DynamORM/Builders/TypedJoinBuilder.cs
@@ -0,0 +1,76 @@
+/*
+ * 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 join specification builder used by typed fluent select queries.
+ /// Left side mapped type.
+ /// Right side mapped type.
+ public class TypedJoinBuilder
+ {
+ internal TypedJoinBuilder()
+ {
+ JoinType = DynamicJoinType.Inner;
+ }
+
+ /// Gets join alias.
+ public string Alias { get; private set; }
+
+ /// Gets join type.
+ public DynamicJoinType JoinType { get; private set; }
+
+ /// Gets ON predicate.
+ public Expression> OnPredicate { get; private set; }
+
+ /// Sets join alias.
+ public TypedJoinBuilder As(string alias)
+ {
+ Alias = alias;
+ return this;
+ }
+
+ /// Sets INNER JOIN.
+ public TypedJoinBuilder Inner()
+ {
+ JoinType = DynamicJoinType.Inner;
+ return this;
+ }
+
+ /// Sets LEFT JOIN.
+ public TypedJoinBuilder Left()
+ {
+ JoinType = DynamicJoinType.Left;
+ return this;
+ }
+
+ /// Sets RIGHT JOIN.
+ public TypedJoinBuilder Right()
+ {
+ JoinType = DynamicJoinType.Right;
+ return this;
+ }
+
+ /// Sets FULL JOIN.
+ public TypedJoinBuilder Full()
+ {
+ JoinType = DynamicJoinType.Full;
+ return this;
+ }
+
+ /// Sets ON predicate.
+ public TypedJoinBuilder On(Expression> predicate)
+ {
+ if (predicate == null)
+ throw new ArgumentNullException("predicate");
+
+ OnPredicate = predicate;
+ return this;
+ }
+ }
+}