Reputation: 6694
Entity Framework is loading everything in the Polygon
but my list of LineSegments
. What am I missing?
Polygon:
public class Polygon
{
List<LineSegment> _lineSegments = new List<LineSegment>();
public List<LineSegment> LineSegments
{
get { return _lineSegments; }
protected set { _lineSegments = value; }
}
public Point BasePoint { get; protected set; }
public Vector NormalVector { get; protected set; }
public int? DatabaseId { get; set; }
// ...
}
LineSegment class (Polygon
has a list of these)
public class LineSegment
{
public virtual Distance Magnitude
{
get { return _magnitude; }
set { _magnitude = value; }
}
private Distance _magnitude;
public virtual Point BasePoint
{
get { return _basePoint; }
set { this._basePoint = value; }
}
private Point _basePoint;
public virtual Direction Direction
{
get { return _direction; }
set { _direction = value; }
}
private Direction _direction;
public int? DatabaseId { get; set; }
// ...
}
And here is the relationship setup in the model:
modelBuilder.Entity<Polygon>()
.HasMany(polygon => polygon.LineSegments);
So there is a table for both Polygons and LineSegments, and they insert properly where the LineSegment has a reference to a Polygon. But when I try to retrieve them using eager loading, it doesn't load the list. I have the properties of LineSegment listed in the includes, but it's not working. I think I need to amend the relationship setup in the model, but I'm not sure how. How can I correct this so that I load the list of LineSegments eagerly when I load a Polygon? Here's the querying:
private static List<Expression<Func<Polygon, object>>> polygonRegionNaviationProperties = new List<Expression<Func<Polygon, object>>>()
{
(polygon => polygon.BasePoint),
(polygon => polygon.BasePoint.X),
(polygon => polygon.BasePoint.Y),
(polygon => polygon.BasePoint.Z),
(polygon => polygon.NormalVector),
(polygon => polygon.NormalVector.Direction),
(polygon => polygon.NormalVector.Direction.Phi),
(polygon => polygon.NormalVector.Direction.Theta),
(polygon => polygon.NormalVector.Magnitude),
(polygon => polygon.NormalVector.BasePoint.X),
(polygon => polygon.NormalVector.BasePoint.Y),
(polygon => polygon.NormalVector.BasePoint.Z),
(polygon => polygon.LineSegments),
(polygon => polygon.LineSegments.Select(lineSegment => lineSegment.Direction)),
(polygon => polygon.LineSegments.Select(lineSegment => lineSegment.Direction.Phi)),
(polygon => polygon.LineSegments.Select(lineSegment => lineSegment.Direction.Theta)),
(polygon => polygon.LineSegments.Select(lineSegment => lineSegment.Magnitude)),
(polygon => polygon.LineSegments.Select(lineSegment => lineSegment.BasePoint.X)),
(polygon => polygon.LineSegments.Select(lineSegment => lineSegment.BasePoint.Y)),
(polygon => polygon.LineSegments.Select(lineSegment => lineSegment.BasePoint.Z))
};
public Polygon GetPolygon(int? databaseId)
{
if(databaseId != null)
{
Polygon retrievedPolygon = Query((polygon => polygon.DatabaseId == databaseId), polygonRegionNaviationProperties);
return retrievedPolygon;
}
else
{
return null;
}
}
public override Polygon Query(Expression<Func<Polygon, bool>> match, List<Expression<Func<Polygon, object>>> includes = null)
{
using (var databaseContext = new ClearspanDatabaseContext())
{
databaseContext.Database.Log = Console.Write;
if (includes != null)
{
var dataSet = databaseContext.Set<Polygon>(); // Get the relevant DataSet
Polygon retrievedObject = includes.Aggregate( // Eagerly load the passed navigation properties
dataSet.AsQueryable(),
(current, include) => current.Include(include)
).SingleOrDefault(match);
databaseContext.Entry(retrievedObject).Collection(polygon => polygon.LineSegments).Load();
return retrievedObject;
}
else
{
Polygon retrievedObject = databaseContext.Set<Polygon>().SingleOrDefault(match);
databaseContext.Entry(retrievedObject).Collection(polygon => polygon.LineSegments).Load();
return retrievedObject;
}
}
}
UPDATE
Here is a link to a thinned out project illustrating my problem.
git submodule init
and git submodule update
in the root of the project) (this may happen automatically if you use SourceTree)Hopefully this is helpful to understanding, diagnosing, and solving this problem. You'll note that in my question I simplified the hierarchy that actually exists in the GeometryClassLibrary. Thanks, Scott H
Upvotes: 4
Views: 217
Reputation: 109099
This is the code of Polygon.LineSegments
:
public virtual List<LineSegment> LineSegments
{
get { return this._Edges.Select(e => (LineSegment)e).ToList(); }
set { _Edges = value.ToList<IEdge>(); }
}
When EF populates a child collection it won't assign a complete collection to it, but initialize it if necessary and then add items to it. Now here's the pitfall: to which collection does EF add items? Not _Edges
, but a transient collection that is build by ToList()
each time LineSegments
is addressed. _Edges
itself remains empty.
Unfortunately, you can't solve this by Include()
-ing Edges
in stead of LineSegments
, because this is the code of Edges
:
protected List<IEdge> _Edges = new List<IEdge>();
public virtual List<IEdge> Edges
{
get { return _Edges; }
set { _Edges = value; }
}
And EF doesn't support interfaces. Frankly, I don't know if there is a decent work-around. This indirection in LineSegments
is likely to cause more issues with EF. For instance, I don't think relationship fixup (auto-population of navigation properties from entities loaded into the context) will run either.
I understand these classes are from an external library. I see all these XyzEntityFramework
partial classes there (e.g. LineSegmentEntityFramework
) that seem to add properties to facilitate working with EF. Maybe they are willing to look at your issue and find a solution for it.
Upvotes: 3
Reputation: 21
I'd take a look at the first example about one-to-many relationships in Code First here. http://www.entityframeworktutorial.net/code-first/configure-one-to-many-relationship-in-code-first.aspx
I think Fabio Luz was on the right track. The LineSegments collection should be virtual. You can also try overriding the default constructor and instantiate the property. In addition, there should probably be a virtual property for the Polygon in the LineSegments class to complete the referential relation.
public class Polygon
{
public Polygon()
{
LineSegments = new List<LineSegment>();
}
public virtual List<LineSegment> LineSegments
{
get;
set;
}
// ...
}
public class LineSegment
{
public virtual Polygon Polygon { get; set; }
}
Upvotes: 0
Reputation: 11990
Try to change your class as follow:
public class Polygon
{
public virtual List<LineSegment> LineSegments
{
get;
set;
}
// ...
}
In your query, include only navigation properties. Strings, primitive types and enumeration types shouldn't be included. Like this:
var dataSet = databaseContext.Set<Polygon>();
Polygon retrievedObject = dataSet.Include(i => i.LineSegments).SingleOrDefault(match);
return retrievedObject;
Does that work for you?
Upvotes: 1