Я получаю ошибку, упомянутую в заголовке.
Важный
Форма успешно отправляет данные в базу данных, но затем пытается отправить еще раз с другим запросом, который пытается одновременно вставить идентификатор обсуждаемого продукта, а затем происходит сбой или сбой. В моем проекте подобных запросов нет. Что могло вызвать это?
Невозможно вставить явное значение для столбца идентификаторов в таблице «Обсуждаемые продукты», если для параметра IDENTITY_INSERT установлено значение OFF.
Я просмотрел несколько сообщений на форуме и порылся в Интернете, но так и не смог решить проблему.
Отчет о контактах будет отправлен в базу данных, а также будет обновлена таблица обсуждения продуктов. Отчет о контактах работал отлично, но в таблице обсуждаемых продуктов произошла ошибка.
Пожалуйста, просмотрите весь код ниже:
ContactReportController
:
[HttpGet]
public async Task<IActionResult> Submit()
{
var contactReport = new ContactReport
{
ProductsDiscussed = new List<ProductDiscussed>
{
new ProductDiscussed() // Add one default item
},
Report_Date = DateTime.Now
};
// Load necessary data for the form
await LoadFormDataAsync();
return View(contactReport);
}
[HttpPost]
public async Task<IActionResult> Submit(ContactReport contactReport)
{
if (User?.Identity?.IsAuthenticated == true)
{
if (ModelState.IsValid)
{
try
{
_logger.LogInformation("Model is valid. Adding contact report to context.");
// Add the ContactReport to the context
_context.ContactReports.Add(contactReport);
await _context.SaveChangesAsync();
// Ensure EF Core recognizes each product as a new entity
foreach (var product in contactReport.ProductsDiscussed)
{
// Set the foreign key
product.ReportID = contactReport.Report_Id;
// Mark the product as a new entity
_context.ProductsDiscussed.Add(product);
}
await _context.SaveChangesAsync();
return RedirectToAction("Index");
}
catch (Exception ex)
{
_logger.LogError(ex, "An error occurred while saving the contact report.");
ModelState.AddModelError("", "An error occurred while saving the report. Please try again.");
}
}
else
{
_logger.LogWarning("Model state is invalid.");
foreach (var error in ModelState.Values.SelectMany(v => v.Errors))
{
_logger.LogWarning(error.ErrorMessage);
}
}
}
else
{
_logger.LogWarning("User is not authenticated.");
return Unauthorized();
}
// Reload data if ModelState is invalid
await LoadFormDataAsync(); // Method to load the data needed for the form
return View(contactReport);
}
private async Task LoadFormDataAsync()
{
var staffList = await _context.Staff.OrderBy(s => s.Name).ToListAsync();
ViewBag.StaffList = new SelectList(staffList, "StaffId", "Name");
var countries = await _context.Countries.OrderBy(c => c.CountryName).ToListAsync();
ViewBag.Countries = new SelectList(countries, "CountryCode", "CountryName");
var productCategories = await _context.ProductCategories.OrderBy(c => c.CategoryName).ToListAsync();
ViewBag.ProductCategories = new SelectList(productCategories, "CategoryID", "CategoryName");
var customers = await _context.Customers.ToListAsync();
var customerList = customers
.Select(c => new
{
c.CustomerId,
DisplayName = string.IsNullOrEmpty(c.City)
? $"{c.Name} {c.Surname} - {c.Country}"
: $"{c.Name} {c.Surname} - {c.Country}, {c.City}",
SortKey = string.IsNullOrEmpty(c.Name) ? c.Surname : $"{c.Name} {c.Surname}"
})
.OrderBy(c => c.SortKey)
.ToList();
ViewBag.CustomerList = new SelectList(customerList, "CustomerId", "DisplayName");
}
Submit.cshtml
:
<div id = "productsContainer">
@for (int i = 0; i < Model.ProductsDiscussed.Count; i++)
{
<div class = "card mb-3">
<div class = "card-body">
<div class = "form-group">
<label asp-for = "ProductsDiscussed[i].BrandProductCategoriesID">Product Category</label>
<select asp-for = "ProductsDiscussed[i].BrandProductCategoriesID" class = "form-control" asp-items = "ViewBag.ProductCategories">
<option value = "">Select a product category</option>
</select>
<span asp-validation-for = "ProductsDiscussed[i].BrandProductCategoriesID" class = "text-danger"></span>
</div>
<div class = "form-group">
<label asp-for = "ProductsDiscussed[i].Sample">Sample Used</label>
<select asp-for = "ProductsDiscussed[i].Sample" class = "form-control">
<option value = "false">No</option>
<option value = "true">Yes</option>
</select>
<span asp-validation-for = "ProductsDiscussed[i].Sample" class = "text-danger"></span>
</div>
</div>
</div>
}
</div>
<button type = "button" id = "addProductButton" class = "btn btn-secondary mt-3">Add Another Product</button>
SQL-код:
[Products Discussed ID] [int] IDENTITY(1,1) NOT NULL,
ContactReport.cs
:
public ContactReport()
{
ProductsDiscussed = new List<ProductDiscussed>();
}
// Text in-between
[NotMapped]
public List<ProductDiscussed> ProductsDiscussed { get; set; } = new List<ProductDiscussed>();
ProductDiscussed.cs
:
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("Products Discussed ID")]
public int ProductsDiscussedID { get; set; } // Manually managed ID
Ошибка после отправки отчета:
CustomerDatabase.Controllers.ContactReportController: Информация: модель действительна. Добавление отчета о контакте в контекст.
Microsoft.EntityFrameworkCore.Database.Command: информация: выполнена DbCommand [Parameters=[@p0='SUR238' (Nullable = false) (размер = 4000), @p1='test' (Nullable = false) (размер = 4000), @p2='test' (Nullable = false) (Размер = 4000), @p3='' (Nullable = false) (Размер = 4000), @p4='test' (Nullable = false) (Размер = 4000), @p5='2024-08-12T00:00:00.0000000', @p6='test' (Nullable = false) (размер = 4000), @p7='STA11' (Nullable = false) (размер = 4000)], CommandType='Текст', CommandTimeout='30']
ВЫКЛЮЧИТЕ IMPLICIT_TRANSACTIONS;
УСТАНОВИТЬ NOCOUNT ON;
ВСТАВИТЬ В [Отчет о контакте] ([Идентификатор клиента], [Отзыв], [Последующие действия], [Местоположение], [Примечания], [Дата отчета], [Описание отчета], [Идентификатор персонала])
ВЫВОД ВСТАВЛЕН.[Id]
ЗНАЧЕНИЯ (@p0, @p1, @p2, @p3, @p4, @p5, @p6, @p7);Выброшено исключение: «Microsoft.Data.SqlClient.SqlException» в Microsoft.Data.SqlClient.dll
Microsoft.EntityFrameworkCore.Database.Command: Ошибка: не удалось выполнить DbCommand [Parameters=[@p0='18', @p1='3', @p2='195', @p3='False'], CommandType='Text ', CommandTimeout='30']Microsoft.EntityFrameworkCore.DbUpdateException: произошла ошибка при сохранении изменений сущности. Подробности смотрите во внутреннем исключении.
Microsoft.Data.SqlClient.SqlException (0x80131904): невозможно вставить явное значение для столбца идентификаторов в таблице «Обсуждаемые продукты», если для параметра IDENTITY_INSERT установлено значение OFF.
Редактировать 1:
ContactReport.cs:
using Microsoft.AspNetCore.Mvc.Rendering;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace CustomerDatabase.Models
{
[Table("Contact Report")]
public class ContactReport
{
public ContactReport()
{
ProductsDiscussed = new List<ProductDiscussed>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("Id")]
public int Report_Id { get; set; }
[Required]
[Column("Report Date")]
public DateTime Report_Date { get; set; }
[Required]
[Column("Report Description")]
public string Report_Description { get; set; } = string.Empty;
[Required]
[Column("Customer ID")]
public string Customer_ID { get; set; } = string.Empty;
[Column("Location")]
public string Location { get; set; } = string.Empty;
[Required]
[Column("Follow Up")]
public string Follow_Up { get; set; } = string.Empty;
[Required]
[Column("Feedback")]
public string Feedback { get; set; } = string.Empty;
[Required]
[Column("Notes")]
public string Notes { get; set; } = string.Empty;
[Required]
[Column("Staff Id")]
public string Staff_Id { get; set; } = string.Empty;
[NotMapped]
public List<SelectListItem>? StaffList { get; set; }
// New properties for location
[NotMapped]
public string Country { get; set; } = string.Empty;
[NotMapped]
public string? Province { get; set; }
[NotMapped]
public string? City { get; set; }
[NotMapped]
public string? Area { get; set; }
public virtual List<ProductDiscussed> ProductsDiscussed { get;} = new List<ProductDiscussed>();
}
}
ПродуктыОбсуждаемые.cs
using CustomerDatabase.Models;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
[Table("Products Discussed")]
public class ProductDiscussed
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("Products Discussed ID")]
public int ProductsDiscussedID { get; protected set; }
[Required]
[Column("Report ID")]
public int ReportID { get; set; }
[ForeignKey("ReportID")]
public ContactReport? ContactReport { get; set; }
[Required]
[Column("Brand Product Categories")]
public int BrandProductCategoriesID { get; set; }
[ForeignKey("BrandProductCategoriesID")]
public BrandProductCategory? BrandProductCategory { get; set; }
[Required]
public bool Sample { get; set; }
}
Во-первых, коллекция ProductsDiscussed не должна быть помечена как [NotMapped]
. Это говорит EF игнорировать коллекцию. Первым шагом является исправление свойства навигации, а также ПК:
[NotMapped]
public List<ProductDiscussed> ProductsDiscussed { get; set; } = new List<ProductDiscussed>();
должно быть:
public virtual ICollection<ProductDiscussed> ProductsDiscussed { get; } = [];
Нет сеттера. Свойства навигации по коллекции не должны предоставлять установщик. Если вы хотите инициализировать продукт по умолчанию, используйте Add()
. Сеттеры могут в конечном итоге привести к проблемам, когда отслеживаемые объекты вызывают сеттер, что приводит к нарушению отслеживания изменений, что приводит к дублирующим вставкам или ошибкам, которые вы видите.
Далее, PK, являющиеся столбцами идентификаторов, также должны быть защищены:
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Column("Products Discussed ID")]
public int ProductsDiscussedID { get; protected set; }
Я не уверен, что означает комментарий об «идентификаторе, управляемом вручную», но в ПК не должно быть ничего ручного. Нам нужен EF, и только EF его назначает. Ошибка Identity Insert OFF обычно означает, что EF получил объект, который ему было приказано обрабатывать как новую вставку, для которой уже установлено значение, отличное от значения по умолчанию. В EF6 (.Net Framework) EF будет игнорировать любое существующее значение, и вы получите молчаливый сбой, когда EF вставит повторяющуюся запись с новым столбцом идентификаторов. Я полагаю, что в EF Core они решили, что если идентификатор был указан, даже если он помечен как идентификатор, он будет передавать его в операторе INSERT, что приведет к исключению вставки идентификатора. Защита установщика должна идентифицировать любой код, который может пытаться вставить существующие значения.
Также кодируйте так:
product.ReportID = contactReport.Report_Id;
в этом нет необходимости для картографических навигационных продуктов. Когда вы звоните:
_context.ContactReports.Add(contactReport);
await _context.SaveChangesAsync();
При правильно сопоставленных ассоциациях EF вставит все продукты в отчет и автоматически свяжет FK. [NotMapped]
собирается это исправить, поэтому, как только будет исправлено, где продукты, имеющие родительский FK ReportId, связаны правильно, весь код для установки FK после сохранения должен быть полностью удален.
Ваш код должен работать для вставки нового отчета, но, скорее всего, не будет работать при обновлении отчета, если вы передаете коллекцию существующих и потенциально новых продуктов. Передача отдельных сущностей может оказаться сложной, поскольку любая сущность с набором идентификаторов, представляющим существующую строку, должна распознаваться DbContext как отслеживаемая сущность. В противном случае EF попытается вставить его, что приведет к этой ошибке. Это предполагает проверку того, что DbContext не отслеживает экземпляр, и после подтверждения присоединение существующих элементов или замену ссылок, если они уже отслеживаются.
Если вы вносите эти изменения и по-прежнему имеете ошибки при вставке или обновлении, обновите свой вопрос фактическими определениями сущностей для ContactReport и ProductDiscussed.
Я пытался следовать вашим рекомендациям и потратил последние полтора дня, пытаясь понять это, поскольку вся концепция для меня нова, но в конце концов я понял это. Большое спасибо!