Skip to content

Commit

Permalink
Уменьшены аллокации, добавлены тесты, в.2.0.1
Browse files Browse the repository at this point in the history
  • Loading branch information
a-postx committed Sep 19, 2023
1 parent b21d0c6 commit 89d2769
Show file tree
Hide file tree
Showing 6 changed files with 231 additions and 37 deletions.
2 changes: 1 addition & 1 deletion src/Delobytes.NetCore.EntityReportGeneration.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Version>2.0.0.0</Version>
<Version>2.0.1.0</Version>
<LangVersion>latest</LangVersion>
<Authors>Алексей Якубин</Authors>
<Company>Делобайты</Company>
Expand Down
47 changes: 23 additions & 24 deletions src/EntityReportGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ private DataTable ConvertObjectsToDataTableForCsv<T>(IEnumerable<T> items, strin
items = items ?? throw new ArgumentNullException(nameof(items));

DataTable result = new DataTable(tableName);
StringBuilder sb = new StringBuilder();

try
{
Expand All @@ -177,44 +178,42 @@ private DataTable ConvertObjectsToDataTableForCsv<T>(IEnumerable<T> items, strin

for (int i = 0; i < properties.Length; i++)
{
object? columnValue;
object? value = properties[i].GetValue(item, null);

Type[]? interfaces = properties[i].PropertyType.GetInterfaces();

if (interfaces.Contains(typeof(IEnumerable))
&& properties[i].PropertyType.Name != "String"
&& _options.DetailedEnumerables)
if (properties[i].PropertyType == typeof(bool)
|| properties[i].PropertyType == typeof(int)
|| properties[i].PropertyType == typeof(string)
|| properties[i].PropertyType == typeof(Guid))
{
object? value = properties[i].GetValue(item, null);
dataRow[i] = ConvertToString(value, stringToCleanup);
continue;
}

StringBuilder sb = value is IList list ? new StringBuilder(list.Count) : new StringBuilder();
if (_options.DetailedEnumerables && value is IEnumerable enumerable)
{
sb.Clear();
bool firstElement = true;

if (properties[i].GetValue(item, null) is IEnumerable enumerable)
foreach (object element in enumerable)
{
foreach (object element in enumerable)
if (firstElement)
{
if (firstElement)
{
sb.Append(element.ToString());
firstElement = false;
}
else
{
sb.Append(',');
sb.Append(element);
}
firstElement = false;
}
else
{
sb.Append(',');
}

sb.Append(element.ToString());
}

columnValue = sb.ToString();
dataRow[i] = ConvertToString(sb.ToString(), stringToCleanup);
}
else
{
columnValue = properties[i].GetValue(item, null);
dataRow[i] = ConvertToString(value, stringToCleanup);
}

dataRow[i] = ConvertToString(columnValue, stringToCleanup);
}

result.Rows.Add(dataRow);
Expand Down
29 changes: 29 additions & 0 deletions test/BenchmarkTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Running;
using Xunit.Abstractions;

namespace Delobytes.NetCore.EntityReportGeneration.Tests;
public class BenchmarkTests
{
private readonly ITestOutputHelper _output;

public BenchmarkTests(ITestOutputHelper output)
{
_output = output;
}

[Fact]
public void Run_Benchmarks()
{
AccumulationLogger logger = new AccumulationLogger();
ManualConfig config = ManualConfig.Create(DefaultConfig.Instance)
.AddLogger(logger)
.WithOptions(ConfigOptions.DisableOptimizationsValidator);

BenchmarkRunner.Run<GeneratorBechmarkTests>(config);

_output.WriteLine(logger.GetLog());
}

}
1 change: 1 addition & 0 deletions test/Delobytes.NetCore.EntityReportGeneration.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.8" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.1" />
<PackageReference Include="FluentAssertions" Version="6.7.0" />
<PackageReference Include="Moq" Version="4.18.2" />
Expand Down
72 changes: 60 additions & 12 deletions test/EntityReportGeneratorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,27 @@ private List<ObjectWithNullableEnumerable> GetNullableEnumerableEntities()

return entitiesList;
}

private List<ObjectWithEnumerable> Get100kEnumerableEntities()
{
List<ObjectWithEnumerable> entitiesList = new List<ObjectWithEnumerable>(99999);

for (int i = 0; i < 99999; i++)
{
string[] strings = new string[2];
strings.SetValue("string1 " + i, 0);
strings.SetValue("string2 " + i, 1);

ObjectWithEnumerable obj = new ObjectWithEnumerable
{
Id = i, Name = "Name " + i, IsDeleted = (i % 2) == 0, GuidProp = Guid.NewGuid(), Properties = strings
};

entitiesList.Add(obj);
}

return entitiesList;
}
#endregion


Expand Down Expand Up @@ -531,11 +552,11 @@ public void EntityReportGenerator_GenerateCsvContentSuccessfully_WithNullableLis
ex.Should().BeNull();
content.Should().NotBeNull();
content.Should().Contain(CsvDelimiter);
content.Should().Contain(nameof(ObjectWithNullableEnumerable.Id));
content.Should().Contain(nameof(ObjectWithNullableEnumerable.IsDeleted));
content.Should().Contain(nameof(ObjectWithNullableEnumerable.Name));
content.Should().Contain(nameof(ObjectWithNullableEnumerable.GuidProp));
content.Should().Contain(nameof(ObjectWithNullableEnumerable.Properties));
content.Should().Contain(nameof(ObjectWithNullableList.Id));
content.Should().Contain(nameof(ObjectWithNullableList.IsDeleted));
content.Should().Contain(nameof(ObjectWithNullableList.Name));
content.Should().Contain(nameof(ObjectWithNullableList.GuidProp));
content.Should().Contain(nameof(ObjectWithNullableList.Properties));
content.Should().Contain(objectList[0].Id.ToString());
content.Should().Contain(objectList[1].Id.ToString());
content.Should().Contain(objectList[0].IsDeleted.ToString());
Expand Down Expand Up @@ -566,11 +587,11 @@ public void EntityReportGenerator_GenerateCsvContentSuccessfully_WithEnumerable(
ex.Should().BeNull();
content.Should().NotBeNull();
content.Should().Contain(CsvDelimiter);
content.Should().Contain(nameof(ObjectWithNullableEnumerable.Id));
content.Should().Contain(nameof(ObjectWithNullableEnumerable.IsDeleted));
content.Should().Contain(nameof(ObjectWithNullableEnumerable.Name));
content.Should().Contain(nameof(ObjectWithNullableEnumerable.GuidProp));
content.Should().Contain(nameof(ObjectWithNullableEnumerable.Properties));
content.Should().Contain(nameof(ObjectWithEnumerable.Id));
content.Should().Contain(nameof(ObjectWithEnumerable.IsDeleted));
content.Should().Contain(nameof(ObjectWithEnumerable.Name));
content.Should().Contain(nameof(ObjectWithEnumerable.GuidProp));
content.Should().Contain(nameof(ObjectWithEnumerable.Properties));
content.Should().Contain(objectList[0].Id.ToString());
content.Should().Contain(objectList[1].Id.ToString());
content.Should().Contain(objectList[0].IsDeleted.ToString());
Expand Down Expand Up @@ -636,7 +657,7 @@ public void EntityReportGenerator_GenerateCsvContentSuccessfully_WithDetailedEnu
ex.Should().BeNull();
content.Should().NotBeNull();
content.Should().Contain(CsvDelimiter);
content.Should().Contain(nameof(ObjectWithNullableEnumerable.Properties));
content.Should().Contain(nameof(ObjectWithEnumerable.Properties));
content.Should().Contain(string.Join(",", objectList[0].Properties!));
content.Should().Contain(Environment.NewLine);
}
Expand All @@ -659,8 +680,35 @@ public void EntityReportGenerator_GenerateCsvContentSuccessfully_WithListAndDeta
ex.Should().BeNull();
content.Should().NotBeNull();
content.Should().Contain(CsvDelimiter);
content.Should().Contain(nameof(ObjectWithNullableEnumerable.Properties));
content.Should().Contain(nameof(ObjectWithList.Properties));
content.Should().Contain(string.Join(",", objectList[0].Properties!));
content.Should().Contain(Environment.NewLine);
}

[Fact]
public void EntityReportGenerator_GenerateCsvContentSuccessfully_WithALotOfEntities()
{
IEntityReportGenerator generator = GetReportGenerator();
List<ObjectWithEnumerable> objectList = Get100kEnumerableEntities();

string? content = null;

Action execute = () =>
{
content = generator.GenerateCsvContent(objectList);
};

Exception ex = Record.Exception(execute);

ex.Should().BeNull();
content.Should().NotBeNull();
content.Should().Contain(CsvDelimiter);
content.Should().Contain(objectList[0].Name);
content.Should().Contain(objectList[0].IsDeleted.ToString());
content.Should().Contain(objectList[0].Name);
content.Should().Contain(objectList[0].GuidProp.ToString());
content.Should().Contain(objectList[0].Properties!.ToString()!.Replace("`", ""));
content.Should().Contain(nameof(ObjectWithEnumerable.Properties));
content.Should().Contain(Environment.NewLine);
}
}
117 changes: 117 additions & 0 deletions test/GeneratorBechmarkTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;

