Следующий код кажется немного странным:
var loanViewModel = loanEditorViewModel.LoanViewModel;
loanViewModel.LoanProduct = LoanProductService.GetLoanProductById(loanViewModel.LoanProductId); // <-- don't want to add to this table in database
loanViewModel.Borrower = BorrowerService.GetBorrowerById(loanViewModel.BorrowerId); //<-- don't want to add to this table in database
Models.Loans.Loan loan = AutoMapper.Mapper.Map<Models.Loans.Loan>(loanEditorViewModel.LoanViewModel);
Вы устанавливаете ссылки на сущности в модели представления, а затем вызываете automapper. ViewModels не должен содержать ссылки на сущности, а Automapper должен эффективно игнорировать любые ссылочные сущности и отображать только создаваемую структуру сущностей. Automapper будет создавать новые экземпляры на основе передаваемых данных.
Вместо этого что-то вроде этого должно работать как ожидалось:
// Assuming these will throw if not found? Otherwise assert that these were returned.
var loanProduct = LoanProductService.GetLoanProductById(loanViewModel.LoanProductId);
var borrower = BorrowerService.GetBorrowerById(loanViewModel.BorrowerId);
Models.Loans.Loan loan = AutoMapper.Mapper.Map<Models.Loans.Loan>(loanEditorViewModel.LoanViewModel);
loan.LoanProduct = loanProduct;
loan.Borrower = borrower;
Edit:
Следующее, что нужно проверить, это то, что ваши Сервисы используют точно такую же ссылку на DbContext. Используете ли вы Dependency Injection с контейнером IoC, таким как Autofac или Unity? Если это так, убедитесь, что DbContext зарегистрирован как экземпляр для запроса или аналогичная область действия. Если Сервисы эффективно вводят новый DbContext, то LoanService DbContext не будет знать об экземплярах Продукта и Заемщика, которые были извлечены DbContext другого сервиса.
Если вы не используете библиотеку DI, вам следует рассмотреть возможность ее добавления. В противном случае вам нужно будет обновить ваши сервисы, чтобы принимать один DbContext с каждым вызовом или использовать шаблон единицы работы, такой как DhContextScope Mehdime, чтобы облегчить сервисам разрешение их DbContext из единицы работы.
Например, чтобы обеспечить тот же DbContext:
using (var context = new MyDbContext())
{
var loanProduct = LoanProductService.GetLoanProductById(context, loanViewModel.LoanProductId);
var borrower = BorrowerService.GetBorrowerById(context, loanViewModel.BorrowerId);
Models.Loans.Loan loan = AutoMapper.Mapper.Map<Models.Loans.Loan>(loanEditorViewModel.LoanViewModel);
loan.LoanProduct = loanProduct;
loan.Borrower = borrower;
LoanService.AddNewLoan(context, loan);
}
Если вы уверены, что все службы имеют один и тот же экземпляр DbContext, то в вашем методе Entities.Add () может произойти что-то странное. Честно говоря, в вашем решении слишком много абстракций вокруг такой простой вещи, как операция создания и ассоциации CRUD. Это похоже на случай преждевременной оптимизации кода для DRY, не начиная с самого простого решения. Код может более просто создать контекст DbContext, извлечь соответствующие сущности, создать новый экземпляр, связать, добавить в DbSet и SaveChanges. Нет смысла абстрагировать вызовы для элементарных операций, таких как выборка ссылки по идентификатору.
public ActionResult Add(Models.ViewModels.Loans.LoanEditorViewModel loanEditorViewModel)
{
if (!ModelState.IsValid)
return View(loanEditorViewModel);
var loanViewModel = loanEditorViewModel.LoanViewModel;
using (var context = new AppContext())
{
var loanProduct = context.LoanProducts.Single(x => x.LoanProductId ==
loanViewModel.LoanProductId);
var borrower = context.Borrowers.Single(x => x.BorrowerId == loanViewModel.BorrowerId);
var loan = AutoMapper.Mapper.Map<Loan>(loanEditorViewModel.LoanViewModel);
loan.LoanProduct = loanProduct;
loan.Borrower = borrower;
context.SaveChanges();
}
return RedirectToAction("Index");
}
Посыпать какой-то обработкой исключений, и все готово и запылено. Нет слоистых сервисных абстракций. Оттуда вы можете стремиться сделать тестирование возможным с помощью контейнера IoC, такого как Autofac, для управления контекстом и / или введения шаблона UoW хранилища / сервисного уровня / w. Вышеуказанное послужит минимальным жизнеспособным решением для действия. Любая абстракция и т. Д. Должны применяться позже. Нарисуйте карандашом, прежде чем растрескивать масла. :)
Используя DhContextScope от Mehdime, он будет выглядеть так:
public ActionResult Add(Models.ViewModels.Loans.LoanEditorViewModel loanEditorViewModel)
{
if (!ModelState.IsValid)
return View(loanEditorViewModel);
var loanViewModel = loanEditorViewModel.LoanViewModel;
using (var contextScope = ContextScopeFactory.Create())
{
var loanProduct = LoanRepository.GetLoanProductById( loanViewModel.LoanProductId).Single();
var borrower = LoanRepository.GetBorrowerById(loanViewModel.BorrowerId);
var loan = LoanRepository.CreateLoan(loanViewModel, loanProduct, borrower).Single();
contextScope.SaveChanges();
}
return RedirectToAction("Index");
}
В моем случае я использую шаблон репозитория, который использует DbContextScopeLocator для разрешения его ContextScope, чтобы получить DbContext. Repo управляет извлечением данных и гарантирует, что создание сущностей получает все необходимые данные, необходимые для создания полной и действительной сущности. Я выбираю репозиторий на контроллер, а не что-то вроде универсального шаблона или репозитория / сервиса на объект, потому что IMO лучше управляет принципом единой ответственности, учитывая, что у кода есть только одна причина для изменения (он служит контроллеру, не разделяемому между многими контроллеры с потенциально разными проблемами). Модульные тесты могут макетировать хранилище для обслуживания ожидаемого состояния данных. Методы repo get возвращают IQueryable
, чтобы потребительская логика могла определить, как она хочет использовать данные.