Ответ 1
Я должен сказать сначала, что нет единственного правильного пути решения этой проблемы. Я просто представляю здесь, что бы я мог сделать.
Во-первых, DbContext
сам реализует Единицу рабочего шаблона. Вызов SaveChanges
делает создание транзакции БД, поэтому каждый запрос, выполненный против БД, будет откат, что-то не так.
Теперь в текущем проекте есть серьезная проблема: ваш репозиторий вызывает SaveChanges
на DbContext
. Это означает, что вы делаете XXXRepository
ответственным за принятие всех изменений, внесенных вами на единицу работы, а не только изменений в объектах XXX, за которые отвечает ваш репозиторий.
Другое дело, что DbContext
тоже сам репозиторий. Поэтому абстрагирование использования DbContext
внутри другого репозитория просто создает еще одну абстракцию на существующую абстракцию, что слишком много кода IMO.
Кроме того, вам может потребоваться доступ к объектам XXX из репозитория YYY и YYY-объектов из репозитория XXX, поэтому, чтобы избежать циклических зависимостей, вы получите бесполезный MyRepository : IRepository<TEntity>
, который просто дублирует все методы DbSet
.
Я бы сбросил весь слой репозитория. Я бы использовал DbContext
непосредственно внутри уровня сервиса. Конечно, вы можете учитывать все сложные запросы, которые вы не хотите дублировать на уровне сервиса. Что-то вроде:
public MyService()
{
...
public MyEntity Create(some parameters)
{
var entity = new MyEntity(some parameters);
this.context.MyEntities.Add(entity);
// Actually commits the whole thing in a transaction
this.context.SaveChanges();
return entity;
}
...
// Example of a complex query you want to use multiple times in MyService
private IQueryable<MyEntity> GetXXXX_business_name_here(parameters)
{
return this.context.MyEntities
.Where(z => ...)
.....
;
}
}
С помощью этого шаблона каждый публичный вызов класса сервиса выполняется внутри транзакции, благодаря DbContext.SaveChanges
, являющемуся транзакционным.
Теперь для примера, который у вас есть с идентификатором, который требуется после первой вставки объекта, одним из решений является не использование идентификатора, а самого объекта. Таким образом, вы разрешаете Entity Framework и свою собственную реализацию шаблона работы.
Итак, вместо:
var entity = new MyEntity();
entity = mydbcontext.Add(entity);
// what should I put here?
var otherEntity = mydbcontext.MyEntities.Single(z => z.ID == 123);
otherEntity.OtherPropertyId = entity.Id;
uow.Commit();
у вас есть:
var entity = new MyEntity();
entity = mydbcontext.Add(entity);
var otherEntity = mydbcontext.MyEntities.Single(z => z.ID == 123);
otherEntity.OtherProperty = entity; // Assuming you have a navigation property
uow.Commit();
Если у вас нет свойства навигации или у вас более сложный случай использования, решение заключается в использовании хорошей транзакции золота внутри вашего метода общедоступной службы:
public MyService()
{
...
public MyEntity Create(some parameters)
{
// Encapuslates multiple SaveChanges calls in a single transaction
// You could use a ITransaction if you don't want to reference System.Transactions directly, but don't think it really useful
using (var transaction = new TransactionScope())
{
var firstEntity = new MyEntity { some parameters };
this.context.MyEntities.Add(firstEntity);
// Pushes to DB, this'll create an ID
this.context.SaveChanges();
// Other commands here
...
var newEntity = new MyOtherEntity { xxxxx };
newEntity.MyProperty = firstEntity.ID;
this.context.MyOtherEntities.Add(newEntity);
// Pushes to DB **again**
this.context.SaveChanges();
// Commits the whole thing here
transaction.Commit();
return firstEntity;
}
}
}
Вы можете даже вызвать метод нескольких служб внутри области транзакций, если это необходимо:
public class MyController()
{
...
public ActionResult Foo()
{
...
using (var transaction = new TransactionScope())
{
this.myUserService.CreateUser(...);
this.myCustomerService.CreateOrder(...);
transaction.Commit();
}
}
}