diff --git a/QMap.SqlBuilder.Tests/SqlBuilderParseTests.cs b/QMap.SqlBuilder.Tests/SqlBuilderParseTests.cs index 1a48e00..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 => { @@ -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/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 5561201..76bd87e 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) @@ -66,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 c8dac64..2fabd1c 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; } @@ -203,6 +205,7 @@ public string Build() public class InsertBuilder : StatementsBuilders, IInsertBuilder { + private IEnumerable _properties; public InsertBuilder(ISqlDialect sqlDialect) : base(sqlDialect) { } @@ -226,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" @@ -239,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 @@ -267,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 => { @@ -311,8 +322,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..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(); @@ -496,5 +495,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..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) @@ -103,13 +128,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(); } }