I use Entity Framework in my project. The issue is well known but supposed solutions (eg. this and this) doesn't work for me.
/// <summary>
/// Returns complete list of lecturers from DB.
/// </summary>
public IEnumerable<Lecturer> GetAllLecturers()
{
IList<Lecturer> query;
using (var dbb = new AcademicTimetableDbContext())
{
query = (from b in dbb.Lecturers select b).ToList();
}
Debug.WriteLine(query[0].AcademicDegree); // Exception (***)
return query;
}
Exception (***):
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
public class Lecturer
{
public Lecturer()
{
this.Timetables = new List<Timetable>();
this.Courses = new List<Course>();
}
public int Id_Lecturer { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public string Phone_Number { get; set; }
public Nullable<int> Academic_Degree_Id { get; set; }
public virtual AcademicDegree AcademicDegree { get; set; }
public virtual ICollection<Timetable> Timetables { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
What's wrong?
Lazy loading works until your DbContext lives.
With the using you dispose your DbContext so EF will throw an exception when you try to access the navigation properties outside the using block.
You can test this with moving the Debug.WriteLine inside the using block where it won't throw exception:
using (var dbb = new AcademicTimetableDbContext())
{
query = (from b in dbb.Lecturers select b).ToList();
Debug.WriteLine(query[0].AcademicDegree);
}
And the solution is to tell EF to eagerly load the navigation properties with the using Include method:
using (var dbb = new AcademicTimetableDbContext())
{
query = (from b in dbb.Lecturers.Include(l => l.AcademicDegree) select b)
.ToList();
}
Debug.WriteLine(query[0].AcademicDegree);
Related
I have been working on a shop site project, using asp.net core spa templates provided with the latest VS2017, and have come across an issue that I haven't had before, possibly because until now my apps were quite simple!
I know what the problem is and where, I just can't fix it. I have a product model which has a collection of "Attributes" and a collection of "Variations" (different colour size, etc) and those variations also have attributes, so if the same Attribute shows up in the Variation (VAttributes), as is already in the main "Attributes" I get the error
InvalidOperationException: The instance of entity type
'ProductAttribute' cannot be tracked because another instance with the
key value 'Id:2' is already being tracked. When attaching existing
entities, ensure that only one entity instance with a given key value
is attached.
The best answer I found was here : https://stackoverflow.com/a/19695833/6749293
Unfortunately, even with the above check I got the error, I even tried making a list of attached attributes, and if the vattribute matched one of the items in the list, I didn't attach it. In fact I found that even if I don't attach (_context.attach()) any of the vAttributes, it still throws the error!.
Here's the code in question:
public async Task<Product> Create(Product product)
{
try
{
foreach (var variation in product.Variations)
{
foreach (var vAttr in variation.VAttributes)
{
bool isDetached = _context.Entry(vAttr).State == EntityState.Detached;
if (isDetached)
_context.Attach(vAttr);
}
}
foreach (var attribute in product.Attributes)
{
bool isDetached = _context.Entry(attribute).State == EntityState.Detached;
if (isDetached)
_context.Attach(attribute);
}
foreach (var category in product.Categories)
{
_context.Attach(category);
_context.Attach(category).Collection(x => x.Children);
}
_context.Products.Add(product);
await Save();
return product;
}
catch (Exception)
{
throw;
}
}
The models for the 3 objects are as follows:
public class Product
{
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public string Description { get; set; }
public string StockRef { get; set; }
public DateTime? LastModified { get; set; }
//image needed
public ICollection<ProductCategory> Categories { get; set; }
public ICollection<ProductAttribute> Attributes { get; set; }
public ICollection<ProductVariation> Variations { get; set; }
public Product()
{
Attributes = new List<ProductAttribute>();
Variations = new List<ProductVariation>();
Categories = new List<ProductCategory>();
}
}
Variation:
public class ProductVariation
{
[Key, DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public DateTime? LastModified { get; set; }
public virtual ICollection<ProductAttribute> VAttributes { get; set; }
//needs images
public decimal VPrice { get; set; }
public string VStockRef { get; set; }
}
Finally the Attribute:
public class ProductAttribute
{
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string Name { get; set; }
[ForeignKey("AttributeCategory")]
public int AttributeCategoryId { get; set; }
public virtual AttributeCategory AttributeCategory { get; set; }
}
Most help I found when searching was more related to having repo's injected as singletons, or HttpPut methods where the code had check for existence omitting the .AsNoTracking() or it was a mistake that they had the second instance in some way, where I am aware of the second instance, I just don't know how to prevent it from being tracked!
EDIT: I found that adding a foreign key on the ProductVariation model to the Product that was being created failed as it was only a temp key!? anyway removed it from the variation model, so have updated my code. Also thought I'd add one of my earler failed attempts, that led to all of the foreach loops.
_context.AttachRange(product.Attributes);
_context.AttachRange(product.Categories);
_context.AttachRange(product.Variations);
_context.Add(product);
I believe you can allow EF to handle the tracking.
public virtual bool Create(T item)
{
try
{
_context.Add(item);
_context.SaveChanges();
return true;
}
catch (Exception e)
{
return false;
}
}
This allows for you to save the entire object structure without worring about attaching items.
var newProduct = new Product();
newProduct.Categories.Add(cat);
newProduct.Attributes.Add(att);
newProduct.Variations.Add(vari);
Create(newProduct);
I have two related Entity Framework 6 classes in my data layer.
public class Order
{
public int Id { get; set; }
public virtual SalesStatus SalesStatus { get; set; }
}
public class SalesStatus
{
public SalesStatus()
{
Orders = new List<Order>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual List<Order> Orders { get; set; }
}
public class OrderVM
{
public int Id { get; set; }
public SalesStatus SalesStatus { get; set; }
}
I am using Automapper to map these to my view models and back again.
cfg.CreateMap<Order, OrderVM>()
.MaxDepth(4)
.ReverseMap();
The status entity is used to populate a drop down list.
In my method I am taking the selected value and trying to update the order record to the new selected status.
private bool SaveOrderToDb(OrderVM orderVM)
{
using (var db = new MyContext())
{
var order = AutomapperConfig.MapperConfiguration.CreateMapper().Map<OrderVM, Order>(orderVM);
order.SalesStatus = db.SalesStatuses.Find(Convert.ToInt16(orderVM.SalesStatusSelectedValue));
db.Set<Order>().AddOrUpdate(order);
db.SaveChanges();
}
return true;
}
This does not update the relationship in the database. Why? What am I missing?
I have a simple BreezeController that returns a unit of work repository object. The object is a DbSet entity object of the class below:
public int OrderId { get; set; }
public string Customer { get; set; }
public virtual ICollection<OrderLine> OrderLines { get; set; }
The Unit of Work class is as follows:
private readonly EFContextProvider<ESpaDBEntities> _contextProvider;
public UoW()
{
_contextProvider = new EFContextProvider<ESpaDBEntities>();
Orders = new Repository<Order>(_contextProvider.Context);
OrderLine = new Repository<OrderLine>(_contextProvider.Context);
Products = new Repository<Product>(_contextProvider.Context);
}
public IRepository<Order> Orders { get; set; }
public IRepository<OrderLine> OrderLine { get; set; }
public IRepository<Product> Products { get; set; }
public SaveResult Commit(JObject changeSet)
{
return _contextProvider.SaveChanges(changeSet);
}
The BreezeController action is as follows:
[HttpGet]
public IQueryable<Order> Orders()
{
return uow.Orders.All();
}
When I access this method from my browser the following Json object is returned:
$id: "1",$type: "KoDurandalBreeze.DomainModel.Order, KoDurandalBreeze",OrderId: 1,Customer: "Bob",OrderLines: [ ]
For whatever reason, orderlines are not populated even though virtual is specified. Does anyone have any ideas of why the JSON object would not contain any OrderLine objects?
You will need to either perform the equivalent of an EF 'Include' on the server or if this is an EF Queryable you can call 'extend' on your client side EntityQuery, i.e.
var query = EntityQuery.from("Orders").expand("OrderDetails");
var myEntityManager.executeQuery(query).then(...)
Assume that I have the following little console application which uses Entity Framework 5:
class Program {
static void Main(string[] args) {
using (var ctx = new ConfContext()) {
var personBefore = ctx.People.First();
Console.WriteLine(personBefore.Name);
personBefore.Name = "Foo2";
ctx.SaveChanges();
var personAfter = ctx.People.First();
Console.WriteLine(personAfter.Name);
}
Console.ReadLine();
}
}
public class ConfContext : DbContext {
public IDbSet<Person> People { get; set; }
public IDbSet<Session> Sessions { get; set; }
}
public class Person {
[Key]
public int Key { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public DateTime? BirthDate { get; set; }
public ICollection<Session> Sessions { get; set; }
}
public class Session {
[Key]
public int Key { get; set; }
public int PersonKey { get; set; }
public string RoomName { get; set; }
public string SessionName { get; set; }
public Person Person { get; set; }
}
As you can see, I am changing the name of the record and saving it. It works but it feels like magic to me. What I am doing in all of my applications is the following one (to be more accurate, inside the Edit method of my generic repository):
static void Main(string[] args) {
using (var ctx = new ConfContext()) {
var personBefore = ctx.People.First();
Console.WriteLine(personBefore.Name);
personBefore.Name = "Foo2";
var entity = ctx.Entry<Person>(personBefore);
entity.State = EntityState.Modified;
ctx.SaveChanges();
var personAfter = ctx.People.First();
Console.WriteLine(personAfter.Name);
}
Console.ReadLine();
}
There is no doubt that the second one is more semantic but is there any other obvious differences?
Well the second code block where you explicitly set the entity state is redundant, as the change tracker already knows that the entity is modified because the context knows about the entity (as you query the context to retrieve the entity).
Setting (or painting) the state of the entity would be more useful when working with disconnected entities, for example in an n-tier environment where the entity was retrieved in a different context and sent to a client for modification, and you wish to mark those changes back on the server using a different context.
Otherwise, the first code block is cleaner in my opinion.
I have an entity (I am using code first) that looks like that:
public class Node
{
public int ID { get; set; }
public string SomeInfo { get; set; }
public virtual Node Previous { get; set; }
public virtual Node Next { get; set; }
}
There is no problem to save the Next Node for example. However if the ID of Previous is 1 and I try to set the Next Node (wich is the one with ID=1) to 2 this exception is thrown.
The object cannot be added to the object context. The object�s
EntityKey has an ObjectStateEntry that indicates that the object is
already participating in a different relationship.
I am saving the node like this:
int nextId;
int previousId;
if (int.TryParse(Request["previous"], out previousId))
node.Previous = this.nodeRepository.GetSingle(previousId);
if (int.TryParse(Request["next"], out nextId))
node.Next = this.nodeRepository.GetSingle(nextId);
this.nodeRepository.Update(node);
Update looks like this:
public virtual void Update(T entity)
{
this.context.Entry(GetSingle(entity.ID)).State = EntityState.Detached;
this.context.Entry(entity).State = EntityState.Added;
this.context.Entry(entity).State = EntityState.Modified;
this.Save();
}
And GetSingle like this:
public virtual T GetSingle(object id)
{
var query = this.entities.Find(id);
return query;
}
UPDATE 1
The line with the exception is in the Update method:
this.context.Entry(entity).State = EntityState.Modified;
Is your context in a Using block and being disposed of at some point? I've had similar 'The object cannot be added to the object context." errors. I would add Id's for Previous and Next to your model and use those to update the foreign keys.
public class Node
{
public int ID { get; set; }
public string SomeInfo { get; set; }
public virtual Node Previous { get; set; }
public int PreviousId { get; set; }
public virtual Node Next { get; set; }
public int NextId { get; set; }
}
To update the foreign keys...
int nodeId; // I'm assuming you know the id of node you want updated.
int nextId;
int previousId;
using (var context = new Context())
{
// Perform data access using the context
var node = context.Nodes.find(nodeId);
node.NextId = nextId;
node.PreviousId = previousId;
context.SaveChanges();
}