Add typed join specification builder syntax

This commit is contained in:
root
2026-02-26 18:42:35 +01:00
parent f28549c775
commit 23a0336b7e
5 changed files with 175 additions and 2 deletions

View File

@@ -7190,6 +7190,12 @@ namespace DynamORM
/// <returns>Builder instance.</returns> /// <returns>Builder instance.</returns>
IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner); IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner);
/// <summary>Add typed join using join-spec builder syntax (<c>As()</c>, join kind and <c>On()</c>).</summary>
/// <typeparam name="TRight">Joined mapped entity type.</typeparam>
/// <param name="specification">Join specification builder callback.</param>
/// <returns>Builder instance.</returns>
IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Func<TypedJoinBuilder<T, TRight>, TypedJoinBuilder<T, TRight>> specification);
/// <summary>Add INNER JOIN using typed ON predicate.</summary> /// <summary>Add INNER JOIN using typed ON predicate.</summary>
IDynamicTypedSelectQueryBuilder<T> InnerJoin<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null); IDynamicTypedSelectQueryBuilder<T> InnerJoin<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null);
@@ -7402,6 +7408,64 @@ namespace DynamORM
return source; return source;
} }
} }
/// <summary>Typed join specification builder used by typed fluent select queries.</summary>
/// <typeparam name="TLeft">Left side mapped type.</typeparam>
/// <typeparam name="TRight">Right side mapped type.</typeparam>
public class TypedJoinBuilder<TLeft, TRight>
{
internal TypedJoinBuilder()
{
JoinType = DynamicJoinType.Inner;
}
/// <summary>Gets join alias.</summary>
public string Alias { get; private set; }
/// <summary>Gets join type.</summary>
public DynamicJoinType JoinType { get; private set; }
/// <summary>Gets ON predicate.</summary>
public Expression<Func<TLeft, TRight, bool>> OnPredicate { get; private set; }
/// <summary>Sets join alias.</summary>
public TypedJoinBuilder<TLeft, TRight> As(string alias)
{
Alias = alias;
return this;
}
/// <summary>Sets INNER JOIN.</summary>
public TypedJoinBuilder<TLeft, TRight> Inner()
{
JoinType = DynamicJoinType.Inner;
return this;
}
/// <summary>Sets LEFT JOIN.</summary>
public TypedJoinBuilder<TLeft, TRight> Left()
{
JoinType = DynamicJoinType.Left;
return this;
}
/// <summary>Sets RIGHT JOIN.</summary>
public TypedJoinBuilder<TLeft, TRight> Right()
{
JoinType = DynamicJoinType.Right;
return this;
}
/// <summary>Sets FULL JOIN.</summary>
public TypedJoinBuilder<TLeft, TRight> Full()
{
JoinType = DynamicJoinType.Full;
return this;
}
/// <summary>Sets ON predicate.</summary>
public TypedJoinBuilder<TLeft, TRight> On(Expression<Func<TLeft, TRight, bool>> predicate)
{
if (predicate == null)
throw new ArgumentNullException("predicate");
OnPredicate = predicate;
return this;
}
}
/// <summary>Compatibility and convenience extensions for typed joins.</summary> /// <summary>Compatibility and convenience extensions for typed joins.</summary>
public static class TypedJoinExtensions public static class TypedJoinExtensions
{ {
@@ -10742,6 +10806,19 @@ namespace DynamORM
base.Join(x => joinExpr); base.Join(x => joinExpr);
return this; return this;
} }
public IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Func<TypedJoinBuilder<T, TRight>, TypedJoinBuilder<T, TRight>> specification)
{
if (specification == null)
throw new ArgumentNullException("specification");
TypedJoinBuilder<T, TRight> spec = specification(new TypedJoinBuilder<T, TRight>());
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<T> InnerJoin<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null) public IDynamicTypedSelectQueryBuilder<T> InnerJoin<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null)
{ {
return Join(on, alias, DynamicJoinType.Inner); return Join(on, alias, DynamicJoinType.Inner);

View File

@@ -100,7 +100,7 @@ namespace DynamORM.Tests.Select
public void TestTypedJoin() public void TestTypedJoin()
{ {
var cmd = Database.From<TypedFluentUser>("u") var cmd = Database.From<TypedFluentUser>("u")
.Join<TypedFluentUser>((l, r) => l.Id == r.Id, "x") .Join<TypedFluentUser>(j => j.As("x").On((l, r) => l.Id == r.Id))
.Select(u => u.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\")", 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() public void TestTypedLeftJoin()
{ {
var cmd = Database.From<TypedFluentUser>("u") var cmd = Database.From<TypedFluentUser>("u")
.LeftJoin<TypedFluentUser>((l, r) => l.Id == r.Id, "x") .Join<TypedFluentUser>(j => j.Left().As("x").On((l, r) => l.Id == r.Id))
.Select(u => u.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\")", Assert.AreEqual("SELECT u.\"id_user\" FROM \"sample_users\" AS u LEFT JOIN \"sample_users\" AS x ON (u.\"id_user\" = x.\"id_user\")",

View File

@@ -43,6 +43,12 @@ namespace DynamORM.Builders
/// <returns>Builder instance.</returns> /// <returns>Builder instance.</returns>
IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner); IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null, DynamicJoinType joinType = DynamicJoinType.Inner);
/// <summary>Add typed join using join-spec builder syntax (<c>As()</c>, join kind and <c>On()</c>).</summary>
/// <typeparam name="TRight">Joined mapped entity type.</typeparam>
/// <param name="specification">Join specification builder callback.</param>
/// <returns>Builder instance.</returns>
IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Func<TypedJoinBuilder<T, TRight>, TypedJoinBuilder<T, TRight>> specification);
/// <summary>Add INNER JOIN using typed ON predicate.</summary> /// <summary>Add INNER JOIN using typed ON predicate.</summary>
IDynamicTypedSelectQueryBuilder<T> InnerJoin<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null); IDynamicTypedSelectQueryBuilder<T> InnerJoin<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null);

View File

@@ -104,6 +104,20 @@ namespace DynamORM.Builders.Implementation
return this; return this;
} }
public IDynamicTypedSelectQueryBuilder<T> Join<TRight>(Func<TypedJoinBuilder<T, TRight>, TypedJoinBuilder<T, TRight>> specification)
{
if (specification == null)
throw new ArgumentNullException("specification");
TypedJoinBuilder<T, TRight> spec = specification(new TypedJoinBuilder<T, TRight>());
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<T> InnerJoin<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null) public IDynamicTypedSelectQueryBuilder<T> InnerJoin<TRight>(Expression<Func<T, TRight, bool>> on, string alias = null)
{ {
return Join(on, alias, DynamicJoinType.Inner); return Join(on, alias, DynamicJoinType.Inner);

View File

@@ -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
{
/// <summary>Typed join specification builder used by typed fluent select queries.</summary>
/// <typeparam name="TLeft">Left side mapped type.</typeparam>
/// <typeparam name="TRight">Right side mapped type.</typeparam>
public class TypedJoinBuilder<TLeft, TRight>
{
internal TypedJoinBuilder()
{
JoinType = DynamicJoinType.Inner;
}
/// <summary>Gets join alias.</summary>
public string Alias { get; private set; }
/// <summary>Gets join type.</summary>
public DynamicJoinType JoinType { get; private set; }
/// <summary>Gets ON predicate.</summary>
public Expression<Func<TLeft, TRight, bool>> OnPredicate { get; private set; }
/// <summary>Sets join alias.</summary>
public TypedJoinBuilder<TLeft, TRight> As(string alias)
{
Alias = alias;
return this;
}
/// <summary>Sets INNER JOIN.</summary>
public TypedJoinBuilder<TLeft, TRight> Inner()
{
JoinType = DynamicJoinType.Inner;
return this;
}
/// <summary>Sets LEFT JOIN.</summary>
public TypedJoinBuilder<TLeft, TRight> Left()
{
JoinType = DynamicJoinType.Left;
return this;
}
/// <summary>Sets RIGHT JOIN.</summary>
public TypedJoinBuilder<TLeft, TRight> Right()
{
JoinType = DynamicJoinType.Right;
return this;
}
/// <summary>Sets FULL JOIN.</summary>
public TypedJoinBuilder<TLeft, TRight> Full()
{
JoinType = DynamicJoinType.Full;
return this;
}
/// <summary>Sets ON predicate.</summary>
public TypedJoinBuilder<TLeft, TRight> On(Expression<Func<TLeft, TRight, bool>> predicate)
{
if (predicate == null)
throw new ArgumentNullException("predicate");
OnPredicate = predicate;
return this;
}
}
}