namespace Delobytes.NetCore.EntityReportGeneration.Tests;

[MemoryDiagnoser]
public class GeneratorBechmarkTests
{
private static readonly Action<EntityReportGeneratorOptions> RegularOptions = options =>
{
options.CsvDelimiter = "`";
};
private static readonly Action<EntityReportGeneratorOptions> DetailedOptions = options =>
{
options.CsvDelimiter = "`";
options.DetailedEnumerables = true;
};

[GlobalSetup]
public void Setup()
{

}

private List<ObjectWithEnumerable> Get100kEntities()
{
List<ObjectWithEnumerable> entitiesList = new List<ObjectWithEnumerable>(99999);

for (int i = 0; i < 99999; i++)
{
string[] strings = new string[2];
strings.SetValue("string1 " + i, 0);
strings.SetValue("string2 " + i, 1);

ObjectWithEnumerable obj = new ObjectWithEnumerable
{
Id = i,
Name = "Name " + i,
IsDeleted = (i % 2) == 0,
GuidProp = Guid.NewGuid(),
Properties = strings
};

entitiesList.Add(obj);
}

return entitiesList;
}

private List<ObjectWithEnumerable> Get10kEntitiesWith18Items()
{
List<ObjectWithEnumerable> entitiesList = new List<ObjectWithEnumerable>(9999);

for (int i = 0; i < 9999; i++)
{
string[] strings = new string[18];
strings.SetValue("string1 " + i, 0);
strings.SetValue("string2 " + i, 1);
strings.SetValue("string3 " + i, 2);
strings.SetValue("string4 " + i, 3);
strings.SetValue("string5 " + i, 4);
strings.SetValue("string6 " + i, 5);
strings.SetValue("string7 " + i, 6);
strings.SetValue("string8 " + i, 7);
strings.SetValue("string9 " + i, 8);
strings.SetValue("string10 " + i, 9);
strings.SetValue("string11 " + i, 10);
strings.SetValue("string12 " + i, 11);
strings.SetValue("string13 " + i, 12);
strings.SetValue("string14 " + i, 13);
strings.SetValue("string15 " + i, 14);
strings.SetValue("string16 " + i, 15);
strings.SetValue("string17 " + i, 16);
strings.SetValue("string18 " + i, 17);

ObjectWithEnumerable obj = new ObjectWithEnumerable
{
Id = i,
Name = "Name " + i,
IsDeleted = (i % 2) == 0,
GuidProp = Guid.NewGuid(),
Properties = strings
};

entitiesList.Add(obj);
}

return entitiesList;
}

[Benchmark]
public void Large_1M_Entities_Exported()
{
WebApplicationBuilder builder = WebApplication.CreateBuilder();
builder.Services.AddEntityReportGenerator(RegularOptions);
WebApplication app = builder.Build();
IEntityReportGenerator generator = app.Services.GetRequiredService<IEntityReportGenerator>();

List<ObjectWithEnumerable> objectList = Get100kEntities();

generator.GenerateCsvContent(objectList);
}

[Benchmark]
public void Medium_10k_Entities_With18items_InDetails_Exported()
{
WebApplicationBuilder builder = WebApplication.CreateBuilder();
builder.Services.AddEntityReportGenerator(DetailedOptions);
WebApplication app = builder.Build();
IEntityReportGenerator generator = app.Services.GetRequiredService<IEntityReportGenerator>();

List<ObjectWithEnumerable> objectList = Get10kEntitiesWith18Items();

generator.GenerateCsvContent(objectList);
}
}

0 comments on commit 89d2769

Please sign in to comment.