I have two classes that I have simplified for this SO question. Whenever I add a new 'parent' and attach existing 'child' entities, I end up with duplicated children. What am I doing wrong?
public class Group
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int GroupId { get; set; }
public virtual Child ChildOne { get; set; }
public virtual Child ChildTwo { get; set; }
}
public class Child
{
[Key]
public int ChildId { get; set; }
public bool Available { get; set; }
}
using (var context = new RPSContext())
{
Child childOne = context.Child.Where(p => p.Available == true).OrderBy(p => p.ChildId).FirstOrDefault();
Child childTwo = context.Child.Where(p => p.Available == true).OrderBy(p => p.ChildId).Skip(1).Take(1).FirstOrDefault();
}
Parent parent = new Models.Parent;
parent.ChildOne = childOne;
parent.ChildTwo = childTwo;
using (var context = new RPSContext())
{
context.Parent.Add(parent);
parent.ChildOne.Available = false;
parent.ChildTwo.Available = false;
context.SaveChanges();
}
I hope I didn't make any errors when I simplified this code. Can anyone assist?
Try performing in single context. You are fetching childOne and childTwo in one context which is disposed immediately after fetching it.
So you might need to attach these two child entities in the new context
context.Child.Attach(childOne);context.Child.Attach(childTwo);
OR perform both operations in the same context so that child entities gets related instead of getting added
Related
We have something like this
var categories = _context.Categories.Include("Categories1.Categories1.Categories1");
That works and handles sub-categories up to 4-level deep (which is enough for now but who knows the future)
Is there a better way to do it?
More info
We use database-first. Category table has these columns:
Id
ParentCategoryId <-- this has foreign key to Category.Id
In this particular case I think a recursive property might be a good bet. Trying to do this from memory (been years), performance won't be great. NO lazy loading and NO explicit loading.
public class Category {
public int Id {get; set;}
// other stuff
public List<Category> MyChildren {
get { return _context.Categories.Where(x => x.ParentCategoryId == Id).ToList<Category>(); }
}
}
This should give you a hierarchical graph starting with a given node.
var justOne = _context.Categories.FirstOrDefault(x => x.Id = <myval>);
Downside is you will have to parse/use the result recursively and potentially it grows exponentially.
Clarification: The use of _context in the recursion is not allowed by EF and was used for illustration purposes. In the repository/UoW or business layer you could use the same technique to "assemble" the finished entity by having a property of a method that calls the method recursively.
Just for fun, here's the same recursion technique (but not as a property, don't have time right now).
public class Category // EF entity
{
public int Id { get; set; }
public int ParentId { get; set; }
public virtual List<Category> MyChildren { get; set; }
}
public static class MVVMCategory
{
public static Category GetCategory(int id)
{
Category result = _context.Categories.FirstOrDefault(x => x.Id == id);
result.MyChildren = GetChildren(id);
return result;
}
public static List<Category> GetChildren(int id)
{
List<Category> result = _context.Categories.Where(x => x.ParentId == id).ToList<Category>();
foreach (var item in result)
{
item.MyChildren = GetChildren(item.Id);
}
return result;
}
}
One thing to notice. I added virtual to the list for lazy loading because I am loading children "by hand" so to speak.
Firstly, add data annotations and make properties readable
public partial class Category
{
public Category()
{
this.Children = new HashSet<Category>();
}
[Key]
public int Id { get; set; }
public string WhatEverProperties { get; set; }
public int ParentCategoryId { get; set; }
[ForeignKey("ParentCategoryId")]
[InverseProperty("Category")]
public Category Parent { get; set; } // name "Category1" as "Parent"
[InverseProperty("Category")]
public ICollection<Category> Children { get; set; } // Name it as Children
}
then, let's say we have got a category,
var category = context.Categories
.Include(x => x.Parent)
.Include(x => x.Children)
.FirstOrDefault(filter);
then we get its parents with:
var rootCategory = category.Parent?.Parent?.Parent; //up-to 4 levels by your request
by following extension, we can easily get its level:
///this extension works only if you used `.Include(x => x.Parent)` from query
public static class CategoryExtensions
{
public static int Level(this Category category)
{
if (category.Parent == null)
{
return 0;
}
return category.Parent.Level() + 1;
}
}
I have two classes with an parent-child (1:n) relationship and want to detect when the parent has been changed by adding a child to it.
I have overriden the SaveChanges method of the context:
public override int SaveChanges()
{
var ctx = ((IObjectContextAdapter)this).ObjectContext;
ctx.DetectChanges();
var entries = ctx.ObjectStateManager.GetObjectStateEntries(EntityState.Added | EntityState.Deleted | EntityState.Modified);
foreach (var entry in entries)
{
if (entry.IsRelationship)
{
Console.WriteLine("Relationship");
}
else
{
Console.WriteLine("Entity");
}
}
return base.SaveChanges();
}
As expected, when adding a child to the parent I get an entity entry for the child and a relationship entry for the parent-child relationship. But as soon as the child class contains a key property for the parent, I no longer get any relationship entries for this relationship.
class Class1
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
public ICollection<Class2> Class2s { get; set; }
}
class Class2
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int Id { get; set; }
public Class1 Parent { get; set; }
public int ParentId { get; set; } // <-- no relationship entries if present
}
Is this expected behavior? How can I detect relationship changes when the id column is present?
Is there any way to save a child object in a parents child object collection and also setting it to a navigation property on the parent without round tripping to the database? example below doesn't work
public class Parent
{
int Id { get; set; }
int? ChildId { get; set; }
Child Child { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
public class Child
{
int Id { get; set; }
public Parent Parent { get; set; }
}
....
var p = new Parent();
var c = new Child();
p.Child = c;
p.Children.Add(c);
Context.Set<Parent>().Add(p);
Context.SaveChanges();
EDIT
The example above throws this error when 'savechanges()' is called.
Unable to determine a valid ordering for dependent operations. Dependencies may exist due to foreign key constraints, model requirements, or store-generated values.
Given the object hierarchy
public class Parent
{
public int Id { get; set; }
public virtual Child Child { get; set; }
}
public class Child
{
public int Id { get; set; }
public virtual GrandChild GrandChild { get; set; }
}
public class GrandChild
{
public int Id { get; set; }
}
and the DB context
public class MyContext : DbContext
{
public DbSet<Parent> Parents { get; set; }
}
One can include children and grandchildren using Lambda syntax (using System.Data.Entity) like this:
using (MyContext ctx = new MyContext())
{
var hierarchy =
from p in ctx.Parents.Include(p => p.Child.GrandChild) select p;
}
The Lambda syntax prevents breaking the query if the class names are subsequently altered. However, if Parent has an ICollection<Child> like this instead:
public class Parent
{
public int Id { get; set; }
public virtual ICollection<Child> Children { get; set; }
}
Lambda syntax no longer works. Instead, one can use the string syntax:
var hierarchy = from p in ctx.Parents.Include("Children.GrandChild") select p;
Is the string syntax the only option, or is there some alternative way to use Lambda syntax in this situation?
Update:
If you are using Entity Framework Core you should use the following syntax
var hierarchy = from p in ctx.Parents
.Include(p => p.Children)
.ThenInclude(c => c.GrandChild)
select p;
Sure, you can do
var hierarchy = from p in ctx.Parents
.Include(p => p.Children.Select(c => c.GrandChild))
select p;
See MSDN, caption Remarks, the fifth bullet.
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);