1

I have a problem when adding new values with a many to many mapping in Entity Framework. I know about the unit of work pattern but in our solution we would like to keep a simple repository pattern and not a unit of work class that contains everything. Is this possible or should I just implement Unit of Work right away?

If I don't use iSupplierRepository below a supplier will be added, but it will always add a new one even though there already exists one with that name.

Error:

The relationship between the two objects cannot be defined because they are attached to different ObjectContext objects.

Repository example:

public class SupplierRepository : IntEntityRepository<Supplier, DbContext>, ISupplierRepository
{
    public SupplierRepository(DbContext context) : base(context, context.Suppliers)
    {
    }
}

Inherited repositories:

public class IntEntityRepository<TEntity, TContext> : EntityRepository<TEntity, TContext, int>
    where TEntity : class, IEntity<int>
    where TContext : BaseIdentityDbContext
{
    public IntEntityRepository(TContext context, IDbSet<TEntity> set) : base(context, set)
    {
    }

    public override async Task<TEntity> GetAsync(int id)
    {
        return (await GetAsync(entity => entity.Id == id)).SingleOrDefault();
    }
...

 public abstract class EntityRepository<TEntity, TContext, TId> : IEntityRepository<TEntity, TId>
    where TEntity : class, IEntity<TId>
    where TContext : BaseIdentityDbContext
{
    protected TContext Context { get; }
    protected IDbSet<TEntity> Set { get; }

     protected EntityRepository(TContext context, IDbSet<TEntity> set)
     {
         Context = context;
         Set = set;
     }

     public abstract Task<TEntity> GetAsync(TId id);
...

Unity:

container.RegisterType<ISupplierRepository, SupplierRepository>();
container.RegisterType<IContactRepository, ContactRepository>();

Controller:

private readonly IContactRepository iContactRepository;
private readonly ISupplierRepository iSupplierRepository;

public ContactsController(IContactRepository iContactRepository, ISupplierRepository iSupplierRepository)
{
    this.iContactRepository = iContactRepository;
    this.iSupplierRepository = iSupplierRepository;
}

[HttpPut]
[Route("UpdateContact/{id}")]
public async Task<IHttpActionResult> UpdateContact(ContactViewModel contactVm, int id)
{
    try
    {
        var supplierList = new List<Supplier>();
        foreach (var contactVmSupplier in contactVm.Suppliers)
        {
            var supplier = await iSupplierRepository.GetAsync(contactVmSupplier.Id);
            supplierList.Add(supplier);
        }

        var contactOriginal = await iContactRepository.GetAsync(id);
        var updatedContact = Mapper.Map<ContactViewModel, Contact>(contactVm, contactOriginal);
        updatedContact.Suppliers = supplierList;

        await iContactRepository.UpdateAsync(updatedContact);
        return Ok();
    }
    catch (Exception e)
    {
        throw new Exception("Could not update a contact", e);
    }

}

Viewmodels:

public class ContactViewModel
{
    public int Id { get; set; }

    public string Name { get; set; }

    public ICollection<SupplierViewModel> Suppliers { get; set; }
}

public class SupplierViewModel
{
    public int Id { get; set; }

    public string Name { get; set; }
}

Models:

public class Contact : IEntity<int>
{
    public Contact()
    {
        Suppliers = new List<Supplier>();
    }

    [Key]
    public int Id { get; set; }

    public DateTime Created { get; set; }

    public DateTime Updated { get; set; }

    public string Name { get; set; }

    public ICollection<Supplier> Suppliers { get; set; }

}

public class Supplier: IEntity<int>
{
    public Supplier()
    {
        Contacts = new List<Contact>();
    }
    [Key]
    public int Id { get; set; }

    public DateTime Created { get; set; }

    public DateTime Updated { get; set; }

    public string Name { get; set; }

    public virtual ICollection<Contact> Contacts { get; set; }
}
Ogglas
  • 62,132
  • 37
  • 328
  • 418

3 Answers3

3

If you install the Unity bootstrapper for ASP.NET Web API package, a UnityHierarchicalDependencyResolver is available which will use a new child container for each IHttpController resolution effectively making all registrations with a HierarchicalLifetimeManager resolved per request so that all repository instances in a controller will use the same DbContext.

The NuGet package will also install some bootstrapping code in App_Start which uses WebActivatorEx. You can either use this approach or change to align with what you are using right now. Based on your posted code it would look something like:

public static void ConfigureUnity(HttpConfiguration config)
{
    var container = new UnityContainer();
    container.RegisterType<DbContext>(new HierarchicalLifetimeManager());
    container.RegisterType<ISupplierRepository, SupplierRepository>();
    container.RegisterType<IContactRepository, ContactRepository>();
    config.DependencyResolver = new UnityHierarchicalDependencyResolver(container);
}
Randy Levy
  • 22,566
  • 4
  • 68
  • 94
0

Update: Use Randy Levy's answer instead.


My recommendation here is not to use Repository or UoW at all. EF already has them implemented. You'll encounter a lot of issues trying to re-implement them.

As to specific issue you encounter with exception: you have to use the same DbContext for your entities. At the same time, you wouldn't like to use DbContext as Singleton and use it per-request instead. A possible solution for it might be found here.

Application_BeginRequest(...)
{
  var childContainer = _container.CreateChildContainer();
  HttpContext.Items["container"] = childContainer;
  childContainer.RegisterType<ObjectContext, MyContext>
     (new ContainerControlledLifetimeManager());
}

Application_EndRequest(...)
{
  var container = HttpContext.Items["container"] as IUnityContainer
  if(container != null)
    container.Dispose();
}
Ogglas
  • 62,132
  • 37
  • 328
  • 418
Vlad Stryapko
  • 1,035
  • 11
  • 29
  • Thanks for input, will consider it but in a best case scenario I would like my code to not be dependent on `DbContext` and only a generic interface implementation like my example. – Ogglas Jul 11 '17 at 11:21
  • Depending on `DbContext` is arguable, but it's out of scope for this question. I think the option with child container is a way to go then. – Vlad Stryapko Jul 11 '17 at 11:25
  • Posted an update myself. Initial testing shows that it works, if not I will have to find another way. By doing like this I'm not dependent on `DbContext` in code and not `Unit of Work pattern` either. – Ogglas Jul 11 '17 at 11:51
0

Solved it like this, dependency injection is from the tutorial Dependency Injection in ASP.NET Web API 2.

https://learn.microsoft.com/en-us/aspnet/web-api/overview/advanced/dependency-injection

App_Start -> WebApiConfig

public static void Register(HttpConfiguration config)
{
    UnityConfig.ConfigureUnity(config);
...

UnityConfig:

public static void ConfigureUnity(HttpConfiguration config)
{
    var context = new DbContext();
    var container = new UnityContainer();
    container.RegisterType<ISupplierRepository, SupplierRepository>(new InjectionConstructor(context));
    container.RegisterType<IContactRepository, ContactRepository>(new InjectionConstructor(context));
    config.DependencyResolver = new UnityResolver(container);
}
Ogglas
  • 62,132
  • 37
  • 328
  • 418
  • 1
    In the posted configuration `DbContext` is a singleton. i.e. every request to the server will be using the same `DbContext`. This could cause a bunch of problems: concurrency issues, large memory usage, invalid state issues. – Randy Levy Jul 11 '17 at 14:45
  • @RandyLevy Thanks for input. Do you suggest `Unit of Work pattern` or do you have a better alternative? – Ogglas Jul 11 '17 at 14:47