Метод Add (), добавляющий повторяющиеся строки для связанных моделей в Code-First Entity Framework - PullRequest
6 голосов
/ 30 мая 2019

Ниже приведено действие, которое добавляет запрос на кредит в базу данных:

[HttpPost]
public ActionResult Add(Models.ViewModels.Loans.LoanEditorViewModel loanEditorViewModel)
{
    if (!ModelState.IsValid)
        return View(loanEditorViewModel);

    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);
    loanService.AddNewLoan(loan);
    return RedirectToAction("Index");
}

Ниже приведен метод AddNewLoan():

public int AddNewLoan(Models.Loans.Loan loan)
{
    loan.LoanStatus = Models.Loans.LoanStatus.PENDING;
    _LoanService.Insert(loan);

    return 0;
}

А вот код для Insert()

public virtual void Insert(TEntity entity)
{
    if (entity == null)
        throw new ArgumentNullException(nameof(entity));

    try
    {
        entity.DateCreated = entity.DateUpdated = DateTime.Now;
        entity.CreatedBy = entity.UpdatedBy = GetCurrentUser();

        Entities.Add(entity);
        context.SaveChanges();
    }
    catch (DbUpdateException exception)
    {
        throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
    }
}

Он успешно добавляет одну строку в таблицу Loans, но также добавляет строки в таблицу LoanProduct и Borrower, как я показал в первых комментариях к коду.

Я проверил возможность нескольких вызовов этого действия и метода Insert, но они вызываются один раз.

ОБНОВЛЕНИЕ

Я сталкиваюсь с аналогичной проблемой, но с противоположной в работепроблема здесь: Сущность не обновляется с использованием подхода Code-First

Я думаю, что эти два имеют одну и ту же причину отслеживания изменений.Но один добавляет другой, а не обновляет.

Ответы [ 4 ]

5 голосов
/ 04 июня 2019

Следующий код кажется немного странным:

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, чтобы потребительская логика могла определить, как она хочет использовать данные.

3 голосов
/ 06 июня 2019

Самый простой способ решить эту проблему - добавить два примитивных свойства внешнего ключа к классу Loan, то есть LoanProductId и BorrowerId. Например, вот так (я, очевидно, должен угадать типы LoanProduct и Заемщика):

public int LoanProductId { get; set; }
[ForeignKey("LoanProductId")]
public Product LoanProduct { get; set; }

public int BorrowerId { get; set; }
[ForeignKey("BorrowerId")]
public User Borrower { get; set; }

Без примитивных свойств FK у вас есть так называемые независимые ассоциации , которые могут быть установлены только путем назначения объектов, состоянием которых необходимо тщательно управлять. Добавление свойств FK превращает его в ассоциации внешних ключей , которые должны быть проще в установке. AutoMapper просто установит эти свойства, когда имена совпадут, и все готово.

3 голосов
/ 06 июня 2019

Наконец, с помощью ссылки, которую разделяет @GertArnold Дубликат DataType создается при каждом создании продукта

Поскольку все мои модели наследуют класс BaseModel, я изменил свой Insert метод, подобный этому:

public virtual void Insert(TEntity entity, params BaseModel[] unchangedModels)
{
    if (entity == null)
        throw new ArgumentNullException(nameof(entity));

    try
    {
        entity.DateCreated = entity.DateUpdated = DateTime.Now;
        entity.CreatedBy = entity.UpdatedBy = GetCurrentUser();

        Entities.Add(entity);

        if (unchangedModels != null)
        {
            foreach (var model in unchangedModels)
            {
                _context.Entry(model).State = EntityState.Unchanged;
            }
        }

        _context.SaveChanges();
    }
    catch (DbUpdateException exception)
    {
        throw new Exception(GetFullErrorTextAndRollbackEntityChanges(exception), exception);
    }
}

И названный так:

_LoanService.Insert(loan, loan.LoanProduct, loan.Borrower);
1 голос
/ 04 июня 2019

Check Models.Loans.Loan. Является ли это объединенной моделью таблиц Loans, LoanProduct и Borrower.

Вы должны добавить

Loans  lentity = new Loans()
lentity.property=value;
Entities.Add(lentity );

var lentity = new Loans  { FirstName = "William", LastName = "Shakespeare" };
context.Add<Loans  >(lentity );
context.SaveChanges();
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...