Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Referencing DbContext from POCO object

I'm using Code First for my next project. I really like the idea, and so far it has worked great. My only beef with it, is that I can't find any documentation on how to wield this beast, and googling often refers to now outdated CTPs.

For this question, i'll model a directed graph. The algorithm for graph traversal is not intended to be optimal.

I have a simple poco structure, something like this

class Graph : DbContext
{
    public DbSet<Node> Nodes { get; set; }
    public DbSet<Edge> Edges { get; set; }

    public Graph(string connectionString) : base(connectionString) { }
}

class Edge
{
    public int Id { get; set;}
    public double Weight { get; set; }

    public Node StartNode { get; set; }
    public Node EndNode { get; set; }
}

class Node
{
    public int Id { get; set; }
    public string Label { get; set; }
}

Simple and neat.

But now suppose I want to add to each node some kind of reference to the Graph object, so that the node can figure things out about itself in the context of a graph, for example how many edges it has.

I want my Node, when created, to have

class Node
{
    public int Id { get; set; }
    public string Label { get; set; }

    //I want this property populated by magic. 
    //Just leaving it here crashes the program
    public Graph Graph { get; set; }

    //So that this property would do meaningful things.
    public int EdgesFromThisNode
    {
        get { return Graph.Edges.Count(e => e.StartNode.Id == Id); }
    }
}

I think that I'm stepping out of convention here to solve a particular problem that I'm having. For example this property could be moved as a method to the Graph-class. The reason that I don't want to do that, is because I want to bind to that property, and binding is evil.

Can one of you wizards guide me to the right combination of Annotation/EntityTypeConfiguration magic necessary to pull this fast one?

Is there a different convention that I should be aware of? For example, If I somehow could bind all the Edges or better yet, some of the Edges (The ones eminating from the node) to the Node, then that's even more elegant.

Thanks in advance, and if you have an advice on what any Code First enthusiast should read ... first, please share your links!

like image 475
Gleno Avatar asked May 22 '26 08:05

Gleno


1 Answers

You could introduce the other side of the association, that means the collections of Edges which start at the node and which end at the node. You wouldn't need then the database context in the Node class:

class Edge
{
    public int Id { get; set;}
    public double Weight { get; set; }

    [InverseProperty("OutgoingEdges")]
    [Required]
    public Node StartNode { get; set; }
    [InverseProperty("IncomingEdges")]
    [Required]
    public Node EndNode { get; set; }
}

class Node
{
    public int Id { get; set; }
    public string Label { get; set; }

    public ICollection<Edge> OutgoingEdges { get; set; }
    public ICollection<Edge> IncomingEdges { get; set; }

    public int EdgesFromThisNode
    {
        get { return OutgoingEdges != null ? OutgoingEdges.Count() : 0; }
    }
}

Or in Fluent configuration if you don't want the attributes in the Edge class:

modelBuilder.Entity<Edge>()
            .HasRequired(e => e.StartNode)
            .WithMany(n => n.OutgoingEdges);

modelBuilder.Entity<Edge>()
            .HasRequired(e => e.EndNode)
            .WithMany(n => n.IncomingEdges);

When you load a Node you have to make sure that also the collection of edges you want is loaded:

using (var graph = new Graph())
{
    Node node = graph.Nodes.Include(n => n.OutgoingEdges)
                     .FirstOrDefault(n => n.Id == 1);
    // node.EdgesFromThisNode would give now correct result
}

Alternatively you can mark your navigation properties as virtual to benefit from lazy loading.

Note: This solution is only useful if you are really interested in the outgoing and incoming edges of a node. (I was refering mainly to this part of your question: "If I somehow could bind all the Edges or better yet, some of the Edges (The ones eminating from the node) to the Node...") If you only want to have the number of edges you are loading too much from the database (all the Edge objects).

Edit

A few resources about EF 4.1, especially Code-First:

Code-First Walkthrough: http://blogs.msdn.com/b/adonet/archive/2011/03/15/ef-4-1-code-first-walkthrough.aspx

12-Part tutorial about EF 4.1: http://blogs.msdn.com/b/adonet/archive/2011/01/27/using-dbcontext-in-ef-feature-ctp5-part-1-introduction-and-model.aspx

Morteza Manavi's blog about Associations and Inheritance in Code-First: http://weblogs.asp.net/manavi/default.aspx

MSDN pages about EF 4.1: http://msdn.microsoft.com/en-us/library/gg696172%28v=vs.103%29.aspx

A few tutorial videos: http://msdn.microsoft.com/en-us/data/cc300162

Edit 2

If you want to only bind the number of edges to a view without loading all edge objects I would consider to work with a ViewModel which wraps the Node itself and has an additional property:

public class NodeViewModel
{
    public Node Node { get; set; }
    public int NumberOfOutgoingEdges { get; set; }
}

I would leave the two navigation collections in the Node class (and remove the EdgesFromThisNode property) but I wouldn't load the collections for this specific binding scenario and use instead a projection into the new ViewModel type:

using (var graph = new Graph())
{
    NodeViewModel nodeViewModel = graph.Nodes
        .Where(n => n.Id == 1)
        .Select(n => new NodeViewModel()
            {
                Node = n,
                NumberOfOutgoingEdges = n.OutgoingEdges.Count()
            })
        .FirstOrDefault();
    // nodeViewModel.Node doesn't have the OutgoingEdges loaded now
}

Then you bind the NodeViewModel to your View and not directly the Node. This solution avoids to inject somehow the database context into your model class (which is very against the idea of a POCO in my opinion).

like image 174
Slauma Avatar answered May 25 '26 07:05

Slauma



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!