Ссылка на DbContext из объекта POCO - PullRequest
3 голосов
/ 23 апреля 2011

Я использую Code First для моего следующего проекта. Мне очень нравится эта идея, и до сих пор она отлично работала. Единственное, что я могу сказать по этому поводу, так это то, что я не могу найти никакой документации о том, как использовать этого зверя, и поиск в Google часто ссылается на устаревшие CTP.

Для этого вопроса я буду моделировать ориентированный граф. Алгоритм обхода графа не является оптимальным.

У меня есть простая структура poco, что-то вроде этого

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; }
}

Просто и аккуратно.

Но теперь предположим, что я хочу добавить к каждому узлу некоторую ссылку на объект Graph, чтобы узел мог сам разобраться в контексте графа, например, сколько у него ребер.

Я хочу, чтобы мой узел при создании имел

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); }
    }
}

Я думаю, что я вышел из соглашения здесь, чтобы решить конкретную проблему, которая у меня возникла. Например, это свойство может быть перемещено как метод в класс Graph. Причина, по которой я не хочу этого делать, заключается в том, что я хочу привязать это свойство, а привязка - это зло.

Может ли один из вас, волшебников, привести меня к правильному сочетанию магии Annotation / EntityTypeConfiguration, необходимой для того, чтобы вывести это быстрое?

Есть ли другое соглашение, о котором я должен знать? Например, если бы я каким-то образом мог связать все края или, что еще лучше, некоторые из ребер (выходящих из узла) с узлом, то это еще более элегантно.

Заранее спасибо, и если у вас есть совет о том, что должен прочитать любой энтузиаст Code First ... сначала, пожалуйста, поделитесь своими ссылками!

1 Ответ

4 голосов
/ 23 апреля 2011

Вы могли бы представить другую сторону ассоциации, что означает наборы ребер, которые начинаются в узле и заканчиваются в узле.Тогда вам не понадобится контекст базы данных в классе Node:

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; }
    }
}

или в конфигурации Fluent, если вам не нужны атрибуты в классе Edge:

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

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

Когда вы загружаете Node, вы должны убедиться, что также загружен набор нужных вам ребер:

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
}

В качестве альтернативы вы можете пометить свои свойства навигации как virtual, чтобы извлечь выгоду из ленивостиloading.

Примечание. Это решение полезно только в том случае, если вас действительно интересуют исходящие и входящие ребра узла.(Я ссылался в основном на эту часть вашего вопроса: «Если бы я каким-то образом мог связать все края или, что еще лучше, некоторые из ребер (выходящих из узла) с узлом ...») Если вы хотите толькоиметь число ребер, которые вы слишком много загружаете из базы данных (все объекты Edge).

Edit

Несколько ресурсов об EF4.1, особенно Code-First:

Пошаговое руководство по Code-First: http://blogs.msdn.com/b/adonet/archive/2011/03/15/ef-4-1-code-first-walkthrough.aspx

Учебник из 12 частей об 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

Блог Мортезы Манави об ассоциациях иНаследование в Code-First: http://weblogs.asp.net/manavi/default.aspx

Страницы MSDN о EF 4.1: http://msdn.microsoft.com/en-us/library/gg696172%28v=vs.103%29.aspx

Несколько обучающих видео: http://msdn.microsoft.com/en-us/data/cc300162

Редактировать 2

Если вы хотите привязать только число ребер к виду, не загружая все объекты ребер, я бы рассмотрел работу с ViewModel, которая оборачивает сам узел и имеет дополнительное свойство:

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

Я бы оставил две коллекции навигации в классе Node (и повторнопереместить свойство EdgesFromThisNode), но я бы не стал загружать коллекции для этого конкретного сценария привязки и вместо этого использовал бы проекцию в новый тип ViewModel:

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
}

Затем вы привязываете NodeViewModel к вашему представлениюа не напрямую Node.Это решение позволяет избежать внедрения контекста базы данных в класс вашей модели (что, на мой взгляд, очень противоречит идее POCO).

...