#c# #entity-framework-core
#c# #сущность-фреймворк-ядро
Вопрос:
В моем решении у меня есть 3-уровневая структура, где два нижних уровня — это логический уровень и уровень базы данных. Уровень базы данных состоит из всех методов CRUD для каждого объекта в моей базе данных. И на логическом уровне у нас есть CRUD-сервисы для каждого CRUD-метода базы данных объектов (EntityService.Добавить / Получить / Обновить / Удалить -> EntityRepository.Добавить / Получить / Обновить / Удалить).
Итак, здесь я пытаюсь создать большой метод сохранения в службах, который сохраняет несколько объектов. Метод вызывает несколько Add
методов на логическом уровне для каждой сущности, которые, в свою очередь, вызывают метод базы Add
данных. Я мог бы добавить, что все методы CRUD базы данных заканчиваются SaveChanges()
. Конечно, я хочу, чтобы метод сохранения был Rollbacked
, если что-то пойдет не так, чтобы в нашей базе данных не было несвязанных строк, поэтому я добавил BeginTransaction()
метод сохранения.
Возможно, стоит упомянуть, что после каждого CRUD мы проверяем, чтобы ничего не пошло не так, и если это так, мы делаем возврат с кодом возврата и еще много чего. Эти возвраты происходят до transaction.Commit()
Метод сохранения структурирован следующим образом: (логический уровень)
public void SaveMethod(...)
{
using (var context = _unitOfWork.EntityRepository.GetContext()) //Gets the dbContext
{
using (var transaction = context.Database.BeginTransaction())
{
try
{
Service.Entity1.AddEntity1();
Service.Entity2.AddEntity2();
//And so on...
transaction.Commit();
}
catch{
//Exception handling...
}
}
}
}
Метод, который получает контекст для транзакции метода сохранения: (уровень базы данных)
public AniPlanContext GetContext()
{
var dbBuilder = new DbContextOptionsBuilder<MyContext>();
var dbConn = _configuration.GetConnectionString("dbContextConnectionstring"); //Gets the db connection string
var options = dbBuilder.UseSqlServer(dbConn).Options;
return new AniPlanContext(options);
}
Как выглядит метод AddEntity: (Логический уровень)
public Entity AddClinic(...)
{
try
{
//Validation....
Entity entity = _unitOfWork.EntityRepository.Add(
new Entity
{
//Set the attributes...
});
return entity;
}
catch (Exception ex)
{
//Exception
}
}
Как выглядит добавление базы данных: (Уровень базы данных)
public TEntity Add(TEntity entity)
{
if (entity == null)
{
throw new ArgumentNullException($"{nameof(Add)} entity must not be null");
}
try
{
_context.Add(entity);
_context.SaveChanges();
return entity;
}
catch (Exception)
{
throw new Exception($"{nameof(entity)} could not be saved");
}
}
Во всяком случае, моя проблема заключается в том, что BeginTransaction
фиксирует то, что было сделано AddEntity()
, даже если метод сохранения выходит из строя или каким-либо образом завершается сбоем. Можете ли вы помочь мне понять, почему это так и как я могу это исправить?
Я пробовал, TransactionScope
и это работает, но при чтении нескольких сообщений в блогах это звучит как BeginTransaction()
сохранение и более надежный способ. Из того, что я понял, оба они должны быть disposed
, и rollbacked
если это не проходит transaction.Commit
или scope.Complete
, это правильно?
Итак, чтобы подвести итог или уточнить: я хотел бы использовать BeginTransaction()
для сохранения нескольких объектов в базе данных, но это также откат / удаление транзакции, если что-то пошло не так.
Комментарии:
1. Не используйте несколько
SaveChanges
для начала. Вам не понадобилась бы явная транзакция, если бы вы использовали EF по назначению — DbContest — это единица работы. Он предназначен для сбора всех изменений и применения их только один раз. Если вы хотите «откат», просто не вызывайтеSaveChanges
перед выходом изusing
блока. Если вы хотите применить их,SaveChanges
использует внутреннюю транзакцию, поэтому явная транзакция также не требуется2. Так
I'd like to use BeginTransaction() to save several entities
что — не3. Фактически, вы
Add
можете выполнить 500 удалений и 20 ОБНОВЛЕНИЙ именно потомуSaveChanges
, что сохраняет все изменения, а не только последние. Вот почему «общий» репозиторий является антишаблоном при использовании с ORM4. @FearlessFox вам не нужен ни один из них. Лучший способ — просто не пытаться наложить низкоуровневый репозиторий поверх высокоуровневого ORM. DbContext — это UoW, DbSet — это репозиторий. Прочитайте книгу Гуннара Пейпмана » Нет необходимости в репозиториях», и единицей работы с ядром EF и репозиторием является новый синглтон . Проблемы с «общими» репозиториями известны более 10 лет, это не что-то новое
5. @TomasChabada тогда вы должны понимать, что весь дизайн должен быть отменен. В коде, который не был опубликован, могут быть другие соединения, задействованные в нескольких контекстах. Мы не знаем, с каким контекстом
Add
работают методы. Ни один из этих кодов не был бы нужен без антипаттерна «репозиторий». И исправление одной ошибки здесь не исправит общие ошибки дизайна, логические ошибки или требования к долговременным соединениям и проблемы с блокировкой, которые это создает
Ответ №1:
Как договорились @PanagiotisKanavos и @Tomas Chabada, проблема заключается не в ошибке, а в моей структуре решения.
Эта ссылка предоставляет (по крайней мере, для меня) новый способ просмотра структуры UnitOfWork / репозитория. По-видимому, они не нужны, что совершенно ошеломляет. Стоит прочитать, если вы когда-нибудь столкнетесь с той же проблемой, что и у меня выше. Возможно, ошибочна не транзакция, а то, как мы используем ядро EF и DbContext и их транзакции.