/// <summary> /// /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="propertyInfo"></param> /// <returns></returns> internal static ClassProperty GetProperty <TEntity>(PropertyInfo propertyInfo) where TEntity : class { if (propertyInfo == null) { return(null); } // Variables var properties = PropertyCache.Get <TEntity>(); var name = PropertyMappedNameCache.Get(propertyInfo); // Failing at some point - for base interfaces var property = properties .FirstOrDefault(p => string.Equals(p.GetMappedName(), name, StringComparison.OrdinalIgnoreCase)); // Matches to the actual class properties if (property == null) { property = properties .FirstOrDefault(p => string.Equals(p.PropertyInfo.Name, name, StringComparison.OrdinalIgnoreCase)); } // Return the value return(property); }
// Static Methods #region Parse (Expression) /// <summary> /// Parse an instance of <see cref="BinaryExpression"/> object. /// </summary> /// <typeparam name="TEntity">The target entity type</typeparam> /// <param name="expression">The instance of <see cref="BinaryExpression"/> to be parsed.</param> /// <returns>An instance of <see cref="QueryField"/> object.</returns> internal static QueryField Parse <TEntity>(BinaryExpression expression) where TEntity : class { // Only support the following expression type if (expression.IsExtractable() == false) { throw new NotSupportedException($"Expression '{expression.ToString()}' is currently not supported."); } // Name var fieldName = expression.GetName(); if (PropertyCache.Get <TEntity>().Any(property => PropertyMappedNameCache.Get(property.PropertyInfo, false) == fieldName) == false) { throw new InvalidQueryExpressionException($"Invalid expression '{expression.ToString()}'. The property {fieldName} is not defined on a target type '{typeof(TEntity).FullName}'."); } // Value var value = expression.GetValue(); // Operation var operation = GetOperation(expression.NodeType); // Return the value return(new QueryField(fieldName, operation, value)); }
/// <summary> /// Parses a dynamic object and convert back the result to an instance of <see cref="QueryGroup"/> object. /// </summary> /// <param name="obj">The dynamic object to be parsed.</param> /// <returns>An instance of the <see cref="QueryGroup"/> with parsed properties and values.</returns> public static QueryGroup Parse(object obj) { // Check for value if (obj == null) { throw new ArgumentNullException($"Parameter 'obj' cannot be null."); } // Type of the object var type = obj.GetType(); // Check if it is a generic type if (type.IsGenericType == false) { throw new InvalidOperationException("Only dynamic object is supported in the 'where' expression."); } // Declare variables var fields = new List <QueryField>(); // Iterate every property foreach (var property in type.GetProperties()) { var value = property.GetValue(obj); fields.Add(new QueryField(PropertyMappedNameCache.Get(property, true), value)); } // Return return(fields != null ? new QueryGroup(fields).Fix() : null); }
/// <summary> /// Parse an object and creates an enumerable of <see cref="Field"/> objects. Each field is equivalent /// to each property of the given object. The parse operation uses a reflection operation. /// </summary> /// <param name="obj">An object to be parsed.</param> /// <returns>An enumerable of <see cref="Field"/> objects.</returns> internal static IEnumerable <Field> Parse(object obj) { foreach (var property in obj.GetType().GetProperties()) { yield return(new Field(PropertyMappedNameCache.Get(property))); } }
/// <summary> /// Gets the mapped-name for the current property. /// </summary> /// <returns>The mapped-name value.</returns> public string GetMappedName() { if (mappedName != null) { return(mappedName); } return(mappedName = PropertyMappedNameCache.Get(GetDeclaringType(), PropertyInfo)); }
/// <summary> /// Gets the quoted mapped-name for the current property. /// </summary> /// <returns>The quoted mapped-name value.</returns> public string GetQuotedMappedName() { if (m_quotedMappedName != null) { return(m_quotedMappedName); } return(m_quotedMappedName = PropertyMappedNameCache.Get(PropertyInfo, true, DbSetting)); }
/// <summary> /// Gets the mapped-name for the current property. /// </summary> /// <returns>The mapped-name value.</returns> public string GetMappedName() { if (m_mappedName != null) { return(m_mappedName); } return(m_mappedName = PropertyMappedNameCache.Get(PropertyInfo)); }
/// <summary> /// Gets the unquoted mapped-name for the current property. /// </summary> /// <returns>The unquoted mapped-name value.</returns> public string GetUnquotedMappedName() { if (m_unquotedMappedName != null) { return(m_unquotedMappedName); } return(m_unquotedMappedName = PropertyMappedNameCache.Get(PropertyInfo, false)); }
/// <summary> /// Gets the cached <see cref="ClassProperty"/> object of the data entity (via <see cref="PropertyInfo"/> object). /// </summary> /// <param name="entityType">The type of the data entity.</param> /// <param name="propertyInfo">The instance of the <see cref="PropertyInfo"/> object.</param> /// <returns>The instance of cached <see cref="ClassProperty"/> object.</returns> internal static ClassProperty Get(Type entityType, PropertyInfo propertyInfo) { // Validate the presence ThrowNullReferenceException(propertyInfo, "PropertyInfo"); // Return the value return(Get(entityType)? .FirstOrDefault(p => p.PropertyInfo == propertyInfo || string.Equals(p.GetMappedName(), PropertyMappedNameCache.Get(entityType, propertyInfo), StringComparison.OrdinalIgnoreCase))); }
/// <summary> /// /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="expression"></param> /// <param name="isNot"></param> /// <param name="isEqualsTo"></param> /// <returns></returns> private static QueryGroup ParseContainsOrStartsWithOrEndsWithForStringProperty <TEntity>(MethodCallExpression expression, bool isNot, bool isEqualsTo) where TEntity : class { // TODO: Refactor this // Return null if there is no any arguments if (expression.Arguments?.Any() != true) { return(null); } // Get the value arg var value = Convert.ToString(expression.Arguments.FirstOrDefault()?.GetValue()); // Make sure it has a value if (string.IsNullOrWhiteSpace(value)) { throw new NotSupportedException($"Expression '{expression.ToString()}' is currently not supported."); } // Make sure it is a property info var member = expression.Object.ToMember().Member; if (member.IsPropertyInfo() == false) { throw new NotSupportedException($"Expression '{expression.ToString()}' is currently not supported."); } // Get the property var property = member.ToPropertyInfo(); // Make sure the property is in the entity if (PropertyCache.Get <TEntity>().FirstOrDefault(p => string.Equals(p.PropertyInfo.Name, property.Name, StringComparison.OrdinalIgnoreCase)) == null) { throw new InvalidExpressionException($"Invalid expression '{expression.ToString()}'. The property {property.Name} is not defined on a target type '{typeof(TEntity).FullName}'."); } // Add to query fields var operation = (isNot == isEqualsTo) ? Operation.NotLike : Operation.Like; var queryField = new QueryField(PropertyMappedNameCache.Get(property), operation, ConvertToLikeableValue(expression.Method.Name, value)); // Return the result return(new QueryGroup(queryField.AsEnumerable())); }
/// <summary> /// Parse an object and creates an enumerable of <see cref="Field"/> objects. Each field is equivalent /// to each property of the given object. The parse operation uses a reflection operation. /// </summary> /// <param name="obj">An object to be parsed.</param> /// <returns>An enumerable of <see cref="Field"/> objects.</returns> public static IEnumerable <Field> Parse(object obj) { if (obj == null) { throw new NullReferenceException("Parameter 'obj' cannot be null."); } if (obj.GetType().GetTypeInfo().IsGenericType == false) { throw new InvalidOperationException("Parameter 'obj' must be dynamic type."); } var properties = obj.GetType().GetTypeInfo().GetProperties(); if (properties?.Any() == false) { throw new InvalidOperationException("Parameter 'obj' must have atleast one property."); } return(properties.Select(property => new Field(PropertyMappedNameCache.Get(property)))); }
/// <summary> /// Parse an instance of <see cref="BinaryExpression"/> object. /// </summary> /// <typeparam name="TEntity">The target entity type</typeparam> /// <param name="expression">The instance of <see cref="BinaryExpression"/> to be parsed.</param> /// <returns>An instance of <see cref="QueryField"/> object.</returns> internal static QueryField Parse <TEntity>(BinaryExpression expression) where TEntity : class { // Only support the following expression type if (expression.IsExtractable() == false) { throw new NotSupportedException($"Expression '{expression.ToString()}' is currently not supported."); } // Name var field = expression.GetField(); var properties = PropertyCache.Get <TEntity>(); // Failing at some point - for base interfaces var property = properties .FirstOrDefault(p => string.Equals(PropertyMappedNameCache.Get(p.PropertyInfo), field.Name, StringComparison.OrdinalIgnoreCase)); // Matches to the actual class properties if (property == null) { property = properties .FirstOrDefault(p => string.Equals(p.PropertyInfo.Name, field.Name, StringComparison.OrdinalIgnoreCase)); // Reset the field field = property?.AsField(); } // Check the existence if (property == null) { throw new InvalidExpressionException($"Invalid expression '{expression.ToString()}'. The property {field.Name} is not defined on a target type '{typeof(TEntity).FullName}'."); } // Value var value = expression.GetValue(); // Operation var operation = GetOperation(expression.NodeType); // Return the value return(new QueryField(field, operation, value)); }
private static QueryGroup ParseAllOrAnyForArrayOrAnyForList <TEntity>(MethodCallExpression expression, bool isNot, bool isEqualsTo) where TEntity : class { // Return null if there is no any arguments if (expression.Arguments?.Any() != true) { return(null); } // Get the last property var last = expression .Arguments .LastOrDefault(); // Make sure the last is a member if (last == null || last?.IsLambda() == false) { throw new NotSupportedException($"Expression '{expression.ToString()}' is currently not supported."); } // Make sure the last is a binary var lambda = last.ToLambda(); if (lambda.Body.IsBinary() == false) { throw new NotSupportedException($"Expression '{expression.ToString()}' is currently not supported."); } // Make sure it is a member var binary = lambda.Body.ToBinary(); if (binary.Left.IsMember() == false && binary.Right.IsMember() == false) { throw new NotSupportedException($"Expression '{expression.ToString()}' is currently not supported. Expression must contain a single condition to any property of type '{typeof(TEntity).FullName}'."); } // Make sure it is a property var member = binary.Left.IsMember() ? binary.Left.ToMember().Member : binary.Right.ToMember().Member; if (member.IsPropertyInfo() == false) { throw new NotSupportedException($"Expression '{expression.ToString()}' is currently not supported."); } // Make sure the property is in the entity var property = member.ToPropertyInfo(); if (PropertyCache.Get <TEntity>().FirstOrDefault(p => p.PropertyInfo == property) == null) { throw new InvalidQueryExpressionException($"Invalid expression '{expression.ToString()}'. The property {property.Name} is not defined on a target type '{typeof(TEntity).FullName}'."); } // Variables needed for fields var queryFields = new List <QueryField>(); var conjunction = Conjunction.And; // Support only various methods if (expression.Method.Name == "Any") { conjunction = Conjunction.Or; } else if (expression.Method.Name == "All") { conjunction = Conjunction.And; } // Call the method var first = expression.Arguments.First(); var values = (object)null; // Identify the type of the argument if (first.IsNewArray()) { values = first.ToNewArray().GetValue(); } else if (first.IsMember()) { values = first.ToMember().GetValue(); } // Values must be an array if (values is Array) { var operation = QueryField.GetOperation(binary.NodeType); foreach (var value in (Array)values) { queryFields.Add(new QueryField(PropertyMappedNameCache.Get(property, true), operation, value)); } } // Return the result return(new QueryGroup(queryFields, null, conjunction, (isNot == isEqualsTo))); }
/// <summary> /// Gets the mapped-name for the current property. /// </summary> /// <returns>The mapped-name value.</returns> public string GetMappedName() { return(mappedName ??= PropertyMappedNameCache.Get(GetDeclaringType(), PropertyInfo)); }
/// <summary> /// Selecting data from Sql with Sql IN clause usually requires 1 Parameter for every value, and this result in /// safe Sql Queries, but there is a limit of 2100 parameters on a Sql Command. This method provides a safe /// alternative implementation that is highly performant for large data sets using a list of int values (e.g Ids). /// </summary> /// <typeparam name="TEntity"></typeparam> /// <param name="sqlConnection"></param> /// <param name="idList"></param> /// <param name="filterFieldName"></param> /// <param name="tableName"></param> /// <param name="fields"></param> /// <param name="orderBy"></param> /// <param name="hints"></param> /// <param name="cacheKey"></param> /// <param name="cacheItemExpiration"></param> /// <param name="commandTimeout"></param> /// <param name="transaction"></param> /// <param name="logTrace"></param> /// <param name="cancellationToken"></param> /// <param name="cache"></param> /// <returns></returns> public static async Task <IEnumerable <TEntity> > QueryBulkResultsByIdAsync <TEntity>( this SqlConnection sqlConnection, IEnumerable <int> idList, string filterFieldName = null, string tableName = null, IEnumerable <Field> fields = null, IEnumerable <OrderField> orderBy = null, string hints = null, string cacheKey = null, int?cacheItemExpiration = null, int?commandTimeout = null, IDbTransaction transaction = null, ICache cache = null, Action <string> logTrace = null, CancellationToken cancellationToken = default ) where TEntity : class { var connection = sqlConnection ?? throw new ArgumentNullException(nameof(sqlConnection)); var timer = Stopwatch.StartNew(); Field filterField; if (string.IsNullOrWhiteSpace(filterFieldName)) { //Attempt to dynamically resolve the Filter Field as the Identity or Primary Key field (if the field is a Numeric Type)! var classProp = IdentityCache.Get <TEntity>() ?? PrimaryCache.Get <TEntity>(); if (classProp == null || !classProp.PropertyInfo.PropertyType.IsNumericType()) { throw new ArgumentException( $"The filter field name was not specified and an Int Id could not be dynamically resolved from the Identity or Primary Key properties for the type [{typeof(TEntity).Name}]", nameof(filterFieldName) ); } filterField = new Field(classProp.GetMappedName()); } else { //If Specified then we use the Filter Field Name specified and attempt to resolve it on the Model! filterField = new Field(PropertyMappedNameCache.Get <TEntity>(filterFieldName) ?? filterFieldName); } var dbTableName = string.IsNullOrWhiteSpace(tableName) ? ClassMappedNameCache.Get <TEntity>() : tableName; //Ensure we have default fields; default is to include All Fields... var fieldsList = fields?.ToList(); var selectFields = fieldsList?.Any() == true ? fieldsList : FieldCache.Get <TEntity>(); //Retrieve only the select fields that are valid for the Database query! //NOTE: We guard against duplicate values as a convenience. var validSelectFields = await connection .GetValidatedDbFieldsAsync(dbTableName, selectFields.Distinct()) .ConfigureAwait(false); var dbSetting = connection.GetDbSetting(); var query = new QueryBuilder() .Clear() .Select().FieldsFrom(validSelectFields, dbSetting) .From().TableNameFrom(dbTableName, dbSetting).WriteText("data") .WriteText("INNER JOIN STRING_SPLIT(@StringSplitCsvValues, ',') split") .On().WriteText("(data.").FieldFrom(filterField).WriteText("= split.value)") .OrderByFrom(orderBy, dbSetting) .HintsFrom(hints) .End(); var commandText = query.GetString(); var commandParams = new { StringSplitCsvValues = idList.ToCsvString(false) }; logTrace?.Invoke($"Query: {commandText}"); logTrace?.Invoke($"Query Param @StringSplitCsvValues: {commandParams.StringSplitCsvValues}"); await connection.EnsureOpenAsync(cancellationToken : cancellationToken); logTrace?.Invoke($"DB Connection Established in: {timer.ToElapsedTimeDescriptiveFormat()}"); //By creating a View Model of the data we are interested in we can easily query the View // and teh complex many-to-many join is now encapsulated for us in the SQL View... var results = await connection.ExecuteQueryAsync <TEntity>( commandText, commandParams, commandType : CommandType.Text, commandTimeout : commandTimeout, transaction : transaction, cancellationToken : cancellationToken, cacheKey : cacheKey, cacheItemExpiration : cacheItemExpiration, cache : cache ).ConfigureAwait(false); logTrace?.Invoke($"Query Execution Completed in: {timer.ToElapsedTimeDescriptiveFormat()}"); return(results); }
/// <summary> /// Gets the mapped-name for the current property. /// </summary> /// <returns>The mapped-name value.</returns> public string GetMappedName() => mappedName ??= PropertyMappedNameCache.Get(GetDeclaringType(), PropertyInfo);
private static QueryGroup ParseContainsForArrayOrList <TEntity>(MethodCallExpression expression, bool isNot, bool isEqualsTo) where TEntity : class { // Return null if there is no any arguments if (expression.Arguments?.Any() != true) { return(null); } // Get the last arg var last = expression .Arguments .LastOrDefault(); // Make sure the last arg is a member if (last == null || last?.IsMember() == false) { throw new NotSupportedException($"Expression '{expression.ToString()}' is currently not supported."); } // Make sure it is a property info var member = last.ToMember().Member; if (member.IsPropertyInfo() == false) { throw new NotSupportedException($"Expression '{expression.ToString()}' is currently not supported."); } // Get the property var property = member.ToPropertyInfo(); // Make sure the property is in the entity if (PropertyCache.Get <TEntity>().FirstOrDefault(p => p.PropertyInfo == property) == null) { throw new InvalidQueryExpressionException($"Invalid expression '{expression.ToString()}'. The property {property.Name} is not defined on a target type '{typeof(TEntity).FullName}'."); } // Get the values var values = (object)null; // Array/List Separation if (expression.Object == null) { // Expecting an array values = expression.Arguments.First().GetValue(); } else { // Expecting a list here values = expression.Object.GetValue(); // Convert to a proper array type if ((values is Array) == false) { values = values.AsArray(); } } // Add to query fields var operation = (isNot == false && isEqualsTo == true) ? Operation.In : Operation.NotIn; var queryField = new QueryField(PropertyMappedNameCache.Get(property, true), operation, values); // Return the result var queryGroup = new QueryGroup(queryField); // Set the IsNot value queryGroup.SetIsNot(isNot == true && isEqualsTo == false); // Return the instance return(queryGroup); }