Skip to content

Conversation

mkolumb
Copy link

@mkolumb mkolumb commented Jan 19, 2022

Hello, I noticed some performance issue with insert many on big collection.
It looks like there are few reasons

  • unnecessary cast to list
  • using string operator instead of string builder
  • compilation as .net standard (or maybe another problem with build, insert from collection with 50k rows and 3 columns takes 15 seconds when using nuget 2.3.7 package, around 1.2 seconds when compiled directly for .net core 3.1)

I fixed first two things, but I didn't change target frameworks, I think it would be good to compile sql kata as multiple frameworks, including .net core 3.1, .net 5.0 and .net 6.0

What I changed

  • Add insert many overload
  • Use dictionary in insert clause
  • Fixed sql server compiler test runner (now every test class have their own instance and legacy pagination is used in tests)
  • Improved string build performance

Use dictionary in insert clause
Fixed sql server compiler test runner
Improved string build performance
@mkolumb mkolumb force-pushed the feature/compiler-performance branch from a36151d to 6f19ea8 Compare May 25, 2022 17:48
…erformance

# Conflicts:
#	QueryBuilder.Tests/Infrastructure/TestCompilersContainer.cs
@mkolumb
Copy link
Author

mkolumb commented Jun 22, 2022

@ahmad-moussawi Any chance to review?

@mkolumb
Copy link
Author

mkolumb commented Mar 28, 2023

@ahmad-moussawi

@wx0xm wx0xm requested a review from Copilot September 27, 2025 03:58
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request improves performance for insert operations, particularly for bulk inserts, by addressing inefficiencies in data structure usage and string building operations. The changes modernize the codebase to use more efficient data structures and operations.

  • Replaces separate Columns/Values lists with Dictionary-based data structure for better performance
  • Optimizes string building using StringBuilder instead of string concatenation
  • Adds new overload for bulk insert operations with KeyValuePair collections

Reviewed Changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
QueryBuilder/Query.Update.cs Updates to use Dictionary-based data structure and new extension methods
QueryBuilder/Query.Insert.cs Replaces column/value lists with Dictionary approach and adds new bulk insert overload
QueryBuilder/Helper.cs Optimizes JoinArray method using StringBuilder for better performance
QueryBuilder/Extensions/CollectionExtensions.cs New extension methods for merging keys/values and creating dictionaries
QueryBuilder/Compilers/OracleCompiler.cs Updates compiler to work with new Dictionary-based data structure
QueryBuilder/Compilers/Compiler.cs Major refactoring to use StringBuilder and Dictionary-based operations
QueryBuilder/Compilers/Compiler.Conditions.cs Optimizes condition compilation using StringBuilder
QueryBuilder/Clauses/InsertClause.cs Changes data structure from separate lists to single Dictionary
QueryBuilder.Tests/InsertTests.cs Adds test for new bulk insert functionality
QueryBuilder.Tests/Infrastructure/TestCompilersContainer.cs Fixes test isolation by using factory pattern for compiler instances

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

foreach (var item in array)
{
result.Add(item.ToString());
result.Append(item + glue);
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

String concatenation using the + operator within a loop creates unnecessary string objects. Use StringBuilder.Append() with separate calls for better performance.

Suggested change
result.Append(item + glue);
result.Append(item);
result.Append(glue);

Copilot uses AI. Check for mistakes.

Comment on lines +106 to +108
result.Length -= glue.Length;

return result.ToString().Trim();
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This approach will fail when the array is empty, causing result.Length to become negative. Add a check for empty arrays before attempting to modify the length.

Copilot uses AI. Check for mistakes.


for (var i = 0; i < keys.Count; i++)
{
data.Add(keys[i], values[i]);
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Potential IndexOutOfRangeException if values list is shorter than keys list. The validation should happen in this method or the caller should guarantee equal lengths.

Copilot uses AI. Check for mistakes.

Comment on lines +118 to +120
if (row.Keys.Count != row.Values.Count)
{
throw new InvalidOperationException($"{nameof(row.Keys)} count should be equal to each {nameof(row.Values)} entry count");
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This validation is incorrect for Dictionary objects. Dictionary.Keys.Count will always equal Dictionary.Values.Count by definition. This check should validate that the dictionary is not empty instead.

Suggested change
if (row.Keys.Count != row.Values.Count)
{
throw new InvalidOperationException($"{nameof(row.Keys)} count should be equal to each {nameof(row.Values)} entry count");
if (row.Count == 0)
{
throw new InvalidOperationException($"{nameof(row)} cannot be empty");

Copilot uses AI. Check for mistakes.

protected virtual SqlResult CompileRemainingInsertClauses(SqlResult ctx, string table, IEnumerable<InsertClause> inserts)
protected virtual SqlResult CompileRemainingInsertClauses(SqlResult ctx, string table, IReadOnlyCollection<InsertClause> inserts)
{
var sql = new StringBuilder(ctx.RawSql, inserts.Count - 1);
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StringBuilder constructor parameters are (string, int capacity), but you're passing (string, int length). The second parameter should be the initial capacity, not the count calculation. Use new StringBuilder(ctx.RawSql) or provide a proper capacity estimate.

Suggested change
var sql = new StringBuilder(ctx.RawSql, inserts.Count - 1);
var sql = new StringBuilder(ctx.RawSql);

Copilot uses AI. Check for mistakes.

SqlResult ctx, string table, IEnumerable<InsertClause> inserts)
SqlResult ctx, string table, IReadOnlyCollection<InsertClause> inserts)
{
var sql = new StringBuilder(ctx.RawSql, inserts.Count - 1);
Copy link

Copilot AI Sep 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same StringBuilder constructor issue as in Compiler.cs. The second parameter should be capacity, not a count calculation.

Suggested change
var sql = new StringBuilder(ctx.RawSql, inserts.Count - 1);
var sql = new StringBuilder(ctx.RawSql);

Copilot uses AI. Check for mistakes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant