From 52172082ccb00c0df55b8db55c229de6c3e1826c Mon Sep 17 00:00:00 2001 From: Michael Pendon Date: Sat, 8 Jun 2019 10:09:50 +0200 Subject: [PATCH] Bug: Operations are failing on the Unorganized Table - by Christian Franck #262 --- .../RepoDb.IntegrationTests/Helper.cs | 42 +++ .../Models/UnorganizedTable.cs | 19 ++ .../ObjectQuotationTest.cs | 315 ++++++++++++++++++ .../RepoDb.IntegrationTests/Setup/Database.cs | 24 ++ .../RepoDb.UnitTests/Others/QuotationTest.cs | 129 +++++++ .../QueryGroups/QueryGroupCommonTest.cs | 48 +++ .../QueryGroupParseDynamicCommonTest.cs | 1 - .../RepoDb/Extensions/FieldExtension.cs | 4 +- .../RepoDb/Extensions/StringExtension.cs | 51 ++- RepoDb.Core/RepoDb/Field.cs | 2 +- RepoDb.Core/RepoDb/Parameter.cs | 2 +- .../RepoDb/Reflection/FunctionFactory.cs | 40 ++- .../RepoDb.IntegrationTests/Helper.cs | 42 +++ .../Models/UnorganizedTable.cs | 19 ++ .../ObjectQuotationTest.cs | 315 ++++++++++++++++++ .../RepoDb.IntegrationTests.csproj | 2 + .../RepoDb.IntegrationTests/Setup/Database.cs | 24 ++ .../RepoDb.UnitTests/Others/QuotationTest.cs | 129 +++++++ .../QueryGroups/QueryGroupCommonTest.cs | 48 +++ .../QueryGroupParseDynamicCommonTest.cs | 1 - .../RepoDb.UnitTests/RepoDb.UnitTests.csproj | 1 + RepoDb/RepoDb/Extensions/FieldExtension.cs | 4 +- RepoDb/RepoDb/Extensions/StringExtension.cs | 75 +++-- RepoDb/RepoDb/Field.cs | 2 +- RepoDb/RepoDb/Parameter.cs | 2 +- RepoDb/RepoDb/Reflection/FunctionFactory.cs | 40 ++- 26 files changed, 1309 insertions(+), 72 deletions(-) create mode 100644 RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Models/UnorganizedTable.cs create mode 100644 RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/ObjectQuotationTest.cs create mode 100644 RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/Others/QuotationTest.cs create mode 100644 RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/Models/UnorganizedTable.cs create mode 100644 RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/ObjectQuotationTest.cs create mode 100644 RepoDb/RepoDb.Tests/RepoDb.UnitTests/Others/QuotationTest.cs diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Helper.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Helper.cs index 35d3a786a..0e9392843 100644 --- a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Helper.cs +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Helper.cs @@ -476,6 +476,48 @@ public static TypeLevelMappedForStringEnumCompleteTable CreateTypeLevelMappedFor #endregion + #region UnorganizedTable + + /// + /// Creates a list of objects. + /// + /// The number of rows. + /// A list of objects. + public static List CreateUnorganizedTables(int count) + { + var tables = new List(); + for (var i = 0; i < count; i++) + { + var index = i + 1; + tables.Add(new UnorganizedTable + { + SessionId = Guid.NewGuid(), + ColumnDateTime2 = DateTime.UtcNow, + ColumnInt = index, + ColumnNVarChar = $"NVARCHAR{index}" + }); + } + return tables; + } + + /// + /// Creates an instance of object. + /// + /// A new created instance of object. + public static UnorganizedTable CreateUnorganizedTable() + { + var random = new Random(); + return new UnorganizedTable + { + SessionId = Guid.NewGuid(), + ColumnDateTime2 = DateTime.UtcNow, + ColumnInt = random.Next(int.MinValue, int.MaxValue), + ColumnNVarChar = Guid.NewGuid().ToString() + }; + } + + #endregion + #region Dynamics #region IdentityTable diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Models/UnorganizedTable.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Models/UnorganizedTable.cs new file mode 100644 index 000000000..bb4c89220 --- /dev/null +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Models/UnorganizedTable.cs @@ -0,0 +1,19 @@ +using RepoDb.Attributes; +using System; + +namespace RepoDb.IntegrationTests.Models +{ + [Map("[dbo].[Unorganized Table]")] + public class UnorganizedTable + { + public long Id { get; set; } + [Map("Session Id")] + public Guid SessionId { get; set; } + [Map("Column Int")] + public int? ColumnInt { get; set; } + [Map("Column/NVarChar")] + public string ColumnNVarChar { get; set; } + [Map("Column.DateTime")] + public DateTime? ColumnDateTime2 { get; set; } + } +} diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/ObjectQuotationTest.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/ObjectQuotationTest.cs new file mode 100644 index 000000000..0b216dde2 --- /dev/null +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/ObjectQuotationTest.cs @@ -0,0 +1,315 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RepoDb.IntegrationTests.Models; +using RepoDb.IntegrationTests.Setup; +using System; +using System.Data.SqlClient; +using System.Linq; + +namespace RepoDb.IntegrationTests +{ + [TestClass] + public class ObjectQuotationTest + { + [TestInitialize] + public void Initialize() + { + Database.Initialize(); + Cleanup(); + } + + [TestCleanup] + public void Cleanup() + { + Database.Cleanup(); + } + + #region Delete + + [TestMethod] + public void TestDeleteObjectQuotation() + { + // Setup + var entities = Helper.CreateUnorganizedTables(10); + var last = entities.Last(); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var rowsInserted = connection.InsertAll(entities); + var deleteResult = connection.Delete(last.Id); + + // Assert + Assert.AreEqual(1, deleteResult); + } + } + + [TestMethod] + public void TestDeleteObjectQuotationViaNonAlphaNumericField() + { + // Setup + var entities = Helper.CreateUnorganizedTables(10); + var last = entities.Last(); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var rowsInserted = connection.InsertAll(entities); + var deleteResult = connection.Delete(e => e.SessionId == last.SessionId); + + // Assert + Assert.AreEqual(1, deleteResult); + } + } + + #endregion + + #region Insert + + [TestMethod] + public void TestInsertObjectQuotation() + { + // Setup + var entity = Helper.CreateUnorganizedTable(); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var id = connection.Insert(entity); + + // Assert + Assert.IsNotNull(id); + Assert.IsTrue(id > 0); + Assert.AreEqual(1, connection.CountAll()); + } + } + + #endregion + + #region InsertAll + + [TestMethod] + public void TestInsertAllObjectQuotation() + { + // Setup + var entities = Helper.CreateUnorganizedTables(10); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var rowsInserted = connection.InsertAll(entities); + + // Assert + Assert.AreEqual(entities.Count, rowsInserted); + Assert.AreEqual(entities.Count, connection.CountAll()); + } + } + + #endregion + + #region Merge + + [TestMethod] + public void TestMergeObjectQuotation() + { + // Setup + var entity = Helper.CreateUnorganizedTable(); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var id = connection.Merge(entity); + + // Assert + Assert.IsNotNull(id); + Assert.IsTrue(id > 0); + Assert.AreEqual(1, connection.CountAll()); + + // Setup + entity.ColumnDateTime2 = DateTime.UtcNow; + entity.ColumnInt = 2; + entity.ColumnNVarChar = Guid.NewGuid().ToString(); + + // Act + id = connection.Merge(entity); + var queryResult = connection.Query(id).First(); + + // Assert + Helper.AssertPropertiesEquality(entity, queryResult); + } + } + + #endregion + + #region MergeAll + + [TestMethod] + public void TestMergeAllObjectQuotation() + { + // Setup + var entities = Helper.CreateUnorganizedTables(10); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var rowsAffected = connection.MergeAll(entities); + + // Assert + Assert.AreEqual(entities.Count, rowsAffected); + Assert.AreEqual(entities.Count, connection.CountAll()); + + // Setup + entities.ForEach(entity => + { + entity.ColumnDateTime2 = DateTime.UtcNow; + entity.ColumnInt = 2; + entity.ColumnNVarChar = Guid.NewGuid().ToString(); + }); + + // Act + rowsAffected = connection.MergeAll(entities); + + // Assert + Assert.AreEqual(entities.Count, rowsAffected); + + // Act + var queryAllResult = connection.QueryAll(); + + // Assert + entities.ForEach(entity => Helper.AssertPropertiesEquality(entity, queryAllResult.First(item => item.Id == entity.Id))); + } + } + + #endregion + + #region Query + + [TestMethod] + public void TestQueryObjectQuotation() + { + // Setup + var entities = Helper.CreateUnorganizedTables(10); + var last = entities.Last(); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var rowsInserted = connection.InsertAll(entities); + var queryResult = connection.Query(last.Id).FirstOrDefault(); + + // Assert + Assert.IsNotNull(queryResult); + Helper.AssertPropertiesEquality(last, queryResult); + } + } + + [TestMethod] + public void TestQueryObjectQuotationViaNonAlphaNumericField() + { + // Setup + var entities = Helper.CreateUnorganizedTables(10); + var last = entities.Last(); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var rowsInserted = connection.InsertAll(entities); + var queryResult = connection.Query(e => e.SessionId == last.SessionId).FirstOrDefault(); + + // Assert + Assert.IsNotNull(queryResult); + Helper.AssertPropertiesEquality(last, queryResult); + } + } + + #endregion + + #region QueryAll + + [TestMethod] + public void TestQueryAllObjectQuotation() + { + // Setup + var entities = Helper.CreateUnorganizedTables(10); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var rowsInserted = connection.InsertAll(entities); + var queryAllResult = connection.QueryAll(); + + // Assert + Assert.AreEqual(entities.Count, queryAllResult.Count()); + entities.ForEach(entity => Helper.AssertPropertiesEquality(entity, queryAllResult.First(item => item.Id == entity.Id))); + } + } + + #endregion + + #region Update + + [TestMethod] + public void TestUpdateObjectQuotation() + { + // Setup + var entity = Helper.CreateUnorganizedTable(); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var id = connection.Insert(entity); + + // Setup + entity.ColumnDateTime2 = DateTime.UtcNow; + entity.ColumnInt = 2; + entity.ColumnNVarChar = Guid.NewGuid().ToString(); + + // Act + var updateReuslt = connection.Update(entity); + var queryResult = connection.Query(id).First(); + + // Assert + Assert.AreEqual(1, updateReuslt); + Helper.AssertPropertiesEquality(entity, queryResult); + } + } + + #endregion + + #region UpdateAll + + [TestMethod] + public void TestUpdateAllObjectQuotation() + { + // Setup + var entities = Helper.CreateUnorganizedTables(10); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var rowsAffected = connection.InsertAll(entities); + + // Setup + entities.ForEach(entity => + { + entity.ColumnDateTime2 = DateTime.UtcNow; + entity.ColumnInt = 2; + entity.ColumnNVarChar = Guid.NewGuid().ToString(); + }); + + // Act + rowsAffected = connection.UpdateAll(entities); + + // Assert + Assert.AreEqual(entities.Count, rowsAffected); + + // Act + var queryAllResult = connection.QueryAll(); + + // Assert + entities.ForEach(entity => Helper.AssertPropertiesEquality(entity, queryAllResult.First(item => item.Id == entity.Id))); + } + } + + #endregion + } +} diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Setup/Database.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Setup/Database.cs index e1aeaebba..96fa858b3 100644 --- a/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Setup/Database.cs +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.IntegrationTests/Setup/Database.cs @@ -85,6 +85,7 @@ public static void CreateTables() CreateCompleteTable(); CreateIdentityTable(); CreateNonIdentityTable(); + CreateUnorganizedTable(); } /// @@ -108,6 +109,7 @@ public static void Cleanup() connection.Truncate("[dbo].[CompleteTable]"); connection.Truncate("[sc].[IdentityTable]"); connection.Truncate("[dbo].[NonIdentityTable]"); + connection.Truncate("[dbo].[Unorganized Table]"); } } @@ -190,6 +192,28 @@ [Id] ASC } } + /// + /// Creates an unorganized table that has some non-alphanumeric fields. All fields are nullable. + /// + public static void CreateUnorganizedTable() + { + var commandText = @"IF (NOT EXISTS(SELECT 1 FROM [sys].[objects] WHERE type = 'U' AND name = 'Unorganized Table')) + BEGIN + CREATE TABLE [dbo].[Unorganized Table] + ( + [Id] BIGINT NOT NULL IDENTITY(1, 1), + [Session Id] UNIQUEIDENTIFIER NOT NULL, + [Column Int] INT NULL, + [Column/NVarChar] NVARCHAR(128) NULL, + [Column.DateTime] DATETIME2(7) NULL + ) ON [PRIMARY]; + END"; + using (var connection = new SqlConnection(ConnectionStringForRepoDb).EnsureOpen()) + { + connection.ExecuteNonQuery(commandText); + } + } + /// /// Creates a table that has a complete fields. All fields are nullable. /// diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/Others/QuotationTest.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/Others/QuotationTest.cs new file mode 100644 index 000000000..6a23019be --- /dev/null +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/Others/QuotationTest.cs @@ -0,0 +1,129 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RepoDb.Extensions; + +namespace RepoDb.UnitTests +{ + [TestClass] + public class QuotationTest + { + #region AsQuoted + + [TestMethod] + public void TestAsQuoted() + { + // Setup + var text = "Value"; + + // Act + var actual = text.AsQuoted(); + var expected = "[Value]"; + + // Assert + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void TestAsQuotedWithSpaces() + { + // Setup + var text = " Field Value "; + + // Act + var actual = text.AsQuoted(); + var expected = "[ Field Value ]"; + + // Assert + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void TestAsQuotedWithSpacesAsTrimmed() + { + // Setup + var text = " Field Value "; + + // Act + var actual = text.AsQuoted(true); + var expected = "[Field Value]"; + + // Assert + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void TestAsQuotedWithSeparators() + { + // Setup + var text = "Database.Schema.Name"; + + // Act + var actual = text.AsQuoted(true); + var expected = "[Database].[Schema].[Name]"; + + // Assert + Assert.AreEqual(expected, actual); + } + + #endregion + + #region AsUnQuoted + + [TestMethod] + public void TestAsUnquoted() + { + // Setup + var text = "[Value]"; + + // Act + var actual = text.AsUnquoted(); + var expected = "Value"; + + // Assert + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void TestAsUnquotedWithSpaces() + { + // Setup + var text = " [Field Value] "; + + // Act + var actual = text.AsUnquoted(); + var expected = " Field Value "; + + // Assert + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void TestAsUnquotedWithSpacesAsTrimmed() + { + // Setup + var text = " [Field Value] "; + + // Act + var actual = text.AsUnquoted(true); + var expected = "Field Value"; + + // Assert + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void TestAsUnquotedWithSeparators() + { + // Setup + var text = "[Database].[Schema].[Name]"; + + // Act + var actual = text.AsUnquoted(true); + var expected = "Database.Schema.Name"; + + // Assert + Assert.AreEqual(expected, actual); + } + + #endregion + } +} diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupCommonTest.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupCommonTest.cs index c071fc0f2..d0a5a3cda 100644 --- a/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupCommonTest.cs +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupCommonTest.cs @@ -5,6 +5,7 @@ namespace RepoDb.UnitTests { + [TestClass] public partial class QueryGroupTest { #region Constructor @@ -770,5 +771,52 @@ public void TestQueryGroupWithEqualAndInIdenticalFields() } #endregion + + #region NonAlphaNumericChars + + [TestMethod] + public void TestQueryGroupWithSpace() + { + // Setup + var queryGroup = new QueryGroup(new QueryField("Field 1", Operation.Equal, 1)); + + // Act + var actual = queryGroup.GetString(); + var expected = "([Field 1] = @Field_1)"; + + // Assert + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void TestQueryGroupWithSpaces() + { + // Setup + var queryGroup = new QueryGroup(new QueryField("Date Of Birth", Operation.Equal, 1)); + + // Act + var actual = queryGroup.GetString(); + var expected = "([Date Of Birth] = @Date_Of_Birth)"; + + // Assert + Assert.AreEqual(expected, actual); + } + + + [TestMethod] + public void TestQueryGroupWithInvalidChars() + { + // Setup + var queryGroup = new QueryGroup(new QueryField("Date.Of.Birth/BirthDay", Operation.Equal, 1)); + + // Act + var actual = queryGroup.GetString(); + var expected = "([Date.Of.Birth/BirthDay] = @Date_Of_Birth_BirthDay)"; + + // Assert + Assert.AreEqual(expected, actual); + } + + #endregion } } diff --git a/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseDynamicCommonTest.cs b/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseDynamicCommonTest.cs index bb6613d63..323aff650 100644 --- a/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseDynamicCommonTest.cs +++ b/RepoDb.Core/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseDynamicCommonTest.cs @@ -2,7 +2,6 @@ namespace RepoDb.UnitTests { - [TestClass] public partial class QueryGroupTest { [TestMethod] diff --git a/RepoDb.Core/RepoDb/Extensions/FieldExtension.cs b/RepoDb.Core/RepoDb/Extensions/FieldExtension.cs index aaff364fa..20798810f 100644 --- a/RepoDb.Core/RepoDb/Extensions/FieldExtension.cs +++ b/RepoDb.Core/RepoDb/Extensions/FieldExtension.cs @@ -27,8 +27,8 @@ private static string AsField(this Field field) // AsParameter private static string AsParameter(this Field field, int index = 0, string prefix = Constant.DefaultParameterPrefix) { - return index > 0 ? string.Concat(prefix, field.UnquotedName, "_", index) : - string.Concat(prefix, field.UnquotedName); + return index > 0 ? string.Concat(prefix, field.UnquotedName.AsAlphaNumeric(true), "_", index) : + string.Concat(prefix, field.UnquotedName.AsAlphaNumeric(true)); } // AsAliasField diff --git a/RepoDb.Core/RepoDb/Extensions/StringExtension.cs b/RepoDb.Core/RepoDb/Extensions/StringExtension.cs index b1b802b2a..0ced4be83 100644 --- a/RepoDb.Core/RepoDb/Extensions/StringExtension.cs +++ b/RepoDb.Core/RepoDb/Extensions/StringExtension.cs @@ -21,20 +21,54 @@ public static string Join(this IEnumerable strings, string separator) return string.Join(separator, strings); } + /// + /// Removes the non-alphanumeric characters. + /// + /// The string value where the non-alphanumeric characters will be removed. + /// The boolean value that indicates whether to trim the string before removing the non-alphanumeric characters. + /// The alphanumeric string. + internal static string AsAlphaNumeric(this string value, bool trim = false) + { + if (trim) + { + value = value.Trim(); + } + return Regex.Replace(value, @"[^a-zA-Z0-9]", "_"); + } + /// /// Removes the database quotes from the string. /// /// The string value where the database quotes will be removed. /// The boolean value that indicates whether to trim the string before unquoting. + /// The separator in which the quotes will be removed. /// The quoted string. - public static string AsUnquoted(this string value, bool trim = false) + public static string AsUnquoted(this string value, bool trim = false, string separator = ".") { if (trim) { value = value.Trim(); } - var v = value?.IndexOf(".") >= 0 ? value.Split(".".ToCharArray()).Last() : value; - return Regex.Replace(v, @"[\[\]']+", ""); + + if (string.IsNullOrEmpty(separator) || value.IndexOf(separator) < 0) + { + return value.AsUnquoted(); + } + else + { + var splitted = value.Split(separator.ToCharArray()); + return splitted.Select(s => s.AsUnquoted()).Join(separator); + } + } + + /// + /// Remove the quotes from the string. + /// + /// The string value where the database quotes will be removed. + /// + private static string AsUnquoted(this string value) + { + return Regex.Replace(value, @"[\[\]']+", ""); } /// @@ -42,26 +76,27 @@ public static string AsUnquoted(this string value, bool trim = false) /// /// The string value where the database quotes will be added. /// The boolean value that indicates whether to trim the string before quoting. + /// The separator in which the quotes will be placed. /// The quoted string. - public static string AsQuoted(this string value, bool trim = false) + public static string AsQuoted(this string value, bool trim = false, string separator = ".") { if (trim) { value = value.Trim(); } - if (value.IndexOf(".") < 0) + if (string.IsNullOrEmpty(separator) || value.IndexOf(separator) < 0) { return value.AsQuoted(); } else { - var splitted = value.Split(".".ToCharArray()); - return splitted.Select(s => s.AsQuoted()).Join("."); + var splitted = value.Split(separator.ToCharArray()); + return splitted.Select(s => s.AsQuoted()).Join(separator); } } /// - /// Adds a quotes to the string. + /// Add the quotes into the string. /// /// The string value where the database quotes will be added. /// diff --git a/RepoDb.Core/RepoDb/Field.cs b/RepoDb.Core/RepoDb/Field.cs index d44454093..102ebab74 100644 --- a/RepoDb.Core/RepoDb/Field.cs +++ b/RepoDb.Core/RepoDb/Field.cs @@ -36,7 +36,7 @@ public Field(string name, } // Set the name - Name = name.AsQuoted(true); + Name = name.AsQuoted(true, null); UnquotedName = name.AsUnquoted(true); // Set the type diff --git a/RepoDb.Core/RepoDb/Parameter.cs b/RepoDb.Core/RepoDb/Parameter.cs index ff93203d6..39e54b1ac 100644 --- a/RepoDb.Core/RepoDb/Parameter.cs +++ b/RepoDb.Core/RepoDb/Parameter.cs @@ -35,7 +35,7 @@ internal Parameter(string name, object value, bool prependUnderscore) } // Set the properties - Name = name.AsUnquoted(true); + Name = name.AsAlphaNumeric(true); Value = value; if (prependUnderscore) { diff --git a/RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs b/RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs index 6e95462d7..f41c3accf 100644 --- a/RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs +++ b/RepoDb.Core/RepoDb/Reflection/FunctionFactory.cs @@ -510,12 +510,13 @@ public static Action GetDataEntityDbCommandParameterSetterFu var parameterAssignments = new List(); // Parameter variables - var parameterVariable = Expression.Variable(typeOfDbParameter, string.Concat("parameter", field.UnquotedName)); + var parameterName = field.UnquotedName.AsAlphaNumeric(); + var parameterVariable = Expression.Variable(typeOfDbParameter, string.Concat("parameter", parameterName)); var parameterInstance = Expression.Call(commandParameterExpression, dbCommandCreateParameterMethod); parameterAssignments.Add(Expression.Assign(parameterVariable, parameterInstance)); // Set the name - var nameAssignment = Expression.Call(parameterVariable, dbParameterParameterNameSetMethod, Expression.Constant(field.UnquotedName)); + var nameAssignment = Expression.Call(parameterVariable, dbParameterParameterNameSetMethod, Expression.Constant(parameterName)); parameterAssignments.Add(nameAssignment); // Property instance @@ -616,7 +617,7 @@ public static Action GetDataEntityDbCommandParameterSetterFu if (isNullable == true) { // Identification of the DBNull - var valueVariable = Expression.Variable(typeOfObject, string.Concat("valueOf", field.UnquotedName)); + var valueVariable = Expression.Variable(typeOfObject, string.Concat("valueOf", parameterName)); var valueIsNull = Expression.Equal(valueVariable, Expression.Constant(null)); // Set the propert value @@ -847,25 +848,26 @@ public static Action GetDataEntityDbCommandParameterSetterFu var propertyVariable = (ParameterExpression)null; var propertyInstance = (Expression)null; var classProperty = (ClassProperty)null; + var propertyName = field.UnquotedName; // Set the proper assignments (property) if (typeOfEntity == typeOfObject) { - propertyVariable = Expression.Variable(typeOfPropertyInfo, string.Concat("property", field.UnquotedName)); + propertyVariable = Expression.Variable(typeOfPropertyInfo, string.Concat("property", propertyName)); propertyInstance = Expression.Call(Expression.Call(instanceVariable, objectGetTypeMethod), typeGetPropertyMethod, new[] { - Expression.Constant(field.UnquotedName), + Expression.Constant(propertyName), Expression.Constant(BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase) }); } else { - classProperty = entityProperties.First(property => property.GetUnquotedMappedName().ToLower() == field.UnquotedName.ToLower()); + classProperty = entityProperties.First(property => property.GetUnquotedMappedName().ToLower() == propertyName.ToLower()); if (classProperty != null) { - propertyVariable = Expression.Variable(classProperty.PropertyInfo.PropertyType, string.Concat("property", field.UnquotedName)); + propertyVariable = Expression.Variable(classProperty.PropertyInfo.PropertyType, string.Concat("property", propertyName)); propertyInstance = Expression.Property(instanceVariable, classProperty.PropertyInfo); } } @@ -998,13 +1000,14 @@ public static Action> GetDataEntitiesDbCommandParamete var parameterAssignments = new List(); // Parameter variables - var parameterVariable = Expression.Variable(typeOfDbParameter, string.Concat("parameter", field.UnquotedName)); + var parameterName = field.UnquotedName.AsAlphaNumeric(); + var parameterVariable = Expression.Variable(typeOfDbParameter, string.Concat("parameter", parameterName)); var parameterInstance = Expression.Call(commandParameterExpression, dbCommandCreateParameterMethod); parameterAssignments.Add(Expression.Assign(parameterVariable, parameterInstance)); // Set the name var nameAssignment = Expression.Call(parameterVariable, dbParameterParameterNameSetMethod, - Expression.Constant(entityIndex > 0 ? string.Concat(field.UnquotedName, "_", entityIndex) : field.UnquotedName)); + Expression.Constant(entityIndex > 0 ? string.Concat(parameterName, "_", entityIndex) : parameterName)); parameterAssignments.Add(nameAssignment); // Property instance @@ -1105,7 +1108,7 @@ public static Action> GetDataEntitiesDbCommandParamete if (isNullable == true) { // Identification of the DBNull - var valueVariable = Expression.Variable(typeOfObject, string.Concat("valueOf", field.UnquotedName)); + var valueVariable = Expression.Variable(typeOfObject, string.Concat("valueOf", parameterName)); var valueIsNull = Expression.Equal(valueVariable, Expression.Constant(null)); // Set the propert value @@ -1340,25 +1343,26 @@ public static Action> GetDataEntitiesDbCommandParamete var propertyVariable = (ParameterExpression)null; var propertyInstance = (Expression)null; var classProperty = (ClassProperty)null; + var propertyName = field.UnquotedName; // Set the proper assignments (property) if (typeOfEntity == typeOfObject) { - propertyVariable = Expression.Variable(typeOfPropertyInfo, string.Concat("property", field.UnquotedName)); + propertyVariable = Expression.Variable(typeOfPropertyInfo, string.Concat("property", propertyName)); propertyInstance = Expression.Call(Expression.Call(instanceVariable, objectGetTypeMethod), typeGetPropertyMethod, new[] { - Expression.Constant(field.UnquotedName), + Expression.Constant(propertyName), Expression.Constant(BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase) }); } else { - classProperty = entityProperties.FirstOrDefault(property => property.GetUnquotedMappedName().ToLower() == field.UnquotedName.ToLower()); + classProperty = entityProperties.FirstOrDefault(property => property.GetUnquotedMappedName().ToLower() == propertyName.ToLower()); if (classProperty != null) { - propertyVariable = Expression.Variable(classProperty.PropertyInfo.PropertyType, string.Concat("property", field.UnquotedName)); + propertyVariable = Expression.Variable(classProperty.PropertyInfo.PropertyType, string.Concat("property", propertyName)); propertyInstance = Expression.Property(instanceVariable, classProperty.PropertyInfo); } } @@ -1445,10 +1449,11 @@ public static Action GetDataEntityPropertySetterFromDbComman var dbParameterValueProperty = typeOfDbParameter.GetProperty("Value"); // Get the entity property - var property = typeOfEntity.GetProperty(field.UnquotedName).SetMethod; + var propertyName = field.UnquotedName.AsAlphaNumeric(); + var property = typeOfEntity.GetProperty(propertyName).SetMethod; // Get the command parameter - var name = parameterName ?? field.UnquotedName; + var name = parameterName ?? propertyName; var parameters = Expression.Property(dbCommandParameterExpression, dbCommandParametersProperty); var parameter = Expression.Call(parameters, dbParameterCollectionIndexerMethod, Expression.Constant(index > 0 ? string.Concat(name, "_", index) : name)); @@ -1526,9 +1531,10 @@ internal static void CreateDbCommandParametersFromFields(DbCommand command, { // Create the parameter var parameter = command.CreateParameter(); + var name = field.UnquotedName.AsAlphaNumeric(); // Set the property - parameter.ParameterName = index > 0 ? string.Concat(field.UnquotedName, "_", index) : field.UnquotedName; + parameter.ParameterName = index > 0 ? string.Concat(name, "_", index) : name; // Set the Direction parameter.Direction = direction; diff --git a/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/Helper.cs b/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/Helper.cs index 35d3a786a..0e9392843 100644 --- a/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/Helper.cs +++ b/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/Helper.cs @@ -476,6 +476,48 @@ public static TypeLevelMappedForStringEnumCompleteTable CreateTypeLevelMappedFor #endregion + #region UnorganizedTable + + /// + /// Creates a list of objects. + /// + /// The number of rows. + /// A list of objects. + public static List CreateUnorganizedTables(int count) + { + var tables = new List(); + for (var i = 0; i < count; i++) + { + var index = i + 1; + tables.Add(new UnorganizedTable + { + SessionId = Guid.NewGuid(), + ColumnDateTime2 = DateTime.UtcNow, + ColumnInt = index, + ColumnNVarChar = $"NVARCHAR{index}" + }); + } + return tables; + } + + /// + /// Creates an instance of object. + /// + /// A new created instance of object. + public static UnorganizedTable CreateUnorganizedTable() + { + var random = new Random(); + return new UnorganizedTable + { + SessionId = Guid.NewGuid(), + ColumnDateTime2 = DateTime.UtcNow, + ColumnInt = random.Next(int.MinValue, int.MaxValue), + ColumnNVarChar = Guid.NewGuid().ToString() + }; + } + + #endregion + #region Dynamics #region IdentityTable diff --git a/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/Models/UnorganizedTable.cs b/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/Models/UnorganizedTable.cs new file mode 100644 index 000000000..bb4c89220 --- /dev/null +++ b/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/Models/UnorganizedTable.cs @@ -0,0 +1,19 @@ +using RepoDb.Attributes; +using System; + +namespace RepoDb.IntegrationTests.Models +{ + [Map("[dbo].[Unorganized Table]")] + public class UnorganizedTable + { + public long Id { get; set; } + [Map("Session Id")] + public Guid SessionId { get; set; } + [Map("Column Int")] + public int? ColumnInt { get; set; } + [Map("Column/NVarChar")] + public string ColumnNVarChar { get; set; } + [Map("Column.DateTime")] + public DateTime? ColumnDateTime2 { get; set; } + } +} diff --git a/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/ObjectQuotationTest.cs b/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/ObjectQuotationTest.cs new file mode 100644 index 000000000..0b216dde2 --- /dev/null +++ b/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/ObjectQuotationTest.cs @@ -0,0 +1,315 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RepoDb.IntegrationTests.Models; +using RepoDb.IntegrationTests.Setup; +using System; +using System.Data.SqlClient; +using System.Linq; + +namespace RepoDb.IntegrationTests +{ + [TestClass] + public class ObjectQuotationTest + { + [TestInitialize] + public void Initialize() + { + Database.Initialize(); + Cleanup(); + } + + [TestCleanup] + public void Cleanup() + { + Database.Cleanup(); + } + + #region Delete + + [TestMethod] + public void TestDeleteObjectQuotation() + { + // Setup + var entities = Helper.CreateUnorganizedTables(10); + var last = entities.Last(); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var rowsInserted = connection.InsertAll(entities); + var deleteResult = connection.Delete(last.Id); + + // Assert + Assert.AreEqual(1, deleteResult); + } + } + + [TestMethod] + public void TestDeleteObjectQuotationViaNonAlphaNumericField() + { + // Setup + var entities = Helper.CreateUnorganizedTables(10); + var last = entities.Last(); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var rowsInserted = connection.InsertAll(entities); + var deleteResult = connection.Delete(e => e.SessionId == last.SessionId); + + // Assert + Assert.AreEqual(1, deleteResult); + } + } + + #endregion + + #region Insert + + [TestMethod] + public void TestInsertObjectQuotation() + { + // Setup + var entity = Helper.CreateUnorganizedTable(); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var id = connection.Insert(entity); + + // Assert + Assert.IsNotNull(id); + Assert.IsTrue(id > 0); + Assert.AreEqual(1, connection.CountAll()); + } + } + + #endregion + + #region InsertAll + + [TestMethod] + public void TestInsertAllObjectQuotation() + { + // Setup + var entities = Helper.CreateUnorganizedTables(10); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var rowsInserted = connection.InsertAll(entities); + + // Assert + Assert.AreEqual(entities.Count, rowsInserted); + Assert.AreEqual(entities.Count, connection.CountAll()); + } + } + + #endregion + + #region Merge + + [TestMethod] + public void TestMergeObjectQuotation() + { + // Setup + var entity = Helper.CreateUnorganizedTable(); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var id = connection.Merge(entity); + + // Assert + Assert.IsNotNull(id); + Assert.IsTrue(id > 0); + Assert.AreEqual(1, connection.CountAll()); + + // Setup + entity.ColumnDateTime2 = DateTime.UtcNow; + entity.ColumnInt = 2; + entity.ColumnNVarChar = Guid.NewGuid().ToString(); + + // Act + id = connection.Merge(entity); + var queryResult = connection.Query(id).First(); + + // Assert + Helper.AssertPropertiesEquality(entity, queryResult); + } + } + + #endregion + + #region MergeAll + + [TestMethod] + public void TestMergeAllObjectQuotation() + { + // Setup + var entities = Helper.CreateUnorganizedTables(10); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var rowsAffected = connection.MergeAll(entities); + + // Assert + Assert.AreEqual(entities.Count, rowsAffected); + Assert.AreEqual(entities.Count, connection.CountAll()); + + // Setup + entities.ForEach(entity => + { + entity.ColumnDateTime2 = DateTime.UtcNow; + entity.ColumnInt = 2; + entity.ColumnNVarChar = Guid.NewGuid().ToString(); + }); + + // Act + rowsAffected = connection.MergeAll(entities); + + // Assert + Assert.AreEqual(entities.Count, rowsAffected); + + // Act + var queryAllResult = connection.QueryAll(); + + // Assert + entities.ForEach(entity => Helper.AssertPropertiesEquality(entity, queryAllResult.First(item => item.Id == entity.Id))); + } + } + + #endregion + + #region Query + + [TestMethod] + public void TestQueryObjectQuotation() + { + // Setup + var entities = Helper.CreateUnorganizedTables(10); + var last = entities.Last(); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var rowsInserted = connection.InsertAll(entities); + var queryResult = connection.Query(last.Id).FirstOrDefault(); + + // Assert + Assert.IsNotNull(queryResult); + Helper.AssertPropertiesEquality(last, queryResult); + } + } + + [TestMethod] + public void TestQueryObjectQuotationViaNonAlphaNumericField() + { + // Setup + var entities = Helper.CreateUnorganizedTables(10); + var last = entities.Last(); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var rowsInserted = connection.InsertAll(entities); + var queryResult = connection.Query(e => e.SessionId == last.SessionId).FirstOrDefault(); + + // Assert + Assert.IsNotNull(queryResult); + Helper.AssertPropertiesEquality(last, queryResult); + } + } + + #endregion + + #region QueryAll + + [TestMethod] + public void TestQueryAllObjectQuotation() + { + // Setup + var entities = Helper.CreateUnorganizedTables(10); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var rowsInserted = connection.InsertAll(entities); + var queryAllResult = connection.QueryAll(); + + // Assert + Assert.AreEqual(entities.Count, queryAllResult.Count()); + entities.ForEach(entity => Helper.AssertPropertiesEquality(entity, queryAllResult.First(item => item.Id == entity.Id))); + } + } + + #endregion + + #region Update + + [TestMethod] + public void TestUpdateObjectQuotation() + { + // Setup + var entity = Helper.CreateUnorganizedTable(); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var id = connection.Insert(entity); + + // Setup + entity.ColumnDateTime2 = DateTime.UtcNow; + entity.ColumnInt = 2; + entity.ColumnNVarChar = Guid.NewGuid().ToString(); + + // Act + var updateReuslt = connection.Update(entity); + var queryResult = connection.Query(id).First(); + + // Assert + Assert.AreEqual(1, updateReuslt); + Helper.AssertPropertiesEquality(entity, queryResult); + } + } + + #endregion + + #region UpdateAll + + [TestMethod] + public void TestUpdateAllObjectQuotation() + { + // Setup + var entities = Helper.CreateUnorganizedTables(10); + + using (var connection = new SqlConnection(Database.ConnectionStringForRepoDb)) + { + // Act + var rowsAffected = connection.InsertAll(entities); + + // Setup + entities.ForEach(entity => + { + entity.ColumnDateTime2 = DateTime.UtcNow; + entity.ColumnInt = 2; + entity.ColumnNVarChar = Guid.NewGuid().ToString(); + }); + + // Act + rowsAffected = connection.UpdateAll(entities); + + // Assert + Assert.AreEqual(entities.Count, rowsAffected); + + // Act + var queryAllResult = connection.QueryAll(); + + // Assert + entities.ForEach(entity => Helper.AssertPropertiesEquality(entity, queryAllResult.First(item => item.Id == entity.Id))); + } + } + + #endregion + } +} diff --git a/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/RepoDb.IntegrationTests.csproj b/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/RepoDb.IntegrationTests.csproj index 6f2e07346..4246573ac 100644 --- a/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/RepoDb.IntegrationTests.csproj +++ b/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/RepoDb.IntegrationTests.csproj @@ -63,6 +63,7 @@ + @@ -74,6 +75,7 @@ + diff --git a/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/Setup/Database.cs b/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/Setup/Database.cs index e1aeaebba..96fa858b3 100644 --- a/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/Setup/Database.cs +++ b/RepoDb/RepoDb.Tests/RepoDb.IntegrationTests/Setup/Database.cs @@ -85,6 +85,7 @@ public static void CreateTables() CreateCompleteTable(); CreateIdentityTable(); CreateNonIdentityTable(); + CreateUnorganizedTable(); } /// @@ -108,6 +109,7 @@ public static void Cleanup() connection.Truncate("[dbo].[CompleteTable]"); connection.Truncate("[sc].[IdentityTable]"); connection.Truncate("[dbo].[NonIdentityTable]"); + connection.Truncate("[dbo].[Unorganized Table]"); } } @@ -190,6 +192,28 @@ [Id] ASC } } + /// + /// Creates an unorganized table that has some non-alphanumeric fields. All fields are nullable. + /// + public static void CreateUnorganizedTable() + { + var commandText = @"IF (NOT EXISTS(SELECT 1 FROM [sys].[objects] WHERE type = 'U' AND name = 'Unorganized Table')) + BEGIN + CREATE TABLE [dbo].[Unorganized Table] + ( + [Id] BIGINT NOT NULL IDENTITY(1, 1), + [Session Id] UNIQUEIDENTIFIER NOT NULL, + [Column Int] INT NULL, + [Column/NVarChar] NVARCHAR(128) NULL, + [Column.DateTime] DATETIME2(7) NULL + ) ON [PRIMARY]; + END"; + using (var connection = new SqlConnection(ConnectionStringForRepoDb).EnsureOpen()) + { + connection.ExecuteNonQuery(commandText); + } + } + /// /// Creates a table that has a complete fields. All fields are nullable. /// diff --git a/RepoDb/RepoDb.Tests/RepoDb.UnitTests/Others/QuotationTest.cs b/RepoDb/RepoDb.Tests/RepoDb.UnitTests/Others/QuotationTest.cs new file mode 100644 index 000000000..6a23019be --- /dev/null +++ b/RepoDb/RepoDb.Tests/RepoDb.UnitTests/Others/QuotationTest.cs @@ -0,0 +1,129 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using RepoDb.Extensions; + +namespace RepoDb.UnitTests +{ + [TestClass] + public class QuotationTest + { + #region AsQuoted + + [TestMethod] + public void TestAsQuoted() + { + // Setup + var text = "Value"; + + // Act + var actual = text.AsQuoted(); + var expected = "[Value]"; + + // Assert + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void TestAsQuotedWithSpaces() + { + // Setup + var text = " Field Value "; + + // Act + var actual = text.AsQuoted(); + var expected = "[ Field Value ]"; + + // Assert + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void TestAsQuotedWithSpacesAsTrimmed() + { + // Setup + var text = " Field Value "; + + // Act + var actual = text.AsQuoted(true); + var expected = "[Field Value]"; + + // Assert + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void TestAsQuotedWithSeparators() + { + // Setup + var text = "Database.Schema.Name"; + + // Act + var actual = text.AsQuoted(true); + var expected = "[Database].[Schema].[Name]"; + + // Assert + Assert.AreEqual(expected, actual); + } + + #endregion + + #region AsUnQuoted + + [TestMethod] + public void TestAsUnquoted() + { + // Setup + var text = "[Value]"; + + // Act + var actual = text.AsUnquoted(); + var expected = "Value"; + + // Assert + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void TestAsUnquotedWithSpaces() + { + // Setup + var text = " [Field Value] "; + + // Act + var actual = text.AsUnquoted(); + var expected = " Field Value "; + + // Assert + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void TestAsUnquotedWithSpacesAsTrimmed() + { + // Setup + var text = " [Field Value] "; + + // Act + var actual = text.AsUnquoted(true); + var expected = "Field Value"; + + // Assert + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void TestAsUnquotedWithSeparators() + { + // Setup + var text = "[Database].[Schema].[Name]"; + + // Act + var actual = text.AsUnquoted(true); + var expected = "Database.Schema.Name"; + + // Assert + Assert.AreEqual(expected, actual); + } + + #endregion + } +} diff --git a/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupCommonTest.cs b/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupCommonTest.cs index c071fc0f2..d0a5a3cda 100644 --- a/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupCommonTest.cs +++ b/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupCommonTest.cs @@ -5,6 +5,7 @@ namespace RepoDb.UnitTests { + [TestClass] public partial class QueryGroupTest { #region Constructor @@ -770,5 +771,52 @@ public void TestQueryGroupWithEqualAndInIdenticalFields() } #endregion + + #region NonAlphaNumericChars + + [TestMethod] + public void TestQueryGroupWithSpace() + { + // Setup + var queryGroup = new QueryGroup(new QueryField("Field 1", Operation.Equal, 1)); + + // Act + var actual = queryGroup.GetString(); + var expected = "([Field 1] = @Field_1)"; + + // Assert + Assert.AreEqual(expected, actual); + } + + [TestMethod] + public void TestQueryGroupWithSpaces() + { + // Setup + var queryGroup = new QueryGroup(new QueryField("Date Of Birth", Operation.Equal, 1)); + + // Act + var actual = queryGroup.GetString(); + var expected = "([Date Of Birth] = @Date_Of_Birth)"; + + // Assert + Assert.AreEqual(expected, actual); + } + + + [TestMethod] + public void TestQueryGroupWithInvalidChars() + { + // Setup + var queryGroup = new QueryGroup(new QueryField("Date.Of.Birth/BirthDay", Operation.Equal, 1)); + + // Act + var actual = queryGroup.GetString(); + var expected = "([Date.Of.Birth/BirthDay] = @Date_Of_Birth_BirthDay)"; + + // Assert + Assert.AreEqual(expected, actual); + } + + #endregion } } diff --git a/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseDynamicCommonTest.cs b/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseDynamicCommonTest.cs index bb6613d63..323aff650 100644 --- a/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseDynamicCommonTest.cs +++ b/RepoDb/RepoDb.Tests/RepoDb.UnitTests/QueryGroups/QueryGroupParseDynamicCommonTest.cs @@ -2,7 +2,6 @@ namespace RepoDb.UnitTests { - [TestClass] public partial class QueryGroupTest { [TestMethod] diff --git a/RepoDb/RepoDb.Tests/RepoDb.UnitTests/RepoDb.UnitTests.csproj b/RepoDb/RepoDb.Tests/RepoDb.UnitTests/RepoDb.UnitTests.csproj index 9d0b85bcf..46bfa15fb 100644 --- a/RepoDb/RepoDb.Tests/RepoDb.UnitTests/RepoDb.UnitTests.csproj +++ b/RepoDb/RepoDb.Tests/RepoDb.UnitTests/RepoDb.UnitTests.csproj @@ -142,6 +142,7 @@ + diff --git a/RepoDb/RepoDb/Extensions/FieldExtension.cs b/RepoDb/RepoDb/Extensions/FieldExtension.cs index aaff364fa..20798810f 100644 --- a/RepoDb/RepoDb/Extensions/FieldExtension.cs +++ b/RepoDb/RepoDb/Extensions/FieldExtension.cs @@ -27,8 +27,8 @@ private static string AsField(this Field field) // AsParameter private static string AsParameter(this Field field, int index = 0, string prefix = Constant.DefaultParameterPrefix) { - return index > 0 ? string.Concat(prefix, field.UnquotedName, "_", index) : - string.Concat(prefix, field.UnquotedName); + return index > 0 ? string.Concat(prefix, field.UnquotedName.AsAlphaNumeric(true), "_", index) : + string.Concat(prefix, field.UnquotedName.AsAlphaNumeric(true)); } // AsAliasField diff --git a/RepoDb/RepoDb/Extensions/StringExtension.cs b/RepoDb/RepoDb/Extensions/StringExtension.cs index b1b802b2a..be5cf3d35 100644 --- a/RepoDb/RepoDb/Extensions/StringExtension.cs +++ b/RepoDb/RepoDb/Extensions/StringExtension.cs @@ -22,50 +22,61 @@ public static string Join(this IEnumerable strings, string separator) } /// - /// Removes the database quotes from the string. + /// Removes the non-alphanumeric characters. /// - /// The string value where the database quotes will be removed. - /// The boolean value that indicates whether to trim the string before unquoting. - /// The quoted string. - public static string AsUnquoted(this string value, bool trim = false) + /// The string value where the non-alphanumeric characters will be removed. + /// The boolean value that indicates whether to trim the string before removing the non-alphanumeric characters. + /// The alphanumeric string. + internal static string AsAlphaNumeric(this string value, bool trim = false) { if (trim) { value = value.Trim(); } - var v = value?.IndexOf(".") >= 0 ? value.Split(".".ToCharArray()).Last() : value; - return Regex.Replace(v, @"[\[\]']+", ""); + return Regex.Replace(value, @"[^a-zA-Z0-9]", "_"); } /// - /// Adds a quotes to the string. + /// Unquotes a string. /// - /// The string value where the database quotes will be added. - /// The boolean value that indicates whether to trim the string before quoting. - /// The quoted string. - public static string AsQuoted(this string value, bool trim = false) + /// 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 unquoted string. + public static string AsUnquoted(this string value, bool trim = false, string separator = ".") { if (trim) { value = value.Trim(); } - if (value.IndexOf(".") < 0) + + if (string.IsNullOrEmpty(separator) || value.IndexOf(separator) < 0) { - return value.AsQuoted(); + return value.AsUnquoted(); } else { - var splitted = value.Split(".".ToCharArray()); - return splitted.Select(s => s.AsQuoted()).Join("."); + var splitted = value.Split(separator.ToCharArray()); + return splitted.Select(s => s.AsUnquoted()).Join(separator); } } /// - /// Adds a quotes to the string. + /// Quotes a string. /// - /// The string value where the database quotes will be added. - /// - private static string AsQuoted(this string value) + /// The string value to be quoted. + /// The quoted string. + public static string AsQuoted(this string value) { if (!value.StartsWith("[")) { @@ -78,6 +89,30 @@ private static string AsQuoted(this string value) return value; } + /// + /// Quotes a string. + /// + /// 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. + public static string AsQuoted(this string value, bool trim = false, string separator = ".") + { + if (trim) + { + value = value.Trim(); + } + if (string.IsNullOrEmpty(separator) || value.IndexOf(separator) < 0) + { + return value.AsQuoted(); + } + else + { + var splitted = value.Split(separator.ToCharArray()); + return splitted.Select(s => s.AsQuoted()).Join(separator); + } + } + // AsEnumerable internal static IEnumerable AsEnumerable(this string value) { diff --git a/RepoDb/RepoDb/Field.cs b/RepoDb/RepoDb/Field.cs index 053a342c4..110aa7310 100644 --- a/RepoDb/RepoDb/Field.cs +++ b/RepoDb/RepoDb/Field.cs @@ -35,7 +35,7 @@ public Field(string name, } // Set the name - Name = name.AsQuoted(true); + Name = name.AsQuoted(true, null); UnquotedName = name.AsUnquoted(true); // Set the type diff --git a/RepoDb/RepoDb/Parameter.cs b/RepoDb/RepoDb/Parameter.cs index ff93203d6..39e54b1ac 100644 --- a/RepoDb/RepoDb/Parameter.cs +++ b/RepoDb/RepoDb/Parameter.cs @@ -35,7 +35,7 @@ internal Parameter(string name, object value, bool prependUnderscore) } // Set the properties - Name = name.AsUnquoted(true); + Name = name.AsAlphaNumeric(true); Value = value; if (prependUnderscore) { diff --git a/RepoDb/RepoDb/Reflection/FunctionFactory.cs b/RepoDb/RepoDb/Reflection/FunctionFactory.cs index 6c21235fa..f43a6b901 100644 --- a/RepoDb/RepoDb/Reflection/FunctionFactory.cs +++ b/RepoDb/RepoDb/Reflection/FunctionFactory.cs @@ -510,12 +510,13 @@ public static Action GetDataEntityDbCommandParameterSetterFu var parameterAssignments = new List(); // Parameter variables - var parameterVariable = Expression.Variable(typeOfDbParameter, string.Concat("parameter", field.UnquotedName)); + var parameterName = field.UnquotedName.AsAlphaNumeric(); + var parameterVariable = Expression.Variable(typeOfDbParameter, string.Concat("parameter", parameterName)); var parameterInstance = Expression.Call(commandParameterExpression, dbCommandCreateParameterMethod); parameterAssignments.Add(Expression.Assign(parameterVariable, parameterInstance)); // Set the name - var nameAssignment = Expression.Call(parameterVariable, dbParameterParameterNameSetMethod, Expression.Constant(field.UnquotedName)); + var nameAssignment = Expression.Call(parameterVariable, dbParameterParameterNameSetMethod, Expression.Constant(parameterName)); parameterAssignments.Add(nameAssignment); // Property instance @@ -616,7 +617,7 @@ public static Action GetDataEntityDbCommandParameterSetterFu if (isNullable == true) { // Identification of the DBNull - var valueVariable = Expression.Variable(typeOfObject, string.Concat("valueOf", field.UnquotedName)); + var valueVariable = Expression.Variable(typeOfObject, string.Concat("valueOf", parameterName)); var valueIsNull = Expression.Equal(valueVariable, Expression.Constant(null)); // Set the propert value @@ -847,25 +848,26 @@ public static Action GetDataEntityDbCommandParameterSetterFu var propertyVariable = (ParameterExpression)null; var propertyInstance = (Expression)null; var classProperty = (ClassProperty)null; + var propertyName = field.UnquotedName; // Set the proper assignments (property) if (typeOfEntity == typeOfObject) { - propertyVariable = Expression.Variable(typeOfPropertyInfo, string.Concat("property", field.UnquotedName)); + propertyVariable = Expression.Variable(typeOfPropertyInfo, string.Concat("property", propertyName)); propertyInstance = Expression.Call(Expression.Call(instanceVariable, objectGetTypeMethod), typeGetPropertyMethod, new[] { - Expression.Constant(field.UnquotedName), + Expression.Constant(propertyName), Expression.Constant(BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase) }); } else { - classProperty = entityProperties.First(property => property.GetUnquotedMappedName().ToLower() == field.UnquotedName.ToLower()); + classProperty = entityProperties.First(property => property.GetUnquotedMappedName().ToLower() == propertyName.ToLower()); if (classProperty != null) { - propertyVariable = Expression.Variable(classProperty.PropertyInfo.PropertyType, string.Concat("property", field.UnquotedName)); + propertyVariable = Expression.Variable(classProperty.PropertyInfo.PropertyType, string.Concat("property", propertyName)); propertyInstance = Expression.Property(instanceVariable, classProperty.PropertyInfo); } } @@ -998,13 +1000,14 @@ public static Action> GetDataEntitiesDbCommandParamete var parameterAssignments = new List(); // Parameter variables - var parameterVariable = Expression.Variable(typeOfDbParameter, string.Concat("parameter", field.UnquotedName)); + var parameterName = field.UnquotedName.AsAlphaNumeric(); + var parameterVariable = Expression.Variable(typeOfDbParameter, string.Concat("parameter", parameterName)); var parameterInstance = Expression.Call(commandParameterExpression, dbCommandCreateParameterMethod); parameterAssignments.Add(Expression.Assign(parameterVariable, parameterInstance)); // Set the name var nameAssignment = Expression.Call(parameterVariable, dbParameterParameterNameSetMethod, - Expression.Constant(entityIndex > 0 ? string.Concat(field.UnquotedName, "_", entityIndex) : field.UnquotedName)); + Expression.Constant(entityIndex > 0 ? string.Concat(parameterName, "_", entityIndex) : parameterName)); parameterAssignments.Add(nameAssignment); // Property instance @@ -1105,7 +1108,7 @@ public static Action> GetDataEntitiesDbCommandParamete if (isNullable == true) { // Identification of the DBNull - var valueVariable = Expression.Variable(typeOfObject, string.Concat("valueOf", field.UnquotedName)); + var valueVariable = Expression.Variable(typeOfObject, string.Concat("valueOf", parameterName)); var valueIsNull = Expression.Equal(valueVariable, Expression.Constant(null)); // Set the propert value @@ -1340,25 +1343,26 @@ public static Action> GetDataEntitiesDbCommandParamete var propertyVariable = (ParameterExpression)null; var propertyInstance = (Expression)null; var classProperty = (ClassProperty)null; + var propertyName = field.UnquotedName; // Set the proper assignments (property) if (typeOfEntity == typeOfObject) { - propertyVariable = Expression.Variable(typeOfPropertyInfo, string.Concat("property", field.UnquotedName)); + propertyVariable = Expression.Variable(typeOfPropertyInfo, string.Concat("property", propertyName)); propertyInstance = Expression.Call(Expression.Call(instanceVariable, objectGetTypeMethod), typeGetPropertyMethod, new[] { - Expression.Constant(field.UnquotedName), + Expression.Constant(propertyName), Expression.Constant(BindingFlags.Instance | BindingFlags.Public | BindingFlags.IgnoreCase) }); } else { - classProperty = entityProperties.FirstOrDefault(property => property.GetUnquotedMappedName().ToLower() == field.UnquotedName.ToLower()); + classProperty = entityProperties.FirstOrDefault(property => property.GetUnquotedMappedName().ToLower() == propertyName.ToLower()); if (classProperty != null) { - propertyVariable = Expression.Variable(classProperty.PropertyInfo.PropertyType, string.Concat("property", field.UnquotedName)); + propertyVariable = Expression.Variable(classProperty.PropertyInfo.PropertyType, string.Concat("property", propertyName)); propertyInstance = Expression.Property(instanceVariable, classProperty.PropertyInfo); } } @@ -1445,10 +1449,11 @@ public static Action GetDataEntityPropertySetterFromDbComman var dbParameterValueProperty = typeOfDbParameter.GetProperty("Value"); // Get the entity property - var property = typeOfEntity.GetProperty(field.UnquotedName).SetMethod; + var propertyName = field.UnquotedName.AsAlphaNumeric(); + var property = typeOfEntity.GetProperty(propertyName).SetMethod; // Get the command parameter - var name = parameterName ?? field.UnquotedName; + var name = parameterName ?? propertyName; var parameters = Expression.Property(dbCommandParameterExpression, dbCommandParametersProperty); var parameter = Expression.Call(parameters, dbParameterCollectionIndexerMethod, Expression.Constant(index > 0 ? string.Concat(name, "_", index) : name)); @@ -1526,9 +1531,10 @@ internal static void CreateDbCommandParametersFromFields(DbCommand command, { // Create the parameter var parameter = command.CreateParameter(); + var name = field.UnquotedName.AsAlphaNumeric(); // Set the property - parameter.ParameterName = index > 0 ? string.Concat(field.UnquotedName, "_", index) : field.UnquotedName; + parameter.ParameterName = index > 0 ? string.Concat(name, "_", index) : name; // Set the Direction parameter.Direction = direction;