简体   繁体   中英

Entity Framework 6 - Dependency Injection with Unity - Repository pattern - Add or Update exception for many to many relationship

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

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

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

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

https://docs.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);
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM