diff --git a/src/DatabaseWrapper.Core/DatabaseClientBase.cs b/src/DatabaseWrapper.Core/DatabaseClientBase.cs
index 160f6bc..3f8a40a 100644
--- a/src/DatabaseWrapper.Core/DatabaseClientBase.cs
+++ b/src/DatabaseWrapper.Core/DatabaseClientBase.cs
@@ -2,12 +2,15 @@
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Data;
+using static System.FormattableString;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using DatabaseWrapper.Core;
using ExpressionTree;
using System.Threading;
+using System.Data.Common;
+using System.Runtime.InteropServices;
namespace DatabaseWrapper.Core
{
@@ -302,12 +305,28 @@ public abstract class DatabaseClientBase
/// Cancellation token.
public abstract Task TruncateAsync(string tableName, CancellationToken token = default);
+ ///
+ /// Execute a query.
+ ///
+ /// A tuple of the aatabase query defined outside of the database client and query parameters.
+ /// A DataTable containing the results.
+ public abstract DataTable Query((string Query, IEnumerable> Parameters) queryAndParameters);
+
///
/// Execute a query.
///
/// Database query defined outside of the database client.
/// A DataTable containing the results.
- public abstract DataTable Query(string query);
+ public DataTable Query(string query)
+ => Query((query, null));
+
+ ///
+ /// Execute a query.
+ ///
+ /// A tuple of the aatabase query defined outside of the database client and query parameters.
+ /// Cancellation token.
+ /// A DataTable containing the results.
+ public abstract Task QueryAsync((string Query, IEnumerable> Parameters) queryAndParameters, CancellationToken token = default(CancellationToken));
///
/// Execute a query.
@@ -315,7 +334,8 @@ public abstract class DatabaseClientBase
/// Database query defined outside of the database client.
/// Cancellation token.
/// A DataTable containing the results.
- public abstract Task QueryAsync(string query, CancellationToken token = default);
+ public Task QueryAsync(string query, CancellationToken token = default(CancellationToken))
+ => QueryAsync((query, null), token);
///
/// Determine if records exist by filter.
diff --git a/src/DatabaseWrapper.Core/DatabaseHelperBase.cs b/src/DatabaseWrapper.Core/DatabaseHelperBase.cs
index 47dbb04..e3e3bdd 100644
--- a/src/DatabaseWrapper.Core/DatabaseHelperBase.cs
+++ b/src/DatabaseWrapper.Core/DatabaseHelperBase.cs
@@ -1,10 +1,14 @@
using System;
using System.Collections.Generic;
+using System.Data;
+using static System.FormattableString;
using System.Text;
using ExpressionTree;
namespace DatabaseWrapper.Core
{
+ using QueryAndParameters = System.ValueTuple>>;
+
///
/// Base implementation of helper properties and methods.
///
@@ -26,6 +30,91 @@ public abstract class DatabaseHelperBase
#region Private-Members
+ ///
+ /// Append object as the parameter to the parameters list.
+ ///
+ /// Returns the parameter name, that is to be used in the query currently under construction.
+ ///
+ /// List of the parameters.
+ /// Object to be added.
+ /// Parameter name.
+ static string AppendParameter(List> parameters, object o)
+ {
+ var r = Invariant($"@E{parameters.Count}");
+ parameters.Add(new KeyValuePair(r, o));
+ return r;
+ }
+
+ ///
+ /// Append object as the parameter to the parameters list.
+ /// Use the object type to apply conversions, if any needed.
+ ///
+ /// Returns the parameter name, that is to be used in the query currently under construction.
+ ///
+ /// List of the parameters.
+ /// Object to be added.
+ /// Parameter name.
+ static string AppendParameterByType(List> parameters, object untypedObject)
+ {
+ if (untypedObject is DateTime || untypedObject is DateTime?)
+ {
+ return AppendParameter(parameters, Convert.ToDateTime(untypedObject));
+ }
+ if (untypedObject is int || untypedObject is long || untypedObject is decimal)
+ {
+ return AppendParameter(parameters, untypedObject);
+ }
+ if (untypedObject is bool)
+ {
+ return AppendParameter(parameters, (bool)untypedObject ? 1 : 0);
+ }
+ if (untypedObject is byte[])
+ {
+ return AppendParameter(parameters, untypedObject);
+ }
+ return AppendParameter(parameters, untypedObject);
+ }
+
+ static readonly Dictionary BinaryOperators = new Dictionary() {
+ { OperatorEnum.And, "AND" },
+ { OperatorEnum.Or, "OR" },
+ { OperatorEnum.Equals, "=" },
+ { OperatorEnum.NotEquals, "<>" },
+ { OperatorEnum.GreaterThan, ">" },
+ { OperatorEnum.GreaterThanOrEqualTo, ">=" },
+ { OperatorEnum.LessThan, "<" },
+ { OperatorEnum.LessThanOrEqualTo, "<=" },
+ };
+
+ static readonly Dictionary InListOperators = new Dictionary() {
+ { OperatorEnum.In, "IN" },
+ { OperatorEnum.NotIn, "NOT IN" },
+ };
+
+ static readonly Dictionary ContainsOperators = new Dictionary() {
+ { OperatorEnum.Contains, ("LIKE", "OR") },
+ { OperatorEnum.ContainsNot, ("NOT LIKE", "AND") },
+ };
+ static readonly Dictionary LikeOperators = new Dictionary() {
+ { OperatorEnum.StartsWith, ("LIKE", "", "%") },
+ { OperatorEnum.StartsWithNot, ("NOT LIKE", "", "%") },
+ { OperatorEnum.EndsWith, ("LIKE", "%", "") },
+ { OperatorEnum.EndsWithNot, ("NOT LIKE", "%", "") },
+ };
+ static readonly Dictionary IsNullOperators = new Dictionary() {
+ { OperatorEnum.IsNull, "IS NULL" },
+ { OperatorEnum.IsNotNull, "IS NOT NULL" },
+ };
+
+ static readonly Dictionary SqlTypeMap = new Dictionary() {
+ { typeof(Int32), SqlDbType.Int },
+ { typeof(Int64), SqlDbType.BigInt },
+ { typeof(double), SqlDbType.Float },
+ { typeof(Guid), SqlDbType.UniqueIdentifier },
+ { typeof(byte[]), SqlDbType.Image },
+ { typeof(DateTimeOffset), SqlDbType.DateTimeOffset },
+ };
+
#endregion
#region Constructors-and-Factories
@@ -33,6 +122,185 @@ public abstract class DatabaseHelperBase
#endregion
#region Public-Methods
+ ///
+ /// Compose a where-cluase corresponding to the tree expression.
+ ///
+ /// Expression to be converted.
+ /// Parameters to append SQL query parameters to.
+ /// Where-clause.
+ ///
+ ///
+ public string ExpressionToWhereClause(Expr expr, List> parameters)
+ {
+ if (expr == null) return null;
+
+ string clause = "";
+
+ if (expr.Left == null) return null;
+
+ clause += "(";
+
+ if (expr.Left is Expr)
+ {
+ clause += ExpressionToWhereClause((Expr)expr.Left, parameters) + " ";
+ }
+ else
+ {
+ if (!(expr.Left is string))
+ {
+ throw new ArgumentException("Left term must be of type Expression or String");
+ }
+
+ if (expr.Operator != OperatorEnum.Contains
+ && expr.Operator != OperatorEnum.ContainsNot
+ && expr.Operator != OperatorEnum.StartsWith
+ && expr.Operator != OperatorEnum.StartsWithNot
+ && expr.Operator != OperatorEnum.EndsWith
+ && expr.Operator != OperatorEnum.EndsWithNot)
+ {
+ //
+ // These operators will add the left term
+ //
+ clause += PreparedFieldName(expr.Left.ToString()) + " ";
+ }
+ }
+
+ string operator_name;
+ string logic_operator_name;
+ string prefix;
+ string suffix;
+ (string, string) operator_pair;
+ (string, string, string) operator_triple;
+ if (BinaryOperators.TryGetValue(expr.Operator, out operator_name))
+ {
+ if (expr.Right == null) return null;
+ clause += operator_name + " ";
+
+ if (expr.Right is Expr)
+ {
+ clause += ExpressionToWhereClause((Expr)expr.Right, parameters);
+ }
+ else
+ {
+ clause += AppendParameterByType(parameters, expr.Right);
+ }
+ }
+ else if (InListOperators.TryGetValue(expr.Operator, out operator_name))
+ {
+ if (expr.Right == null) return null;
+ int inAdded = 0;
+ if (!Helper.IsList(expr.Right)) return null;
+ List