I have seen plenty of questions regarding the same topic, but never found solution as yet. The code that is bringing me problems is:
public async Task<IHttpActionResult> Post(FullOrderViewModel fullOrder)
{
//Get all order items
List<OrderItem> orderItems = new List<OrderItem>();
foreach (var oi in fullOrder.Order)
{
var color = _colorRepo.Find(oi.ColorId);
var item = new OrderItem
{
Quantity = oi.Quantity,
Color = color
};
orderItems.Add(item);
}
//Get customer id
var customer = await _adminRepo.GetCustomerByUserName(fullOrder.Customer.UserName);
var billAddress = _addressRepo.All.FirstOrDefault(x => x.AddressLine == fullOrder.BillingAddress.AddressLine && x.PostalCode == fullOrder.BillingAddress.PostalCode);
var deliveryAddress = _addressRepo.All.FirstOrDefault(x => x.AddressLine == fullOrder.DeliveryAddress.AddressLine && x.PostalCode == fullOrder.DeliveryAddress.PostalCode);
//CASE : sample order
if (fullOrder.OrderType == OrderType.Sample)
{
var order = new SampleOrder {
Type = OrderType.Sample,
Status = "Unauthorized",
Origin = Origin.Online,
OrderItems = orderItems,
CreationDate = DateTime.Now,
CustomerId = customer.Id,
BillAddressId = billAddress.AddressId,
DeliveryAddressId = deliveryAddress.AddressId
};
try
{
_sampleOrderRepo.InserGraph(order);
_unitOfWork.Save();
}
catch (Exception e)
{
return InternalServerError(e);
}
The way I get my repositories is via dependency injection in the OrderController constructor, so in a standard way as I believe. Each of my repos is build in this way:
public class SampleOrderRepository : EntityRepository<SampleOrder, IPhoeniceUnitOfWork<PhoeniceContext>>, ISampleOrderRepository
{
public SampleOrderRepository(IPhoeniceUnitOfWork<PhoeniceContext> unitOfWork) : base(unitOfWork)
{
}
protected override IDbSet<SampleOrder> List(IPhoeniceUnitOfWork<PhoeniceContext> unitOfWork)
{
return unitOfWork.Context.SampleOrders;
}
protected override Expression<Func<SampleOrder, bool>> FindInt(int id)
{
return x => x.OrderId == id;
}
}
Where I override methods from general entity repository:
public abstract class EntityRepository<T, TContext> : IEntityRepository<T>
where T : class, IObjectWithState
where TContext : IUnitOfWork<EntityContext>
{
protected readonly TContext _unitOfWork;
protected EntityRepository(TContext unitOfWork)
{
_unitOfWork = unitOfWork;
}
protected virtual IDbSet<T> List(TContext unitOfWork)
{
throw new NotImplementedException("List operation not implemented!");
}
protected virtual Expression<Func<T, bool>> FindInt(int id)
{
throw new NotImplementedException("Finding entity with an int is not implemented!");
}
protected virtual Expression<Func<T, bool>> FindString(string id)
{
throw new NotImplementedException("Finding entity with an int is not implemented!");
}
public virtual IQueryable<T> All { get { return List(_unitOfWork); } }
public virtual IQueryable<T> AllIncluding(params Expression<Func<T, object>>[] includeProperties)
{
return includeProperties.Aggregate(All, (current, includeProperty) => current.Include(includeProperty));
}
public virtual T Find(int id)
{
return All.FirstOrDefault(FindInt(id));
}
public virtual T FindByString(string id)
{
return All.FirstOrDefault(FindString(id));
}
public virtual void InserGraph(T entity)
{
List(_unitOfWork).Add(entity);
}
public void InsertOrUpdate(T entity)
{
if (entity.ObjectState == State.Added) // New entity
{
_unitOfWork.Context.Entry(entity).State = EntityState.Added;
}
else // Existing entity
{
_unitOfWork.Context.Entry(entity).State = EntityState.Modified;
_unitOfWork.Context.ApplyStateChanges();
}
}
public virtual void Delete(int id)
{
var entity = Find(id);
List(_unitOfWork).Remove(entity);
}
public void Dispose()
{
}
}
Finally this is my UnitOfWork item:
public class PhoeniceUnitOfWork : IPhoeniceUnitOfWork<PhoeniceContext>
{
private readonly PhoeniceContext _context;
public PhoeniceUnitOfWork() : base()
{
_context = new PhoeniceContext();
}
//Left for testing purposes
public PhoeniceUnitOfWork(PhoeniceContext context)
{
_context = context;
}
public int Save()
{
return _context.SaveChanges();
}
public void Dispose()
{
_context.Dispose();
}
public IAddressRepository AddressRepository
{
get { return new AddressRepository(this); }
}
public IColorRepository ColorRepository
{
get { return new ColorRepository(this); }
}
public IHideOrderRepository HideOrderReposiotory
{
get { return new HideOrderRepository(this); }
}
public ISampleOrderRepository SampleOrderRepository
{
get { return new SampleOrderRepository(this); }
}
public ITaxonomyRepository TaxonomyRepository
{
get { return new TaxonomyRepository(this); }
}
PhoeniceContext IUnitOfWork<PhoeniceContext>.Context
{
get { return _context; }
}
}
and context that I'm using
public class PhoeniceContext : EntityContext
{
// You can add custom code to this file. Changes will not be overwritten.
//
// If you want Entity Framework to drop and regenerate your database
// automatically whenever you change your model schema, add the following
// code to the Application_Start method in your Global.asax file.
// Note: this will destroy and re-create your database with every model change.
//
// System.Data.Entity.Database.SetInitializer(new System.Data.Entity.DropCreateDatabaseIfModelChanges<Phoenice.EntityFramework.Models.PhoeniceContext>());
public PhoeniceContext()
: base("Phoenice")
{
Database.SetInitializer<PhoeniceContext>(new DropCreateIfChangeInitializer());
//Database.SetInitializer<PhoeniceContext>(new DropCreateDatabaseAlways());
}
public DbSet<Address> Addresses { get; set; }
public DbSet<SampleOrder> SampleOrders { get; set; }
public DbSet<HideOrder> HideOrders { get; set; }
public DbSet<OrderItem> OrderItems { get; set; }
public DbSet<Color> Colors { get; set; }
public DbSet<Taxonomy> Taxonomies { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//There is OnModelCreating already overwritten in IdentityDbContext
//with all details required for ASP.NET identity
base.OnModelCreating(modelBuilder);
//solution for this mapping: http://stackoverflow.com/questions/11632951/primary-key-violation-inheritance-using-ef-code-first
modelBuilder.Entity<SampleOrder>()
.HasKey(x => x.OrderId)
.Map(m =>
{
//Requires EF 6.1
m.MapInheritedProperties();
m.ToTable("SampleOrders");
})
.Property(x => x.OrderId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<HideOrder>().Map(m =>
{
//Requires EF 6.1
m.MapInheritedProperties();
m.ToTable("HideOrders");
});
modelBuilder.Entity<HideOrder>().HasKey(x => x.OrderId);
}
}
I know that it's a lot of code that I am showing, but hope these are all of the building blocks for data access layer. For some reason when I call InsertGraph method on SampleOrders I get "An entity object cannot be referenced by multiple instances of IEntityChangeTracker" exception. I looked through this code dozens of times, and cannot get why does it throw this error message
The items in your orderItems contains color. Which is returned by:
The colorRepo holds the entities of color.
The order in the next line is used by another repo, but in 'order' there are colors from the previous repo.
Two repo's are holding the same entity object which causes this exception.
To avoid this problem you can two things:
Create a new object of Color every time you use _colorRepo.Find():
or use only one repo by moving the functionality to the services