From 91b3a56c852b5aec82153ecdc43c9d4f3c3d45e6 Mon Sep 17 00:00:00 2001 From: "DESKTOP-IOHGBT9\\AnyHDD" Date: Fri, 17 Oct 2025 22:03:16 +0300 Subject: [PATCH 1/3] Add parametrization in Delete --- QMap.SqlBuilder.Tests/SqlBuilderParseTests.cs | 8 ++--- QMap.SqlBuilder/QueryBuilderExtensions.cs | 8 +++-- QMap.SqlBuilder/StatementsBuilders.cs | 2 -- QMap.Tests/QMapConnectionExtensionTests.cs | 29 +++++++++++++++++++ QMap/QMapConnectionExtension.cs | 8 +++-- 5 files changed, 45 insertions(+), 10 deletions(-) diff --git a/QMap.SqlBuilder.Tests/SqlBuilderParseTests.cs b/QMap.SqlBuilder.Tests/SqlBuilderParseTests.cs index 1a48e00..46f06cd 100644 --- a/QMap.SqlBuilder.Tests/SqlBuilderParseTests.cs +++ b/QMap.SqlBuilder.Tests/SqlBuilderParseTests.cs @@ -151,7 +151,7 @@ public void BuildDeleteNoThrowsErrors() StatementsBuilders queryBuilder = new StatementsBuilders(new SqlDialectBase()); var sql = queryBuilder - .Delete() + .Delete(out var _) .From(typeof(TypesTestEntity)) .Build(); } @@ -165,7 +165,7 @@ public void BuildDeleteThrowInvalidOperationException() StatementsBuilders queryBuilder = new StatementsBuilders(new SqlDialectBase()); var sql = queryBuilder - .Delete() + .Delete(out var _) .Build(); }); } @@ -177,7 +177,7 @@ public void DeleteSqlBuidWitoutSyntaxErrosInAllParsers() StatementsBuilders queryBuilder = new(new TSqlDialect()); var sql = queryBuilder - .Delete() + .Delete(out var _) .From(typeof(TypesTestEntity)) .Build(); @@ -196,7 +196,7 @@ public void DeleteFullSqlBuidWitoutSyntaxErrosInAllParsers() StatementsBuilders queryBuilder = new(new SqlDialectBase()); var sql = queryBuilder - .Delete() + .Delete(out var _) .From(typeof(TypesTestEntity)) .Where((TypesTestEntity t) => t.Id < 0, out var parameters) .Build(); diff --git a/QMap.SqlBuilder/QueryBuilderExtensions.cs b/QMap.SqlBuilder/QueryBuilderExtensions.cs index 5561201..7b7c733 100644 --- a/QMap.SqlBuilder/QueryBuilderExtensions.cs +++ b/QMap.SqlBuilder/QueryBuilderExtensions.cs @@ -55,10 +55,14 @@ public static IUpdateBuilder Update(this IQueryBuilder queryBuilder, IQMap return builder; } - public static IDeleteBuilder Delete(this IQueryBuilder queryBuilder) + public static IDeleteBuilder Delete(this IQueryBuilder queryBuilder, out Dictionary parameters) { - return new DeleteBuilder(queryBuilder.SqlDialect) + var builder = new DeleteBuilder(queryBuilder.SqlDialect) .BuildDelete(); + + parameters = builder.Parameters; + + return builder; } public static string Build(this IQueryBuilder queryBuilder) diff --git a/QMap.SqlBuilder/StatementsBuilders.cs b/QMap.SqlBuilder/StatementsBuilders.cs index c8dac64..50dba2e 100644 --- a/QMap.SqlBuilder/StatementsBuilders.cs +++ b/QMap.SqlBuilder/StatementsBuilders.cs @@ -311,8 +311,6 @@ public DeleteBuilder(ISqlDialect sqlDialect) : base(sqlDialect) { } - public Dictionary Parameters => throw new NotImplementedException(); - private Type _entity = null; public string Build() diff --git a/QMap.Tests/QMapConnectionExtensionTests.cs b/QMap.Tests/QMapConnectionExtensionTests.cs index eb0e9d9..b00032d 100644 --- a/QMap.Tests/QMapConnectionExtensionTests.cs +++ b/QMap.Tests/QMapConnectionExtensionTests.cs @@ -496,5 +496,34 @@ public void Update_Should_Not_Pass_Drop_Statemant_When_Injection_In_Where() context.TypesTestEntity.Count(); }); } + + [Fact] + public void Delete_Should_Not_Pass_Drop_Statemant_When_Injection_In_Where() + { + var builder = new StatementsBuilders(new SqlDialectBase()); + + _connectionFactories.ForEach(c => + { + + var context = c.GetDbContext(); + + context.Database.EnsureCreated(); + + using var connection = c.Create(); + + connection.Open(); + + var entity = new Fixture() + .Build() + .Without(t => t.Id) + .Create(); + + context.TypesTestEntity.Add(entity); + context.SaveChanges(); + connection.Delete((TypesTestEntity t) => t.StringField != "\'DROP TABLE TypesTestEntity;--"); + + context.TypesTestEntity.Count(); + }); + } } } \ No newline at end of file diff --git a/QMap/QMapConnectionExtension.cs b/QMap/QMapConnectionExtension.cs index 873fa32..a47fde0 100644 --- a/QMap/QMapConnectionExtension.cs +++ b/QMap/QMapConnectionExtension.cs @@ -103,13 +103,17 @@ public static void Insert(this IQMapConnection connection, T entity) { var command = connection.CreateCommand(); var sql = new StatementsBuilders(connection.Dialect) - .Delete() + .Delete(out var parameters) .From(typeof(T)) - .Where(predicate, out var parameters) + .Where(predicate, out var parameters1) .Build(); + var allParameters = parameters.AsEnumerable().Concat(parameters1).ToDictionary((p) => p.Key, (p) => p.Value); + command.CommandText = sql; + connection.Dialect.BuildParameters(command, allParameters); + command.ExecuteNonQuery(); } } From 043e243eb0781c0e7338381d500e1b2ed1715d7e Mon Sep 17 00:00:00 2001 From: "DESKTOP-IOHGBT9\\AnyHDD" Date: Fri, 17 Oct 2025 22:18:18 +0300 Subject: [PATCH 2/3] Repace with FormattableString --- QMap.SqlBuilder/StatementsBuilders.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/QMap.SqlBuilder/StatementsBuilders.cs b/QMap.SqlBuilder/StatementsBuilders.cs index 50dba2e..8e5687f 100644 --- a/QMap.SqlBuilder/StatementsBuilders.cs +++ b/QMap.SqlBuilder/StatementsBuilders.cs @@ -109,14 +109,16 @@ public string Sql public IFromBuilder BuildFrom(ISelectBuilder quryBuilder, Type entity, params Type[] entities) { - this.Sql += $"{quryBuilder.Sql}" + $" from {entity.Name} " + _aliases.GetOrAdd(entity.Name, (ak) => NameToAlias(entity.Name)); + this.Sql += FormattableStringFactory + .Create("{0} from {1} {2}", quryBuilder.Sql, entity.Name, _aliases.GetOrAdd(entity.Name, (ak) => NameToAlias(entity.Name))); return this; } public IFromBuilder BuildFrom(IDeleteBuilder quryBuilder, Type entity, params Type[] entities) { - this.Sql += $"{quryBuilder.Sql}" + $" from {entity.Name} "; + this.Sql += FormattableStringFactory + .Create("{0} from {1}", quryBuilder.Sql, entity.Name); return this; } From 6b771fb2929a56299e428d4eb6c4d19229a28157 Mon Sep 17 00:00:00 2001 From: "DESKTOP-IOHGBT9\\AnyHDD" Date: Sun, 21 Dec 2025 15:09:02 +0300 Subject: [PATCH 3/3] Add normal property selection to insert --- QMap.SqlBuilder.Tests/SqlBuilderParseTests.cs | 2 +- .../Abstractions/IInsertBuilder.cs | 5 ++-- QMap.SqlBuilder/QueryBuilderExtensions.cs | 4 +-- QMap.SqlBuilder/StatementsBuilders.cs | 27 ++++++++++++------- QMap.Tests/QMapConnectionExtensionTests.cs | 3 +-- QMap/QMapConnectionExtension.cs | 27 ++++++++++++++++++- 6 files changed, 51 insertions(+), 17 deletions(-) diff --git a/QMap.SqlBuilder.Tests/SqlBuilderParseTests.cs b/QMap.SqlBuilder.Tests/SqlBuilderParseTests.cs index 46f06cd..0da845e 100644 --- a/QMap.SqlBuilder.Tests/SqlBuilderParseTests.cs +++ b/QMap.SqlBuilder.Tests/SqlBuilderParseTests.cs @@ -84,7 +84,7 @@ public void FullSqlInsertBuildWitoutSyntaxErrosInAllParsers() .Create(); var sql = queryBuilder - .BuildInsert(connectionFake, out var _, entity, (p) => p.Name == "Id"); + .BuildInsert(connectionFake, out var _, entity, (p) => p.Id); _parsers.ToList().ForEach(p => { diff --git a/QMap.SqlBuilder/Abstractions/IInsertBuilder.cs b/QMap.SqlBuilder/Abstractions/IInsertBuilder.cs index 5032993..bb0d085 100644 --- a/QMap.SqlBuilder/Abstractions/IInsertBuilder.cs +++ b/QMap.SqlBuilder/Abstractions/IInsertBuilder.cs @@ -1,10 +1,11 @@ -using System.Reflection; +using System.Linq.Expressions; +using System.Reflection; namespace QMap.SqlBuilder.Abstractions { public interface IInsertBuilder : IQueryBuilder { IInsertBuilder BuildInsert(T entity); - public IInsertBuilder BuildInsertExcept(T entity, Func exceptPropsFilter); + public IInsertBuilder BuildInsertExcept(T entity, Expression> exceptPropsFilter); } } diff --git a/QMap.SqlBuilder/QueryBuilderExtensions.cs b/QMap.SqlBuilder/QueryBuilderExtensions.cs index 7b7c733..76bd87e 100644 --- a/QMap.SqlBuilder/QueryBuilderExtensions.cs +++ b/QMap.SqlBuilder/QueryBuilderExtensions.cs @@ -70,10 +70,10 @@ public static string Build(this IQueryBuilder queryBuilder) return queryBuilder.Build(); } - public static string BuildInsert(this IQueryBuilder queryBuilder, IQMapConnection connection, out Dictionary parameters, T entity, Func exceptProperty) + public static string BuildInsert(this IQueryBuilder queryBuilder, IQMapConnection connection, out Dictionary parameters, T entity, Expression> exceptProp) { var builder = new InsertBuilder(queryBuilder.SqlDialect) - .BuildInsertExcept(entity, exceptProperty); + .BuildInsertExcept(entity, exceptProp); parameters = builder.Parameters; diff --git a/QMap.SqlBuilder/StatementsBuilders.cs b/QMap.SqlBuilder/StatementsBuilders.cs index 8e5687f..2fabd1c 100644 --- a/QMap.SqlBuilder/StatementsBuilders.cs +++ b/QMap.SqlBuilder/StatementsBuilders.cs @@ -205,6 +205,7 @@ public string Build() public class InsertBuilder : StatementsBuilders, IInsertBuilder { + private IEnumerable _properties; public InsertBuilder(ISqlDialect sqlDialect) : base(sqlDialect) { } @@ -228,11 +229,19 @@ public IInsertBuilder BuildInsert(T entity) return this; } - public IInsertBuilder BuildInsertExcept(T entity, Func exceptPropsFilter) + public IInsertBuilder BuildInsertExcept(T entity, Expression> exceptProp) { - var columns = BuildColumns(exceptPropsFilter); + MemberInfo? excludedMember = null; + + if(exceptProp.Body is MemberExpression member) + { + excludedMember = member.Member; + } + + var columns = BuildColumns(excludedMember); var values = BuildValues(entity, columns); + Sql = $"insert into {typeof(T).Name} " + $"({columns.Aggregate((c1, c2) => $"{c1},{c2}")})" + "values" @@ -241,27 +250,27 @@ public IInsertBuilder BuildInsertExcept(T entity, Func ex return this; } - private IEnumerable BuildColumns(Func? exceptPropsFilter = null) + private IEnumerable BuildColumns(MemberInfo? exceptProp = null) { - var properties = typeof(T) + _properties = typeof(T) .GetProperties(BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.SetProperty | BindingFlags.Instance) .AsEnumerable(); - if (exceptPropsFilter != null) + if (exceptProp != null) { - properties = properties.Where(p => !exceptPropsFilter.Invoke(p)); + _properties = _properties.Where(p => p.Name != exceptProp.Name); } - return properties + return _properties .Select(p => p.Name); } private IEnumerable BuildValues(T entity, IEnumerable columns) { - var properties = typeof(T) + _properties = typeof(T) .GetProperties(BindingFlags.Public | BindingFlags.GetProperty | BindingFlags.SetProperty @@ -269,7 +278,7 @@ private IEnumerable BuildValues(T entity, IEnumerable columns .AsEnumerable(); #nullable disable - return properties + return _properties .Where(p => columns.Contains(p.Name)) .Select(p => { diff --git a/QMap.Tests/QMapConnectionExtensionTests.cs b/QMap.Tests/QMapConnectionExtensionTests.cs index b00032d..bf0f158 100644 --- a/QMap.Tests/QMapConnectionExtensionTests.cs +++ b/QMap.Tests/QMapConnectionExtensionTests.cs @@ -229,8 +229,7 @@ public void InsertWithoutPropertyExecutesWithoutErrors() connection.Open(); - connection.Insert(entity, p => p.Name == "Id"); - + connection.Insert(entity, p => p.Id); connection.Close(); context.Database.EnsureDeleted(); diff --git a/QMap/QMapConnectionExtension.cs b/QMap/QMapConnectionExtension.cs index a47fde0..17efc18 100644 --- a/QMap/QMapConnectionExtension.cs +++ b/QMap/QMapConnectionExtension.cs @@ -6,6 +6,7 @@ using System.Data; using System.Linq.Expressions; using System.Reflection; +using System.Runtime.CompilerServices; namespace QMap { @@ -34,6 +35,30 @@ public static class QMapConnectionExtension return queryMapper.Map(command.ExecuteReader()); } + /// + /// Execute query and map to + /// + /// + /// Connection + /// Qery text + /// Custom mapper if required, else uses base implementation"/> + /// + public static IEnumerable Query(this IQMapConnection connection, string sql, object[] parameters, IEntityMapper? customMapper = null) where T : class, new() + { + var mapper = _mappersCache.GetOrAdd(typeof(T), + (customMapper is null ? new EntityMapper() : customMapper)); + + var queryMapper = new QueryMapperBase(mapper); + + var parametrizedString = FormattableStringFactory.Create(sql, parameters); + + var command = connection.CreateCommand(); + + command.CommandText = parametrizedString.ToString(); + + return queryMapper.Map(command.ExecuteReader()); + } + public static IEnumerable Where(this IQMapConnection connection, LambdaExpression predicate, IEntityMapper? customMapper = null) where T : class, new() { var mapper = _mappersCache.GetOrAdd(typeof(T), @@ -55,7 +80,7 @@ public static class QMapConnectionExtension return queryMapper.Map(command.ExecuteReader()); } - public static void Insert(this IQMapConnection connection, T entity, Func exceptProperty) + public static void Insert(this IQMapConnection connection, T entity, Expression> exceptProperty) { var command = connection.CreateCommand(); var sql = new StatementsBuilders(connection.Dialect)