Я создаю новый веб-сайт .Net Core 2.2 на работе и пробовал несколько разных подходов, но я получаю ошибки при использовании функции редактирования после настройки модели CRUD. Первоначально я начал с подхода «База данных», а затем получил исключение DbUpdateConcurrencyException при попытке изменить элемент. Я предположил, что что-то не так с моей БД или таблицей, поэтому начал новый проект, создав базу данных на основе модели и контекста в новом проекте.
Среда:
- MacOS Mojave
- Сообщество Visual Studio для Mac 8.2.3 (сборка 16)
- База данных SQL Server
- .Net Core 2.2
- C #
Несколько месяцев назад я создал еще один сайт с той же средой, который этого не делал.
Шаги для создания:
- dotnet new webapp -o TestSite
- cd TestSite /
- dotnet добавить пакет Microsoft.EntityFrameworkCore.Design --version 2.2.6
- dotnet добавить пакет Microsoft.EntityFrameworkCore.SqlServer--version 2.2.6
- dotnet добавить пакет Microsoft.EntityFrameworkCore.Tools --version 2.2.6
- dotnet добавить пакет Microsoft.VisualStudio.Web.CodeGeneration.Design --version 2.2.3
Проверьте, что сайт работает нормально
Создать модель и контекст
Fileset.cs
using System;
using System.ComponentModel.DataAnnotations;
namespace TestSite.Models
{
public class Fileset
{
public long Id { get; set; }
public string Ticket { get; set; }
public string Requester { get; set; }
[Display(Name = "Research Centre")]
public string ResearchCentre { get; set; }
[Display(Name = "Project ID")]
public string ProjectId { get; set; }
[Display(Name = "Title")]
public string ProjectTitle { get; set; }
[Display(Name = "Name")]
public string Name { get; set; }
[Display(Name = "Internal ID")]
public string InternalId { get; set; }
public string Email { get; set; }
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }
[Display(Name = "End Date")]
public DateTime EndDate { get; set; }
[Display(Name = "Quota (GB)")]
public long Quota { get; set; }
[Display(Name = "UNC Path")]
public string StoragePath { get; set; }
[Display(Name = "AD Group")]
public string Adgroup { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
}
}
TestSiteContext. cs
using System;
using Microsoft.EntityFrameworkCore;
namespace TestSite.Data
{
public class TestSiteContext : DbContext
{
public TestSiteContext (DbContextOptions<TestSiteContext> options) : base(options)
{
}
public DbSet<Models.Fileset> Fileset { get; set; }
}
}
Обновить файл Startup.cs, включив ссылку на строку подключения для БД
services.AddDbContext<Data.TestSiteContext>(options => options.UseSqlServer(Configuration.GetConnectionString("TestDB")));
Добавить строку подключения в appsettings.json
"ConnectionStrings": {
"TestDB": "Server=server;Database=database;Integrated Security=SSPI;Connection Timeout=15"
}
Эшафот модели
- dotnet aspnet-codegenerator razorpage -m Набор файлов -dc TestSite.Data.TestSiteContext -udl -outDir Pages /Наборы файлов --referenceScriptLibraries
Начальная миграция
- Миграция dotnet ef добавляет InitialCreate
- Обновление базы данных dotnet ef
Подтверждено, что база данных имеет новую таблицу и правильную схему, и об ошибках не было сообщено при создании в выводе dotnet cli
Перешел к https://localhost:5001/Filesets на сервере kestrel, создал новый элемент и подтвердил, что он появился в базе данных SQL
- Попробовал страницу "Изменить" для этого элемента *
Ошибка
DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyException(int commandIndex, int expectedRowsAffected, int rowsAffected)
Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithPropagationAsync(int commandIndex, RelationalDataReader reader, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeAsync(RelationalDataReader reader, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(DbContext _, ValueTuple<IEnumerable<ModificationCommandBatch>, IRelationalConnection> parameters, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync<TState, TResult>(TState state, Func<DbContext, TState, CancellationToken, Task<TResult>> operation, Func<DbContext, TState, CancellationToken, Task<ExecutionResult<TResult>>> verifySucceeded, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IReadOnlyList<InternalEntityEntry> entriesToSave, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken)
TestSite.Pages.Filesets.EditModel.OnPostAsync() in Edit.cshtml.cs
-
}
_context.Attach(Fileset).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync(); // Error line
}
catch (DbUpdateConcurrencyException)
{
if (!FilesetExists(Fileset.Id))
{
return NotFound();
Microsoft.AspNetCore.Mvc.RazorPages.Internal.ExecutorFactory+GenericTaskHandlerMethod.Convert<T>(object taskAsObject)
Microsoft.AspNetCore.Mvc.RazorPages.Internal.ExecutorFactory+GenericTaskHandlerMethod.Execute(object receiver, object[] arguments)
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker.InvokeHandlerMethodAsync()
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker.InvokeNextPageFilterAsync()
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker.Rethrow(PageHandlerExecutedContext context)
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker.InvokeInnerFilterAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
Я относительно новичок в этом,но при следовании инструкциям или при создании моего первого сайта в версии 2.2 этого не произошло. Я не сделал никаких других шагов, кроме указанных выше. Я не должен получать ошибки параллелизма для чего-то, что я один тестирую, если я не сделал это намеренно, то есть откройте страницу, чтобы отредактировать ее, отредактируйте элемент на второй странице, а затем вернитесь, чтобы попытаться отредактировать первую страницу. Кто-нибудь может помочь? Я сделал что-то глупое? Значения первичного ключа и версии строки указаны в моей модели и правильно отображаются в моей структуре БД SQL.
Пожалуйста, дайте мне знать, если вам нужна дополнительная информация.
Обновление
Добавлен код для Edit.cshtml.cs в соответствии с запросом.
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Fileset).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
if (!FilesetExists(Fileset.Id))
{
return NotFound();
}
else
{
throw;
}
}
return RedirectToPage("./Index");
}
Я еще не исправил его, поэтому должен быть по умолчанию, создан скаффолдингамиprocess.
Update 2
После ответа Нила и комментариев ниже я обновил свой метод OnPostAsync.
[BindProperty]
public Fileset Fileset { get; set; }
public SelectList FilesetSL { get; set; }
public async Task<IActionResult> OnPostAsync(int id)
{
if (!ModelState.IsValid)
{
return Page();
}
_context.Attach(Fileset).State = EntityState.Modified;
var filesetToUpdate = await _context.Fileset
.FirstOrDefaultAsync(m => m.Id == id);
if (filesetToUpdate == null)
{
return HandleDeletedFileset();
}
// Update the RowVersion to the value when this entity was
// fetched. If the entity has been updated after it was
// fetched, RowVersion won't match the DB RowVersion and
// a DbUpdateConcurrencyException is thrown.
// A second postback will make them match, unless a new
// concurrency issue happens.
_context.Entry(filesetToUpdate)
.Property("RowVersion").OriginalValue = Fileset.RowVersion;
if (await TryUpdateModelAsync<Fileset>(
filesetToUpdate, "Fileset", f => f.Ticket, f => f.Requester, f => f.ResearchCentre, f => f.ProjectId, f => f.ProjectTitle, f => f.Name, f => f.InternalId,
f => f.Email, f => f.StartDate, f => f.EndDate, f => f.Quota, f => f.StoragePath, f => f.Adgroup))
{
try
{
await _context.SaveChangesAsync();
return RedirectToPage("./Index");
}
catch (DbUpdateConcurrencyException ex)
{
var exceptionEntry = ex.Entries.Single();
var clientValues = (Fileset)exceptionEntry.Entity;
var databaseEntry = exceptionEntry.GetDatabaseValues();
if (databaseEntry == null)
{
ModelState.AddModelError(string.Empty, "Unable to save. " + "The fileset was deleted by another user");
return Page();
}
var dbValues = (Fileset)databaseEntry.ToObject();
await SetDbErrorMessage(dbValues, clientValues, _context);
// Save the current RowVersion so next postback
// matches unless an new concurrency issue happens.
Fileset.RowVersion = (byte[])dbValues.RowVersion;
// Must clear the model error for the next postback.
ModelState.Remove("Fileset.RowVersion");
}
}
FilesetSL = new SelectList(_context.Fileset, "ID", "Project ID", filesetToUpdate.ProjectId);
return Page();
}
private IActionResult HandleDeletedFileset()
{
var fileset = new Fileset();
// ModelState contains the posted data because of the deletion error and will overide the Department instance values when displaying Page().
ModelState.AddModelError(string.Empty,
"Unable to save. The Fileset was deleted by another user.");
FilesetSL = new SelectList(_context.Fileset, "ID", "Project ID", fileset.ProjectId);
return Page();
}
private async Task SetDbErrorMessage(Fileset dbValues, Fileset clientValues, TestSiteContext context)
{
if (dbValues.Ticket != clientValues.Ticket)
{
ModelState.AddModelError("Fileset.Ticket",
$"Current value: {dbValues.Ticket}");
}
if (dbValues.Requester != clientValues.Requester)
{
ModelState.AddModelError("Fileset.Requester",
$"Current value: {dbValues.Requester}");
}
if (dbValues.ResearchCentre != clientValues.ResearchCentre)
{
ModelState.AddModelError("Fileset.ResearchCentre",
$"Current value: {dbValues.ResearchCentre}");
}
if (dbValues.ProjectId != clientValues.ProjectId)
{
ModelState.AddModelError("Fileset.ProjectId",
$"Current value: {dbValues.ProjectId}");
}
if (dbValues.ProjectTitle != clientValues.ProjectTitle)
{
ModelState.AddModelError("Fileset.ProjectTitle",
$"Current value: {dbValues.ProjectTitle}");
}
if (dbValues.Name != clientValues.Name)
{
ModelState.AddModelError("Fileset.Name",
$"Current value: {dbValues.Name}");
}
if (dbValues.InternalId != clientValues.InternalId)
{
ModelState.AddModelError("Fileset.InternalId",
$"Current value: {dbValues.InternalId}");
}
if (dbValues.Email != clientValues.Email)
{
ModelState.AddModelError("Fileset.Email",
$"Current value: {dbValues.Email}");
}
if (dbValues.StartDate != clientValues.StartDate)
{
ModelState.AddModelError("Fileset.StartDate",
$"Current value: {dbValues.StartDate}");
}
if (dbValues.EndDate != clientValues.EndDate)
{
ModelState.AddModelError("Fileset.EndDate",
$"Current value: {dbValues.EndDate}");
}
if (dbValues.Quota != clientValues.Quota)
{
ModelState.AddModelError("Fileset.Quota",
$"Current value: {dbValues.Quota}");
}
if (dbValues.StoragePath != clientValues.StoragePath)
{
ModelState.AddModelError("Fileset.StoragePath",
$"Current value: {dbValues.StoragePath}");
}
if (dbValues.Adgroup != clientValues.Adgroup)
{
Fileset fileset = await context.Fileset
.FindAsync(dbValues.Adgroup);
ModelState.AddModelError("Fileset.Adgroup",
$"Current value: {fileset?.Adgroup}");
}
ModelState.AddModelError(string.Empty,
"The record you attempted to edit "
+ "was modified by another user after you. The "
+ "edit operation was canceled and the current values in the database "
+ "have been displayed. If you still want to edit this record, click "
+ "the Save button again.");
}
Что приводит к:
The record you attempted to edit was modified by another user after you. The edit operation was canceled and the current values in the database have been displayed. If you still want to edit this record, click the Save button again.
Я могу создавать и удалять записи без каких-либо проблем, но Редактирование продолжает сталкиваться с проблемами параллелизма. Даже второй постбэк не работает.