Pomelo.entityframeworkcore.mysql: تؤدي إضافة كيان إلى قاعدة البيانات بعمود "معرف" محدد مسبقًا إلى إنشاء معرف خاطئ في قاعدة البيانات

تم إنشاؤها على ٢٧ مايو ٢٠٢٠  ·  4تعليقات  ·  مصدر: PomeloFoundation/Pomelo.EntityFrameworkCore.MySql

خطوات التكاثر

فئة المجال:

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

تكوين الكيان:

    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);
        }
    }

المكان الذي أضيف فيه الكيان إلى قاعدة البيانات (CategoryDto مطابق للفئة)

        [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);
        }

يمكنك التحقق من الكود الكامل هنا:

المشكلة

عندما أرسل طلب POST بعمود معرف محدد مسبقًا (على سبيل المثال 1000 أو 15) ، سيكون هذا الرقم المحدد مسبقًا هو معرف الكيان الذي تمت إضافته إلى قاعدة البيانات. كما ستبدأ جميع المعرفات الإضافية من تلك القيمة.
error

سلوك متوقع

بغض النظر عن المعرف الموجود في الحقل ، عند إنشاء سجل في قاعدة البيانات ، يجب تعيين قيمة العمود بشكل صحيح.

مزيد من التفاصيل الفنية

إصدار MySQL: 8.0.19-mysql
نظام التشغيل: Windows 10
Pomelo.EntityFrameworkCore.MySQL الإصدار: 3.1.1
إصدار Microsoft.AspNetCore.App: 3.1.201

type-question

التعليق الأكثر فائدة

ستستخدم MySQL أي قيمة معرّف تقدمها. فقط إذا لم تقدم قيمة ، فستولد MySQL قيمة auto_increment لك. عند إنشاء معرّف ، سيستخدم أعلى قيمة موجودة بالفعل في الجدول (أو 0 خلاف ذلك) كمبدأ.

ألق نظرة على 3.6.9 باستخدام AUTO_INCREMENT في مستندات MySQL:

يمكن استخدام السمة AUTO_INCREMENT لإنشاء هوية فريدة للصفوف الجديدة:
[...]
لم يتم تحديد أي قيمة لعمود AUTO_INCREMENT ، لذلك قامت MySQL بتعيين أرقام التسلسل تلقائيًا. يمكنك أيضًا تعيين 0 بشكل صريح للعمود لإنشاء أرقام التسلسل
[...]
إذا تم الإعلان عن العمود "ليس فارغًا" ، فمن الممكن أيضًا تعيين "فارغ" للعمود لإنشاء أرقام التسلسل.
[...]
عندما تقوم بإدراج أي قيمة أخرى في عمود AUTO_INCREMENT ، يتم تعيين العمود إلى تلك القيمة ويتم إعادة تعيين التسلسل بحيث تتبع القيمة التالية التي تم إنشاؤها تلقائيًا بالتسلسل من قيمة العمود الأكبر.

يوضح الكود التالي أن:

ج #
باستخدام System.Diagnostics ؛
باستخدام System.Linq ؛
باستخدام Microsoft.EntityFrameworkCore ؛
باستخدام Microsoft.Extensions.Logging ؛

