Optimize invoice performance with indexes and DTO

Added indexes for `ImportedAt` and a composite key on
`InvoiceNumber` and `SellerTaxId` to improve query performance.
Updated `AppDbContext` and EF Core migrations to reflect these
changes. Introduced `ZugferdInvoiceListDto` to optimize memory
usage by excluding large fields like `RawXml`. Updated the
frontend (`Index.cshtml`) and backend (`Index.cshtml.cs`) to use
the new DTO for better performance.
This commit is contained in:
OlgunR
2026-06-02 13:45:57 +02:00
parent 2ae2bbdaf6
commit 03599addb8
7 changed files with 236 additions and 4 deletions

View File

@@ -6,4 +6,27 @@ namespace DXApp.TemplateKitProject.Data;
public class AppDbContext(DbContextOptions<AppDbContext> options) : DbContext(options)
{
public DbSet<ZugferdInvoice> ZugferdInvoices { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ZugferdInvoice>(entity =>
{
// Index für Performance-Optimierung der Rechnungsliste
entity.HasIndex(e => e.ImportedAt)
.HasDatabaseName("IX_ZugferdInvoices_ImportedAt");
// Index für Duplikatprüfung
entity.HasIndex(e => new { e.InvoiceNumber, e.SellerTaxId })
.HasDatabaseName("IX_ZugferdInvoices_InvoiceNumber_SellerTaxId");
// Decimal-Präzision explizit festlegen (behebt EF Core Warnung)
entity.Property(e => e.TotalAmount)
.HasColumnType("decimal(18,2)");
entity.Property(e => e.TaxAmount)
.HasColumnType("decimal(18,2)");
});
}
}

View File

@@ -0,0 +1,91 @@
// <auto-generated />
using System;
using DXApp.TemplateKitProject.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace DXApp.TemplateKitProject.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20260602114123_AddPerformanceIndexes")]
partial class AddPerformanceIndexes
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "8.0.8")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("DXApp.TemplateKitProject.Models.ZugferdInvoice", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
b.Property<string>("BuyerName")
.HasColumnType("nvarchar(max)");
b.Property<string>("CurrencyCode")
.HasColumnType("nvarchar(max)");
b.Property<string>("GuidelineId")
.HasColumnType("nvarchar(max)");
b.Property<string>("Iban")
.HasColumnType("nvarchar(max)");
b.Property<DateTime>("ImportedAt")
.HasColumnType("datetime2");
b.Property<DateTime>("InvoiceDate")
.HasColumnType("datetime2");
b.Property<string>("InvoiceNumber")
.HasColumnType("nvarchar(450)");
b.Property<string>("RawXml")
.HasColumnType("nvarchar(max)");
b.Property<string>("ResultFilePath")
.HasColumnType("nvarchar(max)");
b.Property<string>("SellerName")
.HasColumnType("nvarchar(max)");
b.Property<string>("SellerTaxId")
.HasColumnType("nvarchar(450)");
b.Property<string>("SourceType")
.HasColumnType("nvarchar(max)");
b.Property<decimal>("TaxAmount")
.HasColumnType("decimal(18,2)");
b.Property<decimal>("TotalAmount")
.HasColumnType("decimal(18,2)");
b.HasKey("Id");
b.HasIndex("ImportedAt")
.HasDatabaseName("IX_ZugferdInvoices_ImportedAt");
b.HasIndex("InvoiceNumber", "SellerTaxId")
.HasDatabaseName("IX_ZugferdInvoices_InvoiceNumber_SellerTaxId");
b.ToTable("ZugferdInvoices");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,72 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace DXApp.TemplateKitProject.Migrations
{
/// <inheritdoc />
public partial class AddPerformanceIndexes : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "SellerTaxId",
table: "ZugferdInvoices",
type: "nvarchar(450)",
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(max)",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "InvoiceNumber",
table: "ZugferdInvoices",
type: "nvarchar(450)",
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(max)",
oldNullable: true);
migrationBuilder.CreateIndex(
name: "IX_ZugferdInvoices_ImportedAt",
table: "ZugferdInvoices",
column: "ImportedAt");
migrationBuilder.CreateIndex(
name: "IX_ZugferdInvoices_InvoiceNumber_SellerTaxId",
table: "ZugferdInvoices",
columns: new[] { "InvoiceNumber", "SellerTaxId" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_ZugferdInvoices_ImportedAt",
table: "ZugferdInvoices");
migrationBuilder.DropIndex(
name: "IX_ZugferdInvoices_InvoiceNumber_SellerTaxId",
table: "ZugferdInvoices");
migrationBuilder.AlterColumn<string>(
name: "SellerTaxId",
table: "ZugferdInvoices",
type: "nvarchar(max)",
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(450)",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "InvoiceNumber",
table: "ZugferdInvoices",
type: "nvarchar(max)",
nullable: true,
oldClrType: typeof(string),
oldType: "nvarchar(450)",
oldNullable: true);
}
}
}

View File

@@ -49,7 +49,7 @@ namespace DXApp.TemplateKitProject.Migrations
.HasColumnType("datetime2");
b.Property<string>("InvoiceNumber")
.HasColumnType("nvarchar(max)");
.HasColumnType("nvarchar(450)");
b.Property<string>("RawXml")
.HasColumnType("nvarchar(max)");
@@ -61,7 +61,7 @@ namespace DXApp.TemplateKitProject.Migrations
.HasColumnType("nvarchar(max)");
b.Property<string>("SellerTaxId")
.HasColumnType("nvarchar(max)");
.HasColumnType("nvarchar(450)");
b.Property<string>("SourceType")
.HasColumnType("nvarchar(max)");
@@ -74,6 +74,12 @@ namespace DXApp.TemplateKitProject.Migrations
b.HasKey("Id");
b.HasIndex("ImportedAt")
.HasDatabaseName("IX_ZugferdInvoices_ImportedAt");
b.HasIndex("InvoiceNumber", "SellerTaxId")
.HasDatabaseName("IX_ZugferdInvoices_InvoiceNumber_SellerTaxId");
b.ToTable("ZugferdInvoices");
});
#pragma warning restore 612, 618

View File

@@ -0,0 +1,22 @@
namespace DXApp.TemplateKitProject.Models;
/// <summary>
/// Lightweight DTO für die Rechnungsliste (ohne RawXml für bessere Performance)
/// </summary>
public class ZugferdInvoiceListDto
{
public int Id { get; set; }
public string InvoiceNumber { get; set; } = string.Empty;
public DateTime InvoiceDate { get; set; }
public string SellerName { get; set; } = string.Empty;
public string SellerTaxId { get; set; } = string.Empty;
public string BuyerName { get; set; } = string.Empty;
public decimal TotalAmount { get; set; }
public decimal TaxAmount { get; set; }
public string CurrencyCode { get; set; } = string.Empty;
public string Iban { get; set; } = string.Empty;
public DateTime ImportedAt { get; set; }
public string SourceType { get; set; } = string.Empty;
public string ResultFilePath { get; set; } = string.Empty;
public string GuidelineId { get; set; } = string.Empty;
}

View File

@@ -12,7 +12,7 @@
}
else
{
@(Html.DevExtreme().DataGrid<DXApp.TemplateKitProject.Models.ZugferdInvoice>()
@(Html.DevExtreme().DataGrid<DXApp.TemplateKitProject.Models.ZugferdInvoiceListDto>()
.DataSource(Model.Invoices)
.KeyExpr("Id")
.ShowBorders(true)

View File

@@ -7,12 +7,30 @@ namespace DXApp.TemplateKitProject.Pages.Invoices;
public class IndexModel(AppDbContext db) : PageModel
{
public List<ZugferdInvoice> Invoices { get; private set; } = [];
public List<ZugferdInvoiceListDto> Invoices { get; private set; } = [];
public async Task OnGetAsync()
{
Invoices = await db.ZugferdInvoices
.OrderByDescending(i => i.ImportedAt)
.Select(i => new ZugferdInvoiceListDto
{
Id = i.Id,
InvoiceNumber = i.InvoiceNumber,
InvoiceDate = i.InvoiceDate,
SellerName = i.SellerName,
SellerTaxId = i.SellerTaxId,
BuyerName = i.BuyerName,
TotalAmount = i.TotalAmount,
TaxAmount = i.TaxAmount,
CurrencyCode = i.CurrencyCode,
Iban = i.Iban,
ImportedAt = i.ImportedAt,
SourceType = i.SourceType,
ResultFilePath = i.ResultFilePath,
GuidelineId = i.GuidelineId
// RawXml wird NICHT geladen ? Performance-Optimierung!
})
.ToListAsync();
}
}