Добавление записи со связанным дочерним объектом в Entity Framework с использованием шаблона репозитория

У меня возникли проблемы с добавлением объекта в базу данных, который содержит отношение к существующему объекту. Я много искал и не мог найти подходящего решения для этого. Я опишу это настолько просто, насколько смогу.

public class Store : IEntity
{
    public int StoreId { get; set; }
    public string StoreName { get; set; }

    public virtual Address Address { get; set; }

    public virtual Contractor Contractor { get; set; }
}

    public class Product : IEntity
{
    public int ProductId { get; set; }
    public string ProductName { get; set; }
    public decimal Price { get; set; }
    public virtual Store Store { get; set; }
}

И в репозиторий я добавляю такие записи. Это общий класс

        public TEntity Add(TEntity entity)
    {
        using (var context = new TContext())
        {
            var addedEntity = context.Entry(entity);
            addedEntity.State = EntityState.Added;
            context.SaveChanges();
            return entity;
        }
    }

Теперь, когда я пытаюсь добавить новую запись, подобную этой

var store = storeManager.GetBy(x => x.StoreId == 1);

var product = new Product() { ProductName = "Bananas", Store = store }; 

productManager.Add(product);

productManager.GetAll().ForEach(x => Console.WriteLine(x.ProductName + " " + x.Store.StoreId));

Отношение к магазину добавляется как новый магазин и получает новый идентификатор. Кто-нибудь знает, как я могу это решить?

Пример из базы данных:

StoreId StoreName   Address_AddressId   Contractor_ContractorId
1   NULL    1   1
2   NULL    2   2
3   NULL    3   3
4   NULL    4   4
5   NULL    5   5
6   NULL    6   6
7   NULL    7   7

Это мой первый вопрос о stackoverflow.


person Hesh    schedule 06.01.2020    source источник
comment
есть ли сообщение об ошибке?   -  person adeel41    schedule 06.01.2020
comment
Нет, это просто добавление новых и новых магазинов   -  person Hesh    schedule 06.01.2020


Ответы (1)


Наиболее вероятная причина вашей проблемы заключается в том, что вы создаете новый экземпляр своего контекста для операции вставки. Из-за этого этот новый контекст получает не только новый продукт, но и магазин, полученный из другого контекста, но этот вновь созданный контекст не имеет ни малейшего представления о том, что магазин уже находится в базе данных.

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

Вместо создания нового контекста в каждой из ваших операций менеджера вы должны совместно использовать экземпляр контекста базы данных между несколькими менеджерами.

public class StoreManager
{
     public StoreManager( Context context )
     {
         this.context = context;
     }

   public TEntity Add(TEntity entity)
   {
        var addedEntity = context.Entry(entity);
        addedEntity.State = EntityState.Added;
        context.SaveChanges();
        return entity;
    }
}

Оркестровка должна сначала создать контекст и убедиться, что он разделен между двумя менеджерами.

var context = new DbContext();

var storeManager   = new StoreManager( context );
var productManager = new ProductManager( context );

var store = storeManager.GetBy(x => x.StoreId == 1);
var product = new Product() { ProductName = "Bananas", Store = store }; 

productManager.Add(product);

productManager.GetAll().ForEach(x => Console.WriteLine(x.ProductName + " " + 
    x.Store.StoreId));

Обычно все они создаются в одной области, например. в области запроса, чтобы один веб-запрос имел один контекст базы данных, и каждый репозиторий получал один и тот же экземпляр контекста.

Вы также можете подписаться на официальный учебник.

person Wiktor Zychla    schedule 06.01.2020
comment
Я использую классы для описания открытого класса уровня доступа к данным ProductDal: EntityRepositoryBase‹Product, DatabaseContext›, IProductDal {}, и они используют этот контекст. Затем я передаю этот класс Dal через конструктор менеджера public ProductManager(IPProductDal productDal): base(productDal) { _productDal = productDal; } - person Hesh; 06.01.2020
comment
Разве это не похоже на ваше решение? - person Hesh; 06.01.2020
comment
Возможно, однако по какой-то причине вы все еще создаете новый экземпляр контекста в методе Insert. Это причина вашей проблемы. - person Wiktor Zychla; 06.01.2020
comment
Хорошо, я реализовал это таким образом, и это работает. Спасибо, мой спаситель! У меня только последний вопрос. Теперь я создаю новый экземпляр DbContext в ProductController, здесь у меня есть storeManager и productManager. Теперь, если я перейду на другой контроллер и создам здесь еще один экземпляр DbContext, потому что он мне понадобится, это может вызвать проблемы? - person Hesh; 06.01.2020
comment
Нет. Экземпляр контроллера создается для каждого запроса. Новый запрос - новый экземпляр контроллера. Таким образом, создание экземпляров контекста БД для каждого контроллера в приложении MVC вполне безопасно. Не забудьте передать эти экземпляры контекста базы данных вниз по стеку приложения (если вам нужно использовать экземпляр где-либо — всегда передавайте его туда, а не создавайте там новый экземпляр). - person Wiktor Zychla; 06.01.2020