مشكلة مساحة الاسم ConsoleTemplate
{
IceCream من الدرجة العامة
{
عامة int IceCreamId {get؛ يضع؛ }
اسم السلسلة العامة {get؛ يضع؛ }
}

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`

لذا فإن السلوك الذي تواجهه متوقع ومتوافق مع الوثائق.

حل ممكن:

لحل مشكلتك ، ما عليك سوى إعادة تعيين Category.Id إلى 0 قبل الاتصال بـ SaveChanges() .

نظرًا لأنك تستخدم نمطًا صريحًا لمستودع التخزين ، فقد يكون الحل البسيط هو تجاوز طريقة Add() :

ج #
مستودع فئة الملخص العام: IRepositoryحيث TEntity: class
{
الفراغ الظاهري العام إضافة (كيان TEntity) // <- جعلها افتراضية
{
السياق() .Add (الكيان) ؛
}
}

فئة عامة فئة المستودع: المستودع، IC categoryRepository
{
تجاوز عام باطل إضافة (كيان الفئة)
{
الكيان معرف = 0 ؛
base.Add (كيان) ؛
}
}
""

ال 4 كومينتر

هل يمكنك مشاركة مخطط MySQL لجدول الفئات؟ ليس من الواضح تمامًا ما هو السلوك الذي تحتاجه ، هل هذا المعرّف قدمه المستخدم أم يتم إنشاؤه تلقائيًا في DB باستخدام auto_increment ؟ إذا كان الأمر الأول ، فلن تحتاج إلى طريقة ValueGeneratedOnAdd .

هل يمكنك مشاركة مخطط MySQL لجدول الفئات؟ ليس من الواضح تمامًا ما هو السلوك الذي تحتاجه ، هل هذا المعرّف قدمه المستخدم أم يتم إنشاؤه تلقائيًا في DB باستخدام auto_increment ؟ إذا كان الأمر الأول ، فلن تحتاج إلى طريقة ValueGeneratedOnAdd .

image

حسنًا ، على سبيل المثال ، عندما أستخدم MS SQL ، إذا قمت بإرسال طلب POST ببيانات مثل هذه:

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

سيتم حذف هذا المعرف العشوائي عند إنشاء إدخال في قاعدة البيانات وسيتم تعيين الصحيح في عمود المعرف. الهدف من كل ذلك هو أنه إذا حاول المستخدم الضار وضع معرف خاطئ ، فسيتم كسر ديسيبل الخاص بي

ستستخدم MySQL أي قيمة معرّف تقدمها. فقط إذا لم تقدم قيمة ، فستولد MySQL قيمة auto_increment لك. عند إنشاء معرّف ، سيستخدم أعلى قيمة موجودة بالفعل في الجدول (أو 0 خلاف ذلك) كمبدأ.

ألق نظرة على 3.6.9 باستخدام AUTO_INCREMENT في مستندات MySQL:

يمكن استخدام السمة AUTO_INCREMENT لإنشاء هوية فريدة للصفوف الجديدة:
[...]
لم يتم تحديد أي قيمة لعمود AUTO_INCREMENT ، لذلك قامت MySQL بتعيين أرقام التسلسل تلقائيًا. يمكنك أيضًا تعيين 0 بشكل صريح للعمود لإنشاء أرقام التسلسل
[...]
إذا تم الإعلان عن العمود "ليس فارغًا" ، فمن الممكن أيضًا تعيين "فارغ" للعمود لإنشاء أرقام التسلسل.
[...]
عندما تقوم بإدراج أي قيمة أخرى في عمود AUTO_INCREMENT ، يتم تعيين العمود إلى تلك القيمة ويتم إعادة تعيين التسلسل بحيث تتبع القيمة التالية التي تم إنشاؤها تلقائيًا بالتسلسل من قيمة العمود الأكبر.

يوضح الكود التالي أن:

ج #
باستخدام System.Diagnostics ؛
باستخدام System.Linq ؛
باستخدام Microsoft.EntityFrameworkCore ؛
باستخدام Microsoft.Extensions.Logging ؛

مشكلة مساحة الاسم ConsoleTemplate
{
IceCream من الدرجة العامة
{
عامة int IceCreamId {get؛ يضع؛ }
اسم السلسلة العامة {get؛ يضع؛ }
}

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`

لذا فإن السلوك الذي تواجهه متوقع ومتوافق مع الوثائق.

حل ممكن:

لحل مشكلتك ، ما عليك سوى إعادة تعيين Category.Id إلى 0 قبل الاتصال بـ SaveChanges() .

نظرًا لأنك تستخدم نمطًا صريحًا لمستودع التخزين ، فقد يكون الحل البسيط هو تجاوز طريقة Add() :

ج #
مستودع فئة الملخص العام: IRepositoryحيث TEntity: class
{
الفراغ الظاهري العام إضافة (كيان TEntity) // <- جعلها افتراضية
{
السياق() .Add (الكيان) ؛
}
}

فئة عامة فئة المستودع: المستودع، IC categoryRepository
{
تجاوز عام باطل إضافة (كيان الفئة)
{
الكيان معرف = 0 ؛
base.Add (كيان) ؛
}
}
""

شكرا على الجواب والمساعدة

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات