Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен
Шрифт:
Интервал:
Закладка:
modelBuilder.Entity<Make>(entity =>
{
entity.HasMany(e => e.Cars)
.WithOne(c => c.MakeNavigation!)
.HasForeignKey(k => k.MakeId)
.OnDelete(DeleteBehavior.Restrict)
.HasConstraintName("FK_Make_Inventory");
});
Сущность Order
Для сущности Order обновите имена навигационных свойств и добавьте утверждение, что обратные навигационные свойства не равны null. Вместо ограничения удалений отношение Customer с Order настраивается на каскадное удаление:
modelBuilder.Entity<Order>(entity =>
{
entity.HasOne(d => d.CarNavigation)
.WithMany(p => p!.Orders)
.HasForeignKey(d => d.CarId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Orders_Inventory");
entity.HasOne(d => d.CustomerNavigation)
.WithMany(p => p!.Orders)
.HasForeignKey(d => d.CustomerId)
.OnDelete(DeleteBehavior.Cascade)
.HasConstraintName("FK_Orders_Customers");
});
Настройте фильтр для свойства CarNavigation сущности Order, чтобы отфильтровывать неуправляемые автомобили. Обратите внимание, что этот код находится не в том же блоке, где был предыдущий код. Никаких формальных причин разносить код не существует; просто здесь демонстрируется альтернативный синтаксис для конфигурирования в отдельных блоках:
modelBuilder.Entity<Order>().HasQueryFilter(e => e.CarNavigation!.IsDrivable);
Сущность Car
Шаблонный класс содержит конфигурацию для класса Inventory, который понадобится изменить на Car. Свойство TimeStamp нужно удалить, а в конфигурации навигационных свойств изменить имена свойств на MakeNavigation и Cars. Сущность получает фильтр запросов для отображения по умолчанию только управляемых автомобилей и устанавливает стандартное значение свойства IsDrivable в true. Код должен иметь следующий вид:
modelBuilder.Entity<Car>(entity =>
{
entity.HasQueryFilter(c => c.IsDrivable);
entity.Property(p
=> p.IsDrivable).HasField("_isDrivable").HasDefaultValue(true);
entity.HasOne(d => d.MakeNavigation)
.WithMany(p => p.Cars)
.HasForeignKey(d => d.MakeId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Make_Inventory");
});
Специальные исключения
Распространенный прием для обработки исключений предусматривает перехват системного исключения (и/или исключения EF Core, как в текущем примере), его регистрацию в журнале и генерацию специального исключения. Если специальное исключение перехватывается вышерасположенным методом, то разработчику известно, что исключение уже было зарегистрировано в журнале, и необходимо только отреагировать на него надлежащим образом в коде.
Создайте в проекте AutoLot.Dal новый каталог по имени Exceptions и поместите в него четыре новых файла классов, CustomException.cs, CustomConcurrencyException.cs, CustomDbUpdateException.cs и CustomRetryLimitExceededException.cs, содержимое которых показано ниже:
// CustomException.cs
using System;
namespace AutoLot.Dal.Exceptions
{
public class CustomException : Exception
{
public CustomException() {}
public CustomException(string message) : base(message) { }
public CustomException(string message, Exception innerException)
: base(message, innerException) { }
}
}
// CustomConcurrencyException.cs
using Microsoft.EntityFrameworkCore;
namespace AutoLot.Dal.Exceptions
{
public class CustomConcurrencyException : CustomException
{
public CustomConcurrencyException() { }
public CustomConcurrencyException(string message) : base(message) { }
public CustomConcurrencyException(
string message, DbUpdateConcurrencyException innerException)
: base(message, innerException) { }
}
}
// CustomDbUpdateException.cs
using Microsoft.EntityFrameworkCore;
namespace AutoLot.Dal.Exceptions
{
public class CustomDbUpdateException : CustomException
{
public CustomDbUpdateException() { }
public CustomDbUpdateException(string message) : base(message) { }
public CustomDbUpdateException(
string message, DbUpdateException innerException)
: base(message, innerException) { }
}
}
// CustomRetryLimitExceededException.cs
using System;
using Microsoft.EntityFrameworkCore.Storage;
namespace AutoLot.Dal.Exceptions
{
public class CustomRetryLimitExceededException : CustomException
{
public CustomRetryLimitExceededException() { }
public CustomRetryLimitExceededException(string message)
: base(message) { }
public CustomRetryLimitExceededException(
string message, RetryLimitExceededException innerException)
: base(message, innerException) { }
}
}
На заметку! Обработка специальных исключений была подробно раскрыта в главе 7.
Переопределение метода SaveChanges()
Как обсуждалось в предыдущей главе, метод SaveChanges() базового класса DbContext сохраняет результаты операций изменения, добавления и удаления в базе данных. Переопределение этого метода позволяет инкапсулировать обработку исключений в одном месте. Располагая специальными исключениями, добавьте оператор using для AutoLot.Dal.Exceptions в начало файла ApplicationDbContext.cs, после чего переопределите метод SaveChanges():
public override int SaveChanges()
{
try
{
return base.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
// Произошла ошибка параллелизма.
// Подлежит регистрации в журнале и надлежащей обработке.
throw new CustomConcurrencyException(
"A concurrency error happened.", ex);
// Произошла ошибка параллелизма
}
catch (RetryLimitExceededException ex)
{
// Подлежит регистрации в журнале и надлежащей обработке.
throw new CustomRetryLimitExceededException(
"There is a problem with SQl Server.", ex);
// Возникла проблема c SQL Server
}
catch (DbUpdateException ex)
{
// Подлежит регистрации в журнале и надлежащей обработке.
throw new CustomDbUpdateException(
"An error occurred updating the database", ex);
// Произошла ошибка при обновлении базы данных
}
catch (Exception ex)
{
// Подлежит регистрации в журнале и надлежащей обработке.
throw new CustomException(
"An error occurred updating the database", ex);
// Произошла ошибка при обновлении базы данных
}
}
Обработка событий DbContext и ChangeTracker
Перейдите к конструктору класса ApplicationDbContext и добавьте три события DbContext, которые обсуждались в предыдущей главе:
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
base.SavingChanges += (sender, args) =>
{
Console.WriteLine($"Saving changes for {((ApplicationDbContext)
sender)!.Database!.GetConnectionString()}");
};
base.SavedChanges += (sender, args) =>
{
Console.WriteLine($"Saved {args!.EntitiesSavedCount} changes for
{((ApplicationDbContext)sender)!.Database!.GetConnectionString()}");
};
base.SaveChangesFailed += (sender, args) =>
{
Console.WriteLine(
$"An exception occurred! {args.Exception.Message} entities");
};
}
Затем добавьте обработчики для событий StateChanged