diff --git a/src/QueryBuilder/ExpandBuilder.cs b/src/QueryBuilder/ExpandBuilder.cs
index 4b37e15..6b5d98e 100644
--- a/src/QueryBuilder/ExpandBuilder.cs
+++ b/src/QueryBuilder/ExpandBuilder.cs
@@ -1,4 +1,4 @@
-namespace DataverseQuery
+namespace DataverseQuery.QueryBuilder
{
public sealed class ExpandBuilder
{
@@ -18,4 +18,4 @@ public ExpandBuilder(string relationshipName, Type targetType, IQueryBuilder bui
IsCollection = isCollection;
}
}
-}
+}
\ No newline at end of file
diff --git a/src/QueryBuilder/Extensions/WhereGroupBuilderExtensions.cs b/src/QueryBuilder/Extensions/WhereGroupBuilderExtensions.cs
new file mode 100644
index 0000000..8559717
--- /dev/null
+++ b/src/QueryBuilder/Extensions/WhereGroupBuilderExtensions.cs
@@ -0,0 +1,167 @@
+using System.Linq.Expressions;
+using Microsoft.Xrm.Sdk;
+using Microsoft.Xrm.Sdk.Query;
+
+namespace DataverseQuery.QueryBuilder.Extensions
+{
+ ///
+ /// Extension methods for WhereGroupBuilder to provide common filter patterns.
+ ///
+ public static class WhereGroupBuilderExtensions
+ {
+ ///
+ /// Adds an equality condition.
+ ///
+ /// The entity type.
+ /// The value type of the property.
+ /// The WhereGroupBuilder instance.
+ /// The lambda expression selecting the field.
+ /// The value to compare against.
+ /// This WhereGroupBuilder instance for method chaining.
+ /// Thrown when builder or fieldSelector is null.
+ public static WhereGroupBuilder WhereEqual(
+ this WhereGroupBuilder builder,
+ Expression> fieldSelector,
+ TValue value)
+ where TEntity : Entity
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ return builder.Where(fieldSelector, ConditionOperator.Equal, value);
+ }
+
+ ///
+ /// Adds a 'like' condition.
+ ///
+ /// The entity type.
+ /// The WhereGroupBuilder instance.
+ /// The lambda expression selecting the field.
+ /// The pattern to match (supports % wildcards).
+ /// This WhereGroupBuilder instance for method chaining.
+ /// Thrown when builder or fieldSelector is null.
+ public static WhereGroupBuilder WhereLike(
+ this WhereGroupBuilder builder,
+ Expression> fieldSelector,
+ string pattern)
+ where TEntity : Entity
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ return builder.Where(fieldSelector, ConditionOperator.Like, pattern);
+ }
+
+ ///
+ /// Adds an 'in' condition.
+ ///
+ /// The entity type.
+ /// The value type of the property.
+ /// The WhereGroupBuilder instance.
+ /// The lambda expression selecting the field.
+ /// The values to check against.
+ /// This WhereGroupBuilder instance for method chaining.
+ /// Thrown when builder or fieldSelector is null.
+ public static WhereGroupBuilder WhereIn(
+ this WhereGroupBuilder builder,
+ Expression> fieldSelector,
+ params TValue[] values)
+ where TEntity : Entity
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ return builder.Where(fieldSelector, ConditionOperator.In, values);
+ }
+
+ ///
+ /// Adds a 'not equal' condition.
+ ///
+ /// The entity type.
+ /// The value type of the property.
+ /// The WhereGroupBuilder instance.
+ /// The lambda expression selecting the field.
+ /// The value to compare against.
+ /// This WhereGroupBuilder instance for method chaining.
+ /// Thrown when builder or fieldSelector is null.
+ public static WhereGroupBuilder WhereNotEqual(
+ this WhereGroupBuilder builder,
+ Expression> fieldSelector,
+ TValue value)
+ where TEntity : Entity
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ return builder.Where(fieldSelector, ConditionOperator.NotEqual, value);
+ }
+
+ ///
+ /// Adds a 'greater than' condition.
+ ///
+ /// The entity type.
+ /// The value type of the property.
+ /// The WhereGroupBuilder instance.
+ /// The lambda expression selecting the field.
+ /// The value to compare against.
+ /// This WhereGroupBuilder instance for method chaining.
+ /// Thrown when builder or fieldSelector is null.
+ public static WhereGroupBuilder WhereGreaterThan(
+ this WhereGroupBuilder builder,
+ Expression> fieldSelector,
+ TValue value)
+ where TEntity : Entity
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ return builder.Where(fieldSelector, ConditionOperator.GreaterThan, value);
+ }
+
+ ///
+ /// Adds a 'less than' condition.
+ ///
+ /// The entity type.
+ /// The value type of the property.
+ /// The WhereGroupBuilder instance.
+ /// The lambda expression selecting the field.
+ /// The value to compare against.
+ /// This WhereGroupBuilder instance for method chaining.
+ /// Thrown when builder or fieldSelector is null.
+ public static WhereGroupBuilder WhereLessThan(
+ this WhereGroupBuilder builder,
+ Expression> fieldSelector,
+ TValue value)
+ where TEntity : Entity
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ return builder.Where(fieldSelector, ConditionOperator.LessThan, value);
+ }
+
+ ///
+ /// Adds a 'null' condition.
+ ///
+ /// The entity type.
+ /// The value type of the property.
+ /// The WhereGroupBuilder instance.
+ /// The lambda expression selecting the field.
+ /// This WhereGroupBuilder instance for method chaining.
+ /// Thrown when builder or fieldSelector is null.
+ public static WhereGroupBuilder WhereNull(
+ this WhereGroupBuilder builder,
+ Expression> fieldSelector)
+ where TEntity : Entity
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ return builder.Where(fieldSelector, ConditionOperator.Null);
+ }
+
+ ///
+ /// Adds a 'not null' condition.
+ ///
+ /// The entity type.
+ /// The value type of the property.
+ /// The WhereGroupBuilder instance.
+ /// The lambda expression selecting the field.
+ /// This WhereGroupBuilder instance for method chaining.
+ /// Thrown when builder or fieldSelector is null.
+ public static WhereGroupBuilder WhereNotNull(
+ this WhereGroupBuilder builder,
+ Expression> fieldSelector)
+ where TEntity : Entity
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ return builder.Where(fieldSelector, ConditionOperator.NotNull);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/QueryBuilder/IQueryBuilder.cs b/src/QueryBuilder/IQueryBuilder.cs
index 4f9a80f..55c2107 100644
--- a/src/QueryBuilder/IQueryBuilder.cs
+++ b/src/QueryBuilder/IQueryBuilder.cs
@@ -1,6 +1,6 @@
using Microsoft.Xrm.Sdk.Query;
-namespace DataverseQuery
+namespace DataverseQuery.QueryBuilder
{
public interface IQueryBuilder
{
diff --git a/src/QueryBuilder/Interfaces/IAttributeNameResolver.cs b/src/QueryBuilder/Interfaces/IAttributeNameResolver.cs
new file mode 100644
index 0000000..5fe0a05
--- /dev/null
+++ b/src/QueryBuilder/Interfaces/IAttributeNameResolver.cs
@@ -0,0 +1,21 @@
+using System.Linq.Expressions;
+using Microsoft.Xrm.Sdk;
+
+namespace DataverseQuery.QueryBuilder.Interfaces
+{
+ ///
+ /// Resolves attribute names from lambda expressions.
+ ///
+ public interface IAttributeNameResolver
+ {
+ ///
+ /// Gets the attribute name from a property selector expression.
+ ///
+ /// The entity type.
+ /// The value type of the property.
+ /// The lambda expression selecting the property.
+ /// The attribute name, or null if it cannot be resolved.
+ string? GetAttributeName(Expression> fieldSelector)
+ where TEntity : Entity;
+ }
+}
\ No newline at end of file
diff --git a/src/QueryBuilder/Interfaces/IValueConverter.cs b/src/QueryBuilder/Interfaces/IValueConverter.cs
new file mode 100644
index 0000000..78e30f5
--- /dev/null
+++ b/src/QueryBuilder/Interfaces/IValueConverter.cs
@@ -0,0 +1,16 @@
+namespace DataverseQuery.QueryBuilder.Interfaces
+{
+ ///
+ /// Converts values for use in Dataverse queries.
+ ///
+ public interface IValueConverter
+ {
+ ///
+ /// Converts an array of values to their primitive representations for use in Dataverse queries.
+ ///
+ /// The type of values to convert.
+ /// The values to convert.
+ /// An array of converted values suitable for Dataverse queries.
+ object[] ConvertValues(TValue[] values);
+ }
+}
\ No newline at end of file
diff --git a/src/QueryBuilder/QueryExpressionBuilder.cs b/src/QueryBuilder/QueryExpressionBuilder.cs
index 807cbb0..0c1bf87 100644
--- a/src/QueryBuilder/QueryExpressionBuilder.cs
+++ b/src/QueryBuilder/QueryExpressionBuilder.cs
@@ -1,8 +1,10 @@
using System.Linq.Expressions;
+using DataverseQuery.QueryBuilder.Interfaces;
+using DataverseQuery.QueryBuilder.Services;
using Microsoft.Xrm.Sdk;
using Microsoft.Xrm.Sdk.Query;
-namespace DataverseQuery
+namespace DataverseQuery.QueryBuilder
{
public sealed class QueryExpressionBuilder : IQueryBuilder
where TEntity : Entity
@@ -11,13 +13,39 @@ public sealed class QueryExpressionBuilder : IQueryBuilder
private readonly List columns = new();
private readonly List filters = new();
private readonly List expands = new();
+ private readonly IAttributeNameResolver attributeNameResolver;
+ private readonly IValueConverter valueConverter;
private int? topCount;
+ ///
+ /// Initializes a new instance of the class.
+ ///
public QueryExpressionBuilder()
{
var logicalNameProp = typeof(TEntity).GetField("EntityLogicalName");
entityLogicalName = logicalNameProp?.GetValue(null) as string
?? typeof(TEntity).Name.ToLowerInvariant();
+
+ attributeNameResolver = new AttributeNameResolver();
+ valueConverter = new ValueConverter();
+ }
+
+ ///
+ /// Initializes a new instance of the class with custom services.
+ ///
+ /// The service for resolving attribute names from expressions.
+ /// The service for converting values for Dataverse queries.
+ /// Thrown when any parameter is null.
+ internal QueryExpressionBuilder(
+ IAttributeNameResolver attributeNameResolver,
+ IValueConverter valueConverter)
+ {
+ var logicalNameProp = typeof(TEntity).GetField("EntityLogicalName");
+ entityLogicalName = logicalNameProp?.GetValue(null) as string
+ ?? typeof(TEntity).Name.ToLowerInvariant();
+
+ this.attributeNameResolver = attributeNameResolver ?? throw new ArgumentNullException(nameof(attributeNameResolver));
+ this.valueConverter = valueConverter ?? throw new ArgumentNullException(nameof(valueConverter));
}
// Implement interface methods
@@ -30,7 +58,8 @@ public ColumnSet GetColumns()
public FilterExpression GetCombinedFilter()
{
- if (filters.Count == 0) return new FilterExpression();
+ if (filters.Count == 0)
+ return new FilterExpression();
var filter = new FilterExpression(LogicalOperator.And);
foreach (var f in filters)
@@ -52,10 +81,10 @@ public QueryExpressionBuilder Select(params Expression(selector);
+ var name = attributeNameResolver.GetAttributeName(selector);
if (!string.IsNullOrEmpty(name))
{
- columns.Add(name.ToLowerInvariant());
+ columns.Add(name);
}
}
@@ -70,22 +99,12 @@ public QueryExpressionBuilder Where(
ArgumentNullException.ThrowIfNull(fieldSelector);
ArgumentNullException.ThrowIfNull(values);
- var name = GetAttributeName(fieldSelector);
+ var name = attributeNameResolver.GetAttributeName(fieldSelector);
if (!string.IsNullOrEmpty(name))
{
- var filter = new FilterExpression();
- var primitiveValues = values
- .Cast