Pomelo.entityframeworkcore.mysql: Das Hinzufügen einer Entität zur DB mit einer vordefinierten "Id"-Spalte führt zur Generierung einer falschen ID in der DB

Erstellt am 27. Mai 2020  ·  4Kommentare  ·  Quelle: PomeloFoundation/Pomelo.EntityFrameworkCore.MySql

Schritte zum Reproduzieren

Domänenklasse:

    public class Category
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }

Entitätskonfiguration:

    public class CategoryConfiguration : IEntityTypeConfiguration<Category>
    {
        public void Configure(EntityTypeBuilder<Category> entity)
        {
            entity.HasKey(c => c.Id);

            entity.Property(c => c.Id).IsRequired().ValueGeneratedOnAdd();
            entity.Property(c => c.Name).IsRequired().HasMaxLength(55);
        }
    }

Ort, an dem ich der DB eine Entität hinzufüge (CategoryDto ist identisch mit Category)

        [HttpPost]
        public IActionResult CreateCategory([FromBody] CategoryDto categoryDto)
        {
            var category = _mapper.Map<Category>(categoryDto);

            _unitOfWork.Categories.Add(category);
            _unitOfWork.Complete();

            _mapper.Map(category, categoryDto);
            return Created($"{Request.Path}/{category.Id}", categoryDto);
        }

Den vollständigen Code können Sie hier einsehen

Das Thema

Wenn ich eine POST-Anfrage mit einer vordefinierten ID-Spalte (zB 1000 oder 15) sende, ist diese vordefinierte Nummer die ID der Entität, die in die DB hinzugefügt wird. Auch alle weiteren IDs beginnen mit diesem Wert.
error

Erwartetes Verhalten

Unabhängig von der ID im Feld sollte beim Erstellen eines Datensatzes in der DB der Spaltenwert richtig festgelegt werden.

Weitere technische Details

MySQL-Version: 8.0.19-mysql
Betriebssystem: Windows 10
Pomelo.EntityFrameworkCore.MySql-Version: 3.1.1
Microsoft.AspNetCore.App-Version: 3.1.201

type-question

Hilfreichster Kommentar

MySQL verwendet jeden von Ihnen angegebenen ID-Wert. Nur wenn Sie keinen Wert angeben, generiert MySQL einen auto_increment Wert für Sie. Wenn es eine ID generiert, verwendet es den höchsten Wert, der bereits in der Tabelle vorhanden ist (oder sonst 0 ) als Seed.

Sehen Sie sich

Das Attribut AUTO_INCREMENT kann verwendet werden, um eine eindeutige Identität für neue Zeilen zu generieren:
[…]
Für die Spalte AUTO_INCREMENT wurde kein Wert angegeben, daher hat MySQL automatisch Sequenznummern zugewiesen. Sie können der Spalte auch explizit 0 zuweisen, um Sequenznummern zu generieren
[…]
Wenn die Spalte als NOT NULL deklariert ist, ist es auch möglich, der Spalte NULL zuzuweisen, um Sequenznummern zu generieren.
[…]
Wenn Sie einen anderen Wert in eine AUTO_INCREMENT-Spalte einfügen, wird die Spalte auf diesen Wert gesetzt und die Reihenfolge zurückgesetzt, sodass der nächste automatisch generierte Wert sequenziell auf den größten Spaltenwert folgt.

Der folgende Code demonstriert das:

```c#
Verwenden von System.Diagnostics;
Verwenden von System.Linq;
unter Verwendung von Microsoft.EntityFrameworkCore;
Verwenden von Microsoft.Extensions.Logging;

Namespace IssueConsoleTemplate
{
öffentliche Klasse IceCream
{
public int IceCreamId { get; einstellen; }
öffentliche Zeichenfolge Name { get; einstellen; }
}

public class Context : DbContext
{
    public DbSet<IceCream> IceCreams { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseMySql(
                "server=127.0.0.1;port=3306;user=root;password=;database=Issue1092",
                b => b.ServerVersion("8.0.20-mysql"))
            .UseLoggerFactory(
                LoggerFactory.Create(b => b
                        .AddConsole()
                        .AddFilter(level => level >= LogLevel.Information)))
            .EnableSensitiveDataLogging()
            .EnableDetailedErrors();
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<IceCream>(
            entity =>
            {
                // Because the property contains "Id", the following lines are added automatically,
                // so no need to specify them explicitly (does not do any harm however):
                // entity.Property(e => e.IceCreamId)
                //    .ValueGeneratedOnAdd();
            });
    }
}

internal class Program
{
    private static void Main()
    {
        using var context = new Context();

        context.Database.EnsureDeleted();
        context.Database.EnsureCreated();

        context.IceCreams.AddRange(
            new IceCream
            {
                IceCreamId = 42, // explicitly set ID
                Name = "Vanilla",
            },
            new IceCream
            {
                // <-- let MySQL use it's auto_increment behavior
                Name = "Chocolate",
            });

        context.SaveChanges();

        var iceCreams = context.IceCreams
            .OrderBy(i => i.IceCreamId)
            .ToList();

        Debug.Assert(iceCreams.Count == 2);
        Debug.Assert(iceCreams[0].IceCreamId == 42);
        Debug.Assert(iceCreams[0].Name == "Vanilla");
        Debug.Assert(iceCreams[1].IceCreamId == 43);
        Debug.Assert(iceCreams[1].Name == "Chocolate");
    }
}

}


It generates the following SQL:

```sql
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 3.1.3 initialized 'Context' using provider 'Pomelo.EntityFrameworkCore.MySql' with options: ServerVersion 8.0.20 MySql SensitiveDataLoggingEnabled DetailedErrorsEnabled

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (12ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE DATABASE `Issue1092`;

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (56ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE `IceCreams` (
          `IceCreamId` int NOT NULL AUTO_INCREMENT,
          `Name` longtext CHARACTER SET utf8mb4 NULL,
          CONSTRAINT `PK_IceCreams` PRIMARY KEY (`IceCreamId`)
      );

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (8ms) [Parameters=[@p0='42', @p1='Vanilla' (Size = 4000)], CommandType='Text', CommandTimeout='30']
      INSERT INTO `IceCreams` (`IceCreamId`, `Name`)
      VALUES (<strong i="6">@p0</strong>, @p1);

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (4ms) [Parameters=[@p0='Chocolate' (Size = 4000)], CommandType='Text', CommandTimeout='30']
      INSERT INTO `IceCreams` (`Name`)
      VALUES (@p0);
      SELECT `IceCreamId`
      FROM `IceCreams`
      WHERE ROW_COUNT() = 1 AND `IceCreamId` = LAST_INSERT_ID();

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT `i`.`IceCreamId`, `i`.`Name`
      FROM `IceCreams` AS `i`
      ORDER BY `i`.`IceCreamId`

Das von Ihnen erlebte Verhalten wird also erwartet und stimmt mit der Dokumentation überein.

Mögliche Lösung:

Um Ihr Problem zu lösen, setzen Sie einfach Ihre Category.Id auf 0 bevor Sie SaveChanges() anrufen.

Da Sie ein explizites Repository-Muster verwenden, besteht eine einfache Lösung darin, einfach die Methode Add() zu überschreiben:

```c#
öffentliches abstraktes Klassen-Repository: IRepositorywo TEntity : Klasse
{
public virtual void Add(TEntity Entity) // <-- virtuell gemacht
{
Kontext.Set().Add(Entität);
}
}

öffentliche Klasse CategoryRepository : Repository, ICategoryRepository
{
öffentliche Überschreibung void Hinzufügen(Kategorie-Entität)
{
Entity.Id = 0;
base.Add(Entität);
}
}
```

Alle 4 Kommentare

Können Sie das MySQL-Schema der Kategorietabelle freigeben? Es ist nicht ganz klar, welches Verhalten Sie benötigen. Ist die ID vom Benutzer bereitgestellt oder automatisch in der DB mit auto_increment generiert? Im ersteren Fall benötigen Sie die

Können Sie das MySQL-Schema der Kategorietabelle freigeben? Es ist nicht ganz klar, welches Verhalten Sie benötigen. Ist die ID vom Benutzer bereitgestellt oder automatisch in der DB mit auto_increment generiert? Im ersteren Fall benötigen Sie die

image

Nun, zum Beispiel, wenn ich MS SQL verwende, wenn ich eine POST-Anfrage mit Daten wie diesen sende:

{
    "Id":12137123,
    "Name":"Some Category"
}

Diese zufällige ID wird beim Erstellen eines Eintrags in der Datenbank weggelassen und in der ID-Spalte korrekt gesetzt. Der Punkt von allem ist, wenn ein böswilliger Benutzer versucht, eine falsche ID einzugeben, wird meine Datenbank beschädigt

MySQL verwendet jeden von Ihnen angegebenen ID-Wert. Nur wenn Sie keinen Wert angeben, generiert MySQL einen auto_increment Wert für Sie. Wenn es eine ID generiert, verwendet es den höchsten Wert, der bereits in der Tabelle vorhanden ist (oder sonst 0 ) als Seed.

Sehen Sie sich

Das Attribut AUTO_INCREMENT kann verwendet werden, um eine eindeutige Identität für neue Zeilen zu generieren:
[…]
Für die Spalte AUTO_INCREMENT wurde kein Wert angegeben, daher hat MySQL automatisch Sequenznummern zugewiesen. Sie können der Spalte auch explizit 0 zuweisen, um Sequenznummern zu generieren
[…]
Wenn die Spalte als NOT NULL deklariert ist, ist es auch möglich, der Spalte NULL zuzuweisen, um Sequenznummern zu generieren.
[…]
Wenn Sie einen anderen Wert in eine AUTO_INCREMENT-Spalte einfügen, wird die Spalte auf diesen Wert gesetzt und die Reihenfolge zurückgesetzt, sodass der nächste automatisch generierte Wert sequenziell auf den größten Spaltenwert folgt.

Der folgende Code demonstriert das:

```c#
Verwenden von System.Diagnostics;
Verwenden von System.Linq;
unter Verwendung von Microsoft.EntityFrameworkCore;
Verwenden von Microsoft.Extensions.Logging;

Namespace IssueConsoleTemplate
{
öffentliche Klasse IceCream
{
public int IceCreamId { get; einstellen; }
öffentliche Zeichenfolge Name { get; einstellen; }
}

public class Context : DbContext
{
    public DbSet<IceCream> IceCreams { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder
            .UseMySql(
                "server=127.0.0.1;port=3306;user=root;password=;database=Issue1092",
                b => b.ServerVersion("8.0.20-mysql"))
            .UseLoggerFactory(
                LoggerFactory.Create(b => b
                        .AddConsole()
                        .AddFilter(level => level >= LogLevel.Information)))
            .EnableSensitiveDataLogging()
            .EnableDetailedErrors();
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<IceCream>(
            entity =>
            {
                // Because the property contains "Id", the following lines are added automatically,
                // so no need to specify them explicitly (does not do any harm however):
                // entity.Property(e => e.IceCreamId)
                //    .ValueGeneratedOnAdd();
            });
    }
}

internal class Program
{
    private static void Main()
    {
        using var context = new Context();

        context.Database.EnsureDeleted();
        context.Database.EnsureCreated();

        context.IceCreams.AddRange(
            new IceCream
            {
                IceCreamId = 42, // explicitly set ID
                Name = "Vanilla",
            },
            new IceCream
            {
                // <-- let MySQL use it's auto_increment behavior
                Name = "Chocolate",
            });

        context.SaveChanges();

        var iceCreams = context.IceCreams
            .OrderBy(i => i.IceCreamId)
            .ToList();

        Debug.Assert(iceCreams.Count == 2);
        Debug.Assert(iceCreams[0].IceCreamId == 42);
        Debug.Assert(iceCreams[0].Name == "Vanilla");
        Debug.Assert(iceCreams[1].IceCreamId == 43);
        Debug.Assert(iceCreams[1].Name == "Chocolate");
    }
}

}


It generates the following SQL:

```sql
info: Microsoft.EntityFrameworkCore.Infrastructure[10403]
      Entity Framework Core 3.1.3 initialized 'Context' using provider 'Pomelo.EntityFrameworkCore.MySql' with options: ServerVersion 8.0.20 MySql SensitiveDataLoggingEnabled DetailedErrorsEnabled

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (12ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE DATABASE `Issue1092`;

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (56ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      CREATE TABLE `IceCreams` (
          `IceCreamId` int NOT NULL AUTO_INCREMENT,
          `Name` longtext CHARACTER SET utf8mb4 NULL,
          CONSTRAINT `PK_IceCreams` PRIMARY KEY (`IceCreamId`)
      );

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (8ms) [Parameters=[@p0='42', @p1='Vanilla' (Size = 4000)], CommandType='Text', CommandTimeout='30']
      INSERT INTO `IceCreams` (`IceCreamId`, `Name`)
      VALUES (<strong i="6">@p0</strong>, @p1);

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (4ms) [Parameters=[@p0='Chocolate' (Size = 4000)], CommandType='Text', CommandTimeout='30']
      INSERT INTO `IceCreams` (`Name`)
      VALUES (@p0);
      SELECT `IceCreamId`
      FROM `IceCreams`
      WHERE ROW_COUNT() = 1 AND `IceCreamId` = LAST_INSERT_ID();

info: Microsoft.EntityFrameworkCore.Database.Command[20101]
      Executed DbCommand (1ms) [Parameters=[], CommandType='Text', CommandTimeout='30']
      SELECT `i`.`IceCreamId`, `i`.`Name`
      FROM `IceCreams` AS `i`
      ORDER BY `i`.`IceCreamId`

Das von Ihnen erlebte Verhalten wird also erwartet und stimmt mit der Dokumentation überein.

Mögliche Lösung:

Um Ihr Problem zu lösen, setzen Sie einfach Ihre Category.Id auf 0 bevor Sie SaveChanges() anrufen.

Da Sie ein explizites Repository-Muster verwenden, besteht eine einfache Lösung darin, einfach die Methode Add() zu überschreiben:

```c#
öffentliches abstraktes Klassen-Repository: IRepositorywo TEntity : Klasse
{
public virtual void Add(TEntity Entity) // <-- virtuell gemacht
{
Kontext.Set().Add(Entität);
}
}

öffentliche Klasse CategoryRepository : Repository, ICategoryRepository
{
öffentliche Überschreibung void Hinzufügen(Kategorie-Entität)
{
Entity.Id = 0;
base.Add(Entität);
}
}
```

Danke für die Antwort und Hilfe

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen