0

I have trouble with adding an entity to database which contains an relation to existing object. I searched alot and couldn't find proper solution for this. I will describe this as simple as I can.

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; }
}

And in repository im adding records like this. This is generic class

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

Now when i try to add new record like this

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));

Store relation is added as new store and it get's new ID. Does someone have idea how i can solve this?

Example from database:

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

It's my first question on stackoverflow.

Hesh
  • 141
  • 1
  • 8

1 Answers1

1

The most probable cause of your issue is that you are creating a new instance of your context for the insert opreration. Because of that, this new context not only gets a new product but also a store, which is received from another context, but this newly created context doesn't have any idea the store is already in the database.

A general issue then is incorrect managing the lifecycle of your database contexts. EF instances are tied to contexts that were used to receive them and you can't just put an entity from a context into another context.

Instead of creating a new context in each of your manager operations, you should share the instance of the database context between multiple managers.

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;
    }
}

The orchestration has to first create the context and make sure it's shared between the two managers

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));

Usually, all these are created in a single scope, e.g. in a request scope, so that a single web request has a single database context and each repositories get the very same instance of the context.

You can also follow an official tutorial.

Wiktor Zychla
  • 47,367
  • 6
  • 74
  • 106
  • I use a classes to describe data access layer public class ProductDal : EntityRepositoryBase, IProductDal { } and they are using this Context. Then im passing this Dal class trought construtctor to manager public ProductManager(IProductDal productDal) : base(productDal) { _productDal = productDal; } – Hesh Jan 06 '20 at 17:38
  • Is this not similar to your solution? – Hesh Jan 06 '20 at 17:40
  • Could be, however for some reason you are still creating a new instance of the context in the `Insert` method. This is the cause of your issue. – Wiktor Zychla Jan 06 '20 at 18:20
  • Okay, I implemented it in that way and it's work. Thank you my savior! I got only last question. Now im creating new instance of DbContext in ProductController, here i have storeManager and productManager. Now if i move to other controller and create here another instace of DbContext beacuse i will need it may cause problems? – Hesh Jan 06 '20 at 18:43
  • No. A controller instance is created per request. A new request - a new instance of a controller. Thus, creating instances of a db context per controller in a MVC app is quite safe. Remember to pass these db context instances down the app stack (if you need to use an instance anywhere - always pass it there rather than create a new instance there). – Wiktor Zychla Jan 06 '20 at 20:25