diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/EnumPropertyTest.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/EnumPropertyTest.cs index ebc96ad42..f99110f72 100644 --- a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/EnumPropertyTest.cs +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/EnumPropertyTest.cs @@ -96,25 +96,25 @@ public void TestQueryGroupForEnumViaDynamic() } } - //[TestMethod] - //public void TestQueryGroupForEnumViaExpression() - //{ - // // Setup - // var entities = Helper.CreateEnumCompleteTables(10); - - // using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) - // { - // // Act - // var insertAllResult = connection.InsertAll(entities); - // var queryResult = connection.Query(e => e.ColumnNVarChar == Direction.West); - - // // Assert - // Assert.AreEqual(entities.Count, queryResult.Count()); - - // // Assert - // entities.ForEach(entity => Helper.AssertPropertiesEquality(entity, queryResult.Where(item => item.SessionId == entity.SessionId))); - // } - //} + [TestMethod] + public void TestQueryGroupForEnumViaExpression() + { + // Setup + var entities = Helper.CreateEnumCompleteTables(10); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var insertAllResult = connection.InsertAll(entities); + var queryResult = connection.Query(e => e.ColumnNVarChar == Direction.West); + + // Assert + Assert.AreEqual(entities.Count, queryResult.Count()); + + // Assert + entities.ForEach(entity => Helper.AssertPropertiesEquality(entity, queryResult.Where(item => item.SessionId == entity.SessionId))); + } + } [TestMethod] public void TestQueryGroupForEnumViaQueryField() diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseDynamicValuesTest.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseDynamicValuesTest.cs index f373fb4de..5edd6a631 100644 --- a/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseDynamicValuesTest.cs +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseDynamicValuesTest.cs @@ -45,5 +45,20 @@ public void TestQueryGroupParseDynamicValueForMultipleFields() Assert.AreEqual(1, actual1); Assert.AreEqual(2, actual2); } + + [TestMethod] + public void TestQueryGroupParseDynamicValueForEnums() + { + // Setup + var parsed = QueryGroup.Parse(new { Field1 = Direction.West, Field2 = Direction.East }); + + // Act + var actual1 = parsed.QueryFields.First().Parameter.Value; + var actual2 = parsed.QueryFields.Last().Parameter.Value; + + // Assert + Assert.AreEqual(Direction.West, actual1); + Assert.AreEqual(Direction.East, actual2); + } } } diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionClassesTest.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionClassesTest.cs index 10ceb82e3..59e1ceacf 100644 --- a/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionClassesTest.cs +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionClassesTest.cs @@ -11,6 +11,14 @@ public class QueryGroupTestExpressionClassMember public string PropertyString { get; set; } } + public enum Direction + { + North, + South, + East, + West + } + public class QueryGroupTestExpressionClass { public int PropertyInt { get; set; } @@ -21,6 +29,7 @@ public class QueryGroupTestExpressionClass public Guid PropertyGuid { get; set; } public Boolean PropertyBoolean { get; set; } public Byte[] PropertyBytes { get; set; } + public Direction Direction { get; set; } [Map("PropertyString")] public string OtherPropertyString { get; set; } public QueryGroupTestExpressionClassMember Member { get; set; } diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionForContainsMethodTest.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionForContainsMethodTest.cs index f0383b91c..4dd3d88e0 100644 --- a/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionForContainsMethodTest.cs +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionForContainsMethodTest.cs @@ -514,7 +514,7 @@ public void TestQueryGroupParseExpressionArrayContainsEqualsFalse() // Act var actual = parsed.GetString(); - var expected = "NOT ([PropertyInt] IN (@PropertyInt_In_0, @PropertyInt_In_1))"; + var expected = "([PropertyInt] NOT IN (@PropertyInt_In_0, @PropertyInt_In_1))"; // Assert Assert.AreEqual(expected, actual); @@ -544,7 +544,7 @@ public void TestQueryGroupParseExpressionArrayContainsFromVariableEqualsFalse() // Act var actual = parsed.GetString(); - var expected = "NOT ([PropertyInt] IN (@PropertyInt_In_0, @PropertyInt_In_1))"; + var expected = "([PropertyInt] NOT IN (@PropertyInt_In_0, @PropertyInt_In_1))"; // Assert Assert.AreEqual(expected, actual); @@ -662,7 +662,7 @@ public void TestQueryGroupParseExpressionListContainsEqualsFalse() // Act var actual = parsed.GetString(); - var expected = "NOT ([PropertyInt] IN (@PropertyInt_In_0, @PropertyInt_In_1))"; + var expected = "([PropertyInt] NOT IN (@PropertyInt_In_0, @PropertyInt_In_1))"; // Assert Assert.AreEqual(expected, actual); @@ -692,7 +692,7 @@ public void TestQueryGroupParseExpressionListContainsFromVariableEqualsFalse() // Act var actual = parsed.GetString(); - var expected = "NOT ([PropertyInt] IN (@PropertyInt_In_0, @PropertyInt_In_1))"; + var expected = "([PropertyInt] NOT IN (@PropertyInt_In_0, @PropertyInt_In_1))"; // Assert Assert.AreEqual(expected, actual); diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionValuesTest.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionValuesTest.cs index de1ec793c..329d1a73f 100644 --- a/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionValuesTest.cs +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionValuesTest.cs @@ -6,7 +6,7 @@ namespace RepoDb.UnitTests { public partial class QueryGroupTest { - // Values + #region Values [TestMethod] public void TestQueryGroupParseExpressionValueIntConstant() @@ -362,7 +362,9 @@ public void TestQueryGroupParseExpressionWithDefaultValue() Assert.AreEqual(expected, actual); } - // Contains + #endregion + + #region Contains [TestMethod] public void TestQueryGroupParseExpressionValueForStringPropertyContains() @@ -522,7 +524,9 @@ public void TestQueryGroupParseExpressionValueForArrayNotContainsEqualsFalse() Assert.AreEqual("B", ((Array)actual).GetValue(1)); } - // All + #endregion + + #region All [TestMethod] public void TestQueryGroupParseExpressionValueForArrayAll() @@ -613,7 +617,9 @@ public void TestQueryGroupParseExpressionValueForArrayNotAllEqualsTrue() Assert.AreEqual("B", actual2); } - // Any + #endregion + + #region Any [TestMethod] public void TestQueryGroupParseExpressionValueForArrayAny() @@ -703,5 +709,39 @@ public void TestQueryGroupParseExpressionValueForArrayNotAnyEqualsTrue() Assert.AreEqual("A", actual1); Assert.AreEqual("B", actual2); } + + #endregion + + #region Enums + + [TestMethod] + public void TestQueryGroupParseExpressionValueForEnum() + { + // Setup + var parsed = QueryGroup.Parse(p => p.Direction == Direction.East); + + // Act + var actual = parsed.QueryFields.First().Parameter.Value; + + // Assert + Assert.AreEqual(Direction.East, actual); + } + + [TestMethod] + public void TestQueryGroupParseExpressionValueForEnums() + { + // Setup + var parsed = QueryGroup.Parse(p => p.Direction == Direction.East || p.Direction == Direction.West); + + // Act + var actual1 = parsed.QueryGroups.First().QueryFields.First().Parameter.Value; + var actual2 = parsed.QueryGroups.Last().QueryFields.First().Parameter.Value; + + // Assert + Assert.AreEqual(Direction.East, actual1); + Assert.AreEqual(Direction.West, actual2); + } + + #endregion } } diff --git a/RepoDb.Core/RepoDb/Extensions/ExpressionExtension.cs b/RepoDb.Core/RepoDb/Extensions/ExpressionExtension.cs index a72746f35..27fc24d64 100644 --- a/RepoDb.Core/RepoDb/Extensions/ExpressionExtension.cs +++ b/RepoDb.Core/RepoDb/Extensions/ExpressionExtension.cs @@ -39,7 +39,7 @@ public static class ExpressionExtension /// /// The instance of object to be identified. /// Returns true if the expression can be extracted as object. - public static bool IsExtractable(this Expression expression) + internal static bool IsExtractable(this Expression expression) { return m_extractableExpressionTypes.Contains(expression.NodeType); } @@ -49,7 +49,7 @@ public static bool IsExtractable(this Expression expression) /// /// The instance of object to be identified. /// Returns true if the expression can be grouped as object. - public static bool IsGroupable(this Expression expression) + internal static bool IsGroupable(this Expression expression) { return expression.NodeType == ExpressionType.AndAlso || expression.NodeType == ExpressionType.OrElse; } @@ -59,7 +59,7 @@ public static bool IsGroupable(this Expression expression) /// /// The instance of object to be identified. /// Returns true if the expression is using the object operations. - public static bool IsMathematical(this Expression expression) + internal static bool IsMathematical(this Expression expression) { return m_mathematicalExpressionTypes.Contains(expression.NodeType); } diff --git a/RepoDb.Core/RepoDb/Extensions/StringExtension.cs b/RepoDb.Core/RepoDb/Extensions/StringExtension.cs index 0ced4be83..be5cf3d35 100644 --- a/RepoDb.Core/RepoDb/Extensions/StringExtension.cs +++ b/RepoDb.Core/RepoDb/Extensions/StringExtension.cs @@ -37,12 +37,22 @@ internal static string AsAlphaNumeric(this string value, bool trim = false) } /// - /// Removes the database quotes from the string. + /// Unquotes a string. /// - /// The string value where the database quotes will be removed. + /// The string value to be unqouted. + /// The unquoted string. + public static string AsUnquoted(this string value) + { + return Regex.Replace(value, @"[\[\]']+", ""); + } + + /// + /// Unquotes a string. + /// + /// The string value to be unqouted. /// The boolean value that indicates whether to trim the string before unquoting. /// The separator in which the quotes will be removed. - /// The quoted string. + /// The unquoted string. public static string AsUnquoted(this string value, bool trim = false, string separator = ".") { if (trim) @@ -62,19 +72,27 @@ public static string AsUnquoted(this string value, bool trim = false, string sep } /// - /// Remove the quotes from the string. + /// Quotes a string. /// - /// The string value where the database quotes will be removed. - /// - private static string AsUnquoted(this string value) + /// The string value to be quoted. + /// The quoted string. + public static string AsQuoted(this string value) { - return Regex.Replace(value, @"[\[\]']+", ""); + if (!value.StartsWith("[")) + { + value = string.Concat("[", value); + } + if (!value.EndsWith("]")) + { + value = string.Concat(value, "]"); + } + return value; } /// - /// Adds a quotes to the string. + /// Quotes a string. /// - /// The string value where the database quotes will be added. + /// The string value to be quoted. /// The boolean value that indicates whether to trim the string before quoting. /// The separator in which the quotes will be placed. /// The quoted string. @@ -95,24 +113,6 @@ public static string AsQuoted(this string value, bool trim = false, string separ } } - /// - /// Add the quotes into the string. - /// - /// The string value where the database quotes will be added. - /// - private static string AsQuoted(this string value) - { - if (!value.StartsWith("[")) - { - value = string.Concat("[", value); - } - if (!value.EndsWith("]")) - { - value = string.Concat(value, "]"); - } - return value; - } - // AsEnumerable internal static IEnumerable AsEnumerable(this string value) { diff --git a/RepoDb.Core/RepoDb/QueryGroup.cs b/RepoDb.Core/RepoDb/QueryGroup.cs index d590b82b2..89c4165b6 100644 --- a/RepoDb.Core/RepoDb/QueryGroup.cs +++ b/RepoDb.Core/RepoDb/QueryGroup.cs @@ -664,11 +664,11 @@ private static QueryGroup Parse(Expression expression) where TEntity : } else if (expression.IsUnary()) { - return Parse(expression.ToUnary(), expression.NodeType); + return Parse(expression.ToUnary(), null, expression.NodeType, true); } else if (expression.IsMethodCall()) { - return Parse(expression.ToMethodCall()); + return Parse(expression.ToMethodCall(), false, true); } return null; } @@ -677,6 +677,7 @@ private static QueryGroup Parse(BinaryExpression expression) where TEnt { var leftQueryGroup = (QueryGroup)null; var rightQueryGroup = (QueryGroup)null; + var rightValue = (object)null; var skipRight = false; var isEqualsTo = true; @@ -684,19 +685,15 @@ private static QueryGroup Parse(BinaryExpression expression) where TEnt * LEFT */ - // Get the value in the right for (IsNot) - if (expression.Type == typeof(bool) && (expression.Right.IsConstant() || expression.Right.IsMember())) + // Get the value in the right + if (expression.IsExtractable()) { - var value = expression.Right.GetValue(); - if (value is bool) - { - isEqualsTo = Equals(value, false) == false; - } - else + rightValue = expression.Right.GetValue(); + skipRight = true; + if (rightValue is bool) { - // TODO: Fix the issue of the Enum for the Expression-Based Query + isEqualsTo = Equals(rightValue, false) == false; } - skipRight = true; } // Binary @@ -708,12 +705,12 @@ private static QueryGroup Parse(BinaryExpression expression) where TEnt // Unary else if (expression.Left.IsUnary() == true) { - leftQueryGroup = Parse(expression.Left.ToUnary(), expression.NodeType, isEqualsTo: isEqualsTo); + leftQueryGroup = Parse(expression.Left.ToUnary(), rightValue, expression.NodeType, isEqualsTo); } // MethodCall else if (expression.Left.IsMethodCall()) { - leftQueryGroup = Parse(expression.Left.ToMethodCall(), isEqualsTo: isEqualsTo); + leftQueryGroup = Parse(expression.Left.ToMethodCall(), false, isEqualsTo); } else { @@ -743,14 +740,14 @@ private static QueryGroup Parse(BinaryExpression expression) where TEnt rightQueryGroup = Parse(expression.Right.ToBinary()); } // Unary - if (expression.Right.IsUnary() == true) + else if (expression.Right.IsUnary() == true) { - rightQueryGroup = Parse(expression.Right.ToUnary(), expression.NodeType); + rightQueryGroup = Parse(expression.Right.ToUnary(), null, expression.NodeType, true); } // MethodCall else if (expression.Right.IsMethodCall()) { - rightQueryGroup = Parse(expression.Right.ToMethodCall()); + rightQueryGroup = Parse(expression.Right.ToMethodCall(), false, true); } // Return both of them @@ -765,11 +762,11 @@ private static QueryGroup Parse(BinaryExpression expression) where TEnt return leftQueryGroup ?? rightQueryGroup; } - private static QueryGroup Parse(UnaryExpression expression, ExpressionType expressionType, bool isEqualsTo = true) where TEntity : class + private static QueryGroup Parse(UnaryExpression expression, object rightValue, ExpressionType expressionType, bool isEqualsTo) where TEntity : class { if (expression.Operand?.IsMember() == true) { - return Parse(expression.Operand.ToMember(), expressionType); + return Parse(expression.Operand.ToMember(), rightValue, expressionType, false, true); } else if (expression.Operand?.IsMethodCall() == true) { @@ -778,38 +775,36 @@ private static QueryGroup Parse(UnaryExpression expression, ExpressionT return null; } - private static QueryGroup Parse(MemberExpression expression, ExpressionType expressionType, bool isNot = false, bool isEqualsTo = true) where TEntity : class + private static QueryGroup Parse(MemberExpression expression, object rightValue, ExpressionType expressionType, bool isNot, bool isEqualsTo) where TEntity : class { var queryGroup = (QueryGroup)null; - var value = expression.GetValue(); + var value = rightValue ?? expression.GetValue(); + + // Check if there are values if (value != null) { - #region Enum - + // Specialized for enum if (expression.Type.GetTypeInfo().IsEnum) { var dbType = TypeMapper.Get(expression.Type); - if (dbType != null && dbType.Value != DbType.String) - { - var mappedToType = new SqlDbTypeToClientTypeResolver().Resolve(dbType.Value); - var convertMethod = typeof(Convert).GetMethod(string.Concat("To", mappedToType.Name), new[] { typeof(object) }); - if (convertMethod != null) - { - value = convertMethod.Invoke(null, new[] { value }); - } - } + value = Enum.ToObject(expression.Type, value); } - #endregion - + // Create a new field var field = new QueryField(expression.Member.GetMappedName(), QueryField.GetOperation(expressionType), value); - queryGroup = new QueryGroup(field.AsEnumerable()); + + // Set the query group + queryGroup = new QueryGroup(field); + + // Set the query group IsNot property queryGroup.SetIsNot(isEqualsTo == false); } + + // Return the result return queryGroup; } - private static QueryGroup Parse(MethodCallExpression expression, bool isNot = false, bool isEqualsTo = true) where TEntity : class + private static QueryGroup Parse(MethodCallExpression expression, bool isNot, bool isEqualsTo) where TEntity : class { // Check methods for the 'Like', both 'Array.()' if (expression.Method.Name == "All" || expression.Method.Name == "Any") @@ -858,7 +853,7 @@ private static QueryGroup Parse(MethodCallExpression expression, bool i return null; } - private static QueryGroup ParseAllOrAnyForArrayOrAnyForList(MethodCallExpression expression, bool isNot = false, bool isEqualsTo = true) where TEntity : class + private static QueryGroup ParseAllOrAnyForArrayOrAnyForList(MethodCallExpression expression, bool isNot, bool isEqualsTo) where TEntity : class { // Return null if there is no any arguments if (expression.Arguments?.Any() != true) @@ -947,7 +942,7 @@ private static QueryGroup ParseAllOrAnyForArrayOrAnyForList(MethodCallE return new QueryGroup(queryFields, null, conjunction, (isNot == isEqualsTo)); } - private static QueryGroup ParseContainsForArrayOrList(MethodCallExpression expression, bool isNot, bool isEqualsTo = true) where TEntity : class + private static QueryGroup ParseContainsForArrayOrList(MethodCallExpression expression, bool isNot, bool isEqualsTo) where TEntity : class { // Return null if there is no any arguments if (expression.Arguments?.Any() != true) @@ -1004,20 +999,20 @@ private static QueryGroup ParseContainsForArrayOrList(MethodCallExpress } // Add to query fields - var operation = isNot ? Operation.NotIn : Operation.In; + var operation = (isNot == false && isEqualsTo == true) ? Operation.In : Operation.NotIn; var queryField = new QueryField(property.Name, operation, values); // Return the result var queryGroup = new QueryGroup(queryField); // Set the IsNot value - queryGroup.SetIsNot(isEqualsTo == false); + queryGroup.SetIsNot(isNot == true && isEqualsTo == false); // Return the instance return queryGroup; } - private static QueryGroup ParseContainsOrStartsWithOrEndsWithForStringProperty(MethodCallExpression expression, bool isNot = false, bool isEqualsTo = true) where TEntity : class + private static QueryGroup ParseContainsOrStartsWithOrEndsWithForStringProperty(MethodCallExpression expression, bool isNot, bool isEqualsTo) where TEntity : class { // Return null if there is no any arguments if (expression.Arguments?.Any() != true) diff --git a/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/EnumPropertyTest.cs b/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/EnumPropertyTest.cs index ebc96ad42..f99110f72 100644 --- a/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/EnumPropertyTest.cs +++ b/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/EnumPropertyTest.cs @@ -96,25 +96,25 @@ public void TestQueryGroupForEnumViaDynamic() } } - //[TestMethod] - //public void TestQueryGroupForEnumViaExpression() - //{ - // // Setup - // var entities = Helper.CreateEnumCompleteTables(10); - - // using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) - // { - // // Act - // var insertAllResult = connection.InsertAll(entities); - // var queryResult = connection.Query(e => e.ColumnNVarChar == Direction.West); - - // // Assert - // Assert.AreEqual(entities.Count, queryResult.Count()); - - // // Assert - // entities.ForEach(entity => Helper.AssertPropertiesEquality(entity, queryResult.Where(item => item.SessionId == entity.SessionId))); - // } - //} + [TestMethod] + public void TestQueryGroupForEnumViaExpression() + { + // Setup + var entities = Helper.CreateEnumCompleteTables(10); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var insertAllResult = connection.InsertAll(entities); + var queryResult = connection.Query(e => e.ColumnNVarChar == Direction.West); + + // Assert + Assert.AreEqual(entities.Count, queryResult.Count()); + + // Assert + entities.ForEach(entity => Helper.AssertPropertiesEquality(entity, queryResult.Where(item => item.SessionId == entity.SessionId))); + } + } [TestMethod] public void TestQueryGroupForEnumViaQueryField() diff --git a/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseDynamicValuesTest.cs b/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseDynamicValuesTest.cs index f373fb4de..5edd6a631 100644 --- a/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseDynamicValuesTest.cs +++ b/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseDynamicValuesTest.cs @@ -45,5 +45,20 @@ public void TestQueryGroupParseDynamicValueForMultipleFields() Assert.AreEqual(1, actual1); Assert.AreEqual(2, actual2); } + + [TestMethod] + public void TestQueryGroupParseDynamicValueForEnums() + { + // Setup + var parsed = QueryGroup.Parse(new { Field1 = Direction.West, Field2 = Direction.East }); + + // Act + var actual1 = parsed.QueryFields.First().Parameter.Value; + var actual2 = parsed.QueryFields.Last().Parameter.Value; + + // Assert + Assert.AreEqual(Direction.West, actual1); + Assert.AreEqual(Direction.East, actual2); + } } } diff --git a/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionClassesTest.cs b/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionClassesTest.cs index 10ceb82e3..59e1ceacf 100644 --- a/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionClassesTest.cs +++ b/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionClassesTest.cs @@ -11,6 +11,14 @@ public class QueryGroupTestExpressionClassMember public string PropertyString { get; set; } } + public enum Direction + { + North, + South, + East, + West + } + public class QueryGroupTestExpressionClass { public int PropertyInt { get; set; } @@ -21,6 +29,7 @@ public class QueryGroupTestExpressionClass public Guid PropertyGuid { get; set; } public Boolean PropertyBoolean { get; set; } public Byte[] PropertyBytes { get; set; } + public Direction Direction { get; set; } [Map("PropertyString")] public string OtherPropertyString { get; set; } public QueryGroupTestExpressionClassMember Member { get; set; } diff --git a/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionForContainsMethodTest.cs b/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionForContainsMethodTest.cs index f0383b91c..4dd3d88e0 100644 --- a/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionForContainsMethodTest.cs +++ b/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionForContainsMethodTest.cs @@ -514,7 +514,7 @@ public void TestQueryGroupParseExpressionArrayContainsEqualsFalse() // Act var actual = parsed.GetString(); - var expected = "NOT ([PropertyInt] IN (@PropertyInt_In_0, @PropertyInt_In_1))"; + var expected = "([PropertyInt] NOT IN (@PropertyInt_In_0, @PropertyInt_In_1))"; // Assert Assert.AreEqual(expected, actual); @@ -544,7 +544,7 @@ public void TestQueryGroupParseExpressionArrayContainsFromVariableEqualsFalse() // Act var actual = parsed.GetString(); - var expected = "NOT ([PropertyInt] IN (@PropertyInt_In_0, @PropertyInt_In_1))"; + var expected = "([PropertyInt] NOT IN (@PropertyInt_In_0, @PropertyInt_In_1))"; // Assert Assert.AreEqual(expected, actual); @@ -662,7 +662,7 @@ public void TestQueryGroupParseExpressionListContainsEqualsFalse() // Act var actual = parsed.GetString(); - var expected = "NOT ([PropertyInt] IN (@PropertyInt_In_0, @PropertyInt_In_1))"; + var expected = "([PropertyInt] NOT IN (@PropertyInt_In_0, @PropertyInt_In_1))"; // Assert Assert.AreEqual(expected, actual); @@ -692,7 +692,7 @@ public void TestQueryGroupParseExpressionListContainsFromVariableEqualsFalse() // Act var actual = parsed.GetString(); - var expected = "NOT ([PropertyInt] IN (@PropertyInt_In_0, @PropertyInt_In_1))"; + var expected = "([PropertyInt] NOT IN (@PropertyInt_In_0, @PropertyInt_In_1))"; // Assert Assert.AreEqual(expected, actual); diff --git a/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionValuesTest.cs b/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionValuesTest.cs index de1ec793c..329d1a73f 100644 --- a/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionValuesTest.cs +++ b/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseExpressionValuesTest.cs @@ -6,7 +6,7 @@ namespace RepoDb.UnitTests { public partial class QueryGroupTest { - // Values + #region Values [TestMethod] public void TestQueryGroupParseExpressionValueIntConstant() @@ -362,7 +362,9 @@ public void TestQueryGroupParseExpressionWithDefaultValue() Assert.AreEqual(expected, actual); } - // Contains + #endregion + + #region Contains [TestMethod] public void TestQueryGroupParseExpressionValueForStringPropertyContains() @@ -522,7 +524,9 @@ public void TestQueryGroupParseExpressionValueForArrayNotContainsEqualsFalse() Assert.AreEqual("B", ((Array)actual).GetValue(1)); } - // All + #endregion + + #region All [TestMethod] public void TestQueryGroupParseExpressionValueForArrayAll() @@ -613,7 +617,9 @@ public void TestQueryGroupParseExpressionValueForArrayNotAllEqualsTrue() Assert.AreEqual("B", actual2); } - // Any + #endregion + + #region Any [TestMethod] public void TestQueryGroupParseExpressionValueForArrayAny() @@ -703,5 +709,39 @@ public void TestQueryGroupParseExpressionValueForArrayNotAnyEqualsTrue() Assert.AreEqual("A", actual1); Assert.AreEqual("B", actual2); } + + #endregion + + #region Enums + + [TestMethod] + public void TestQueryGroupParseExpressionValueForEnum() + { + // Setup + var parsed = QueryGroup.Parse(p => p.Direction == Direction.East); + + // Act + var actual = parsed.QueryFields.First().Parameter.Value; + + // Assert + Assert.AreEqual(Direction.East, actual); + } + + [TestMethod] + public void TestQueryGroupParseExpressionValueForEnums() + { + // Setup + var parsed = QueryGroup.Parse(p => p.Direction == Direction.East || p.Direction == Direction.West); + + // Act + var actual1 = parsed.QueryGroups.First().QueryFields.First().Parameter.Value; + var actual2 = parsed.QueryGroups.Last().QueryFields.First().Parameter.Value; + + // Assert + Assert.AreEqual(Direction.East, actual1); + Assert.AreEqual(Direction.West, actual2); + } + + #endregion } } diff --git a/RepoDb/RepoDb/Extensions/ExpressionExtension.cs b/RepoDb/RepoDb/Extensions/ExpressionExtension.cs index d8327bdf2..ffbf7eba2 100644 --- a/RepoDb/RepoDb/Extensions/ExpressionExtension.cs +++ b/RepoDb/RepoDb/Extensions/ExpressionExtension.cs @@ -39,7 +39,7 @@ public static class ExpressionExtension /// /// The instance of object to be identified. /// Returns true if the expression can be extracted as object. - public static bool IsExtractable(this Expression expression) + internal static bool IsExtractable(this Expression expression) { return m_extractableExpressionTypes.Contains(expression.NodeType); } @@ -49,7 +49,7 @@ public static bool IsExtractable(this Expression expression) /// /// The instance of object to be identified. /// Returns true if the expression can be grouped as object. - public static bool IsGroupable(this Expression expression) + internal static bool IsGroupable(this Expression expression) { return expression.NodeType == ExpressionType.AndAlso || expression.NodeType == ExpressionType.OrElse; } @@ -59,7 +59,7 @@ public static bool IsGroupable(this Expression expression) /// /// The instance of object to be identified. /// Returns true if the expression is using the object operations. - public static bool IsMathematical(this Expression expression) + internal static bool IsMathematical(this Expression expression) { return m_mathematicalExpressionTypes.Contains(expression.NodeType); } diff --git a/RepoDb/RepoDb/QueryGroup.cs b/RepoDb/RepoDb/QueryGroup.cs index 3bcfa9eba..826a5dc9e 100644 --- a/RepoDb/RepoDb/QueryGroup.cs +++ b/RepoDb/RepoDb/QueryGroup.cs @@ -664,11 +664,11 @@ private static QueryGroup Parse(Expression expression) where TEntity : } else if (expression.IsUnary()) { - return Parse(expression.ToUnary(), expression.NodeType); + return Parse(expression.ToUnary(), null, expression.NodeType, true); } else if (expression.IsMethodCall()) { - return Parse(expression.ToMethodCall()); + return Parse(expression.ToMethodCall(), false, true); } return null; } @@ -677,6 +677,7 @@ private static QueryGroup Parse(BinaryExpression expression) where TEnt { var leftQueryGroup = (QueryGroup)null; var rightQueryGroup = (QueryGroup)null; + var rightValue = (object)null; var skipRight = false; var isEqualsTo = true; @@ -684,19 +685,15 @@ private static QueryGroup Parse(BinaryExpression expression) where TEnt * LEFT */ - // Get the value in the right for (IsNot) - if (expression.Type == typeof(bool) && (expression.Right.IsConstant() || expression.Right.IsMember())) + // Get the value in the right + if (expression.IsExtractable()) { - var value = expression.Right.GetValue(); - if (value is bool) - { - isEqualsTo = Equals(value, false) == false; - } - else + rightValue = expression.Right.GetValue(); + skipRight = true; + if (rightValue is bool) { - // TODO: Fix the issue of the Enum for the Expression-Based Query + isEqualsTo = Equals(rightValue, false) == false; } - skipRight = true; } // Binary @@ -708,12 +705,12 @@ private static QueryGroup Parse(BinaryExpression expression) where TEnt // Unary else if (expression.Left.IsUnary() == true) { - leftQueryGroup = Parse(expression.Left.ToUnary(), expression.NodeType, isEqualsTo: isEqualsTo); + leftQueryGroup = Parse(expression.Left.ToUnary(), rightValue, expression.NodeType, isEqualsTo); } // MethodCall else if (expression.Left.IsMethodCall()) { - leftQueryGroup = Parse(expression.Left.ToMethodCall(), isEqualsTo: isEqualsTo); + leftQueryGroup = Parse(expression.Left.ToMethodCall(), false, isEqualsTo); } else { @@ -743,14 +740,14 @@ private static QueryGroup Parse(BinaryExpression expression) where TEnt rightQueryGroup = Parse(expression.Right.ToBinary()); } // Unary - if (expression.Right.IsUnary() == true) + else if (expression.Right.IsUnary() == true) { - rightQueryGroup = Parse(expression.Right.ToUnary(), expression.NodeType); + rightQueryGroup = Parse(expression.Right.ToUnary(), null, expression.NodeType, true); } // MethodCall else if (expression.Right.IsMethodCall()) { - rightQueryGroup = Parse(expression.Right.ToMethodCall()); + rightQueryGroup = Parse(expression.Right.ToMethodCall(), false, true); } // Return both of them @@ -765,11 +762,11 @@ private static QueryGroup Parse(BinaryExpression expression) where TEnt return leftQueryGroup ?? rightQueryGroup; } - private static QueryGroup Parse(UnaryExpression expression, ExpressionType expressionType, bool isEqualsTo = true) where TEntity : class + private static QueryGroup Parse(UnaryExpression expression, object rightValue, ExpressionType expressionType, bool isEqualsTo) where TEntity : class { if (expression.Operand?.IsMember() == true) { - return Parse(expression.Operand.ToMember(), expressionType); + return Parse(expression.Operand.ToMember(), rightValue, expressionType, false, true); } else if (expression.Operand?.IsMethodCall() == true) { @@ -778,38 +775,36 @@ private static QueryGroup Parse(UnaryExpression expression, ExpressionT return null; } - private static QueryGroup Parse(MemberExpression expression, ExpressionType expressionType, bool isNot = false, bool isEqualsTo = true) where TEntity : class + private static QueryGroup Parse(MemberExpression expression, object rightValue, ExpressionType expressionType, bool isNot, bool isEqualsTo) where TEntity : class { var queryGroup = (QueryGroup)null; - var value = expression.GetValue(); + var value = rightValue ?? expression.GetValue(); + + // Check if there are values if (value != null) { - #region Enum - + // Specialized for enum if (expression.Type.IsEnum) { var dbType = TypeMapper.Get(expression.Type); - if (dbType != null && dbType.Value != DbType.String) - { - var mappedToType = new SqlDbTypeToClientTypeResolver().Resolve(dbType.Value); - var convertMethod = typeof(Convert).GetMethod(string.Concat("To", mappedToType.Name), new[] { typeof(object) }); - if (convertMethod != null) - { - value = convertMethod.Invoke(null, new[] { value }); - } - } + value = Enum.ToObject(expression.Type, value); } - #endregion - + // Create a new field var field = new QueryField(expression.Member.GetMappedName(), QueryField.GetOperation(expressionType), value); - queryGroup = new QueryGroup(field.AsEnumerable()); + + // Set the query group + queryGroup = new QueryGroup(field); + + // Set the query group IsNot property queryGroup.SetIsNot(isEqualsTo == false); } + + // Return the result return queryGroup; } - private static QueryGroup Parse(MethodCallExpression expression, bool isNot = false, bool isEqualsTo = true) where TEntity : class + private static QueryGroup Parse(MethodCallExpression expression, bool isNot, bool isEqualsTo) where TEntity : class { // Check methods for the 'Like', both 'Array.()' if (expression.Method.Name == "All" || expression.Method.Name == "Any") @@ -858,7 +853,7 @@ private static QueryGroup Parse(MethodCallExpression expression, bool i return null; } - private static QueryGroup ParseAllOrAnyForArrayOrAnyForList(MethodCallExpression expression, bool isNot = false, bool isEqualsTo = true) where TEntity : class + private static QueryGroup ParseAllOrAnyForArrayOrAnyForList(MethodCallExpression expression, bool isNot, bool isEqualsTo) where TEntity : class { // Return null if there is no any arguments if (expression.Arguments?.Any() != true) @@ -947,7 +942,7 @@ private static QueryGroup ParseAllOrAnyForArrayOrAnyForList(MethodCallE return new QueryGroup(queryFields, null, conjunction, (isNot == isEqualsTo)); } - private static QueryGroup ParseContainsForArrayOrList(MethodCallExpression expression, bool isNot, bool isEqualsTo = true) where TEntity : class + private static QueryGroup ParseContainsForArrayOrList(MethodCallExpression expression, bool isNot, bool isEqualsTo) where TEntity : class { // Return null if there is no any arguments if (expression.Arguments?.Any() != true) @@ -1004,20 +999,20 @@ private static QueryGroup ParseContainsForArrayOrList(MethodCallExpress } // Add to query fields - var operation = isNot ? Operation.NotIn : Operation.In; + var operation = (isNot == false && isEqualsTo == true) ? Operation.In : Operation.NotIn; var queryField = new QueryField(property.Name, operation, values); // Return the result var queryGroup = new QueryGroup(queryField); // Set the IsNot value - queryGroup.SetIsNot(isEqualsTo == false); + queryGroup.SetIsNot(isNot == true && isEqualsTo == false); // Return the instance return queryGroup; } - private static QueryGroup ParseContainsOrStartsWithOrEndsWithForStringProperty(MethodCallExpression expression, bool isNot = false, bool isEqualsTo = true) where TEntity : class + private static QueryGroup ParseContainsOrStartsWithOrEndsWithForStringProperty(MethodCallExpression expression, bool isNot, bool isEqualsTo) where TEntity : class { // Return null if there is no any arguments if (expression.Arguments?.Any() != true)