Как делиться объектами EF6 в code-first

Я только начинаю изучать Entity Framework (6) из сценария «сначала код». Моя проблема в том, что я не понимаю, как правильно указать EF, что некоторые объекты могут быть разделены между другими объектами.

Вот очень простой сценарий с трехуровневой древовидной структурой. Объекты класса A владеют объектами класса B, которые владеют или ссылаются на объекты класса C:

public class A
{
    [Key]
    public string Id { get; set; }

    public ICollection<B> Bees { get; set; }

    public override string ToString() { return Id; }
}

public class B
{
    [Key]
    public string Id { get; set; }

    public ICollection<C> Cees { get; set; }

    public override string ToString() { return Id; }
}

public class C
{
    [Key]
    public string Id { get; set; }

    public override string ToString() { return Id; }
}

Теперь я создаю сценарий, в котором один объект A владеет пятью объектами B, которые, в свою очередь, разделяют набор из пяти объектов C. Вот DbContext, который я использовал:

public class Context : DbContext
{
    public DbSet<A> As { get; set; }

    public DbSet<C> Cs { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // SHOULD I INSTRUCT EF HOW TO HANDLE THIS HERE?
        base.OnModelCreating(modelBuilder);
    }

    public Context(string nameOrConnectionString)
        : base(nameOrConnectionString)
    {
    }
}

А вот простой код для настройки этого сценария:

internal class Program
{
    private static void Main(string[] args)
    {
        var context = new Context(@"Data Source=(localdb)\v11.0; Initial Catalog=TestEF6.Collections; Integrated Security=true");
        var sharedCees = createCees("A1.B1", 5, context);
        context.As.Add(new A { Id = "A1", Bees = createBees("A1", 5, sharedCees) });
        context.SaveChanges();
    }

    private static ICollection<B> createBees(string aId, int count, ICollection<C> cees = null)
    {
        var list = new List<B>();
        for (var i = 0; i < count; i++)
        {
            var id = aId + ".B" + (i + 1);
            list.Add(new B { Id = id, Cees = cees ?? createCees(id, count) });
        }
        return list;
    }

    private static ICollection<C> createCees(string bId, int count, Context saveInContext = null)
    {
        var list = new List<C>();
        for (var i = 0; i < count; i++)
        {
            var id = bId + ".C" + (i+1);
            var c = new C {Id = id};
            list.Add(c);
            if (saveInContext != null)
                saveInContext.Cs.Add(c);
        }
        if (saveInContext != null)
            saveInContext.SaveChanges();
        return list;
    }
}

Поскольку EF создает базу данных ("TestEF6.SharedEntities"), он предполагает, что A -> B, а также B -> C являются отношениями "один ко многим" и что дочерние сущности являются компонентами, полностью принадлежащими и контролируемыми его главной сущностью.

Итак, как заставить EF «видеть», что экземпляры C могут совместно использоваться экземплярами B?

Честно говоря, я прошу быстро начать работу с EF. Я понимаю, что могу порыться в Интернете и понять это, но если кто-нибудь может указать мне правильное направление, я был бы очень признателен.

Ваше здоровье


person Jonas Rembratt    schedule 05.02.2014    source источник
comment
Вы описываете связь «многие ко многим» между B и C. Когда вы говорите, что несколько B могут иметь одно и то же C, вы также говорите, что C может иметь несколько B. Таким образом, C должен иметь набор B, а EF будет моделировать его с помощью соединительной таблицы BC в базе данных.   -  person Gert Arnold    schedule 06.02.2014
comment
Спасибо, Герт. Это действительно поставило меня на правильный путь. Смотрите мой следующий ответ, пожалуйста (мне нужно добавить код)...   -  person Jonas Rembratt    schedule 06.02.2014


Ответы (1)


Герт направил меня в нужное русло, предложив добавить свойство коллекции в класс C. Действительно, этого достаточно, чтобы EF/CF автоматически распознал отношение «многие ко многим» между B и C. Но весь смысл кода в первую очередь заключается в том, чтобы спроектировать предметную модель классов, а затем сохранить их в EF, не увязая в информации о сохраняемости.

У меня нет проблем с аннотированием Key свойств или даже Required. Этот вид информации действительно имеет смысл в модели предметной области. Их можно использовать в коде домена, но даже если это не так, код становится более понятным. Но добавлять свойства, которые не выполняют никакой другой функции, кроме как действовать как подсказки к автоматической магии EF, не казалось очень элегантным решением.

Поэтому я еще немного поэкспериментировал с fluent API и понял, что пропустил безпараметрическую перегрузку .WithMany(). Я искал общую форму, которая позволила бы указать другой тип отношения «многие ко многим». В уме я бы тогда написал: modelBuilder.Entity<B>.HasMany(b => b.Cees).WithMany<B>(). Как оказалось, перегрузка WithMany() без параметров служит именно этой цели, так как тип C в этом сценарии уже указан первой частью HasMany().

Таким образом, EF распознает отношения «многие ко многим» между классами B и C без необходимости «засорять» класс C дополнительной общей коллекцией элементов типа B. Итак, вот как мы можем описать отношения «многие ко многим» между двумя классами, вообще не касаясь классов предметной области:

public class Context : DbContext
{
    public DbSet<A> As { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<B>().HasMany(b => b.Cees).WithMany(); // <-- THE SOLUTION
        base.OnModelCreating(modelBuilder);
    }

    public Context(string nameOrConnectionString)
        : base(nameOrConnectionString)
    {
    }
}

Я понимаю, что это очень простой материал для любого, кто имеет опыт работы с EF, но я продолжу и отмечу это как ответ, чтобы другие могли сэкономить время на поиске этой информации в будущем.

Спасибо

person Jonas Rembratt    schedule 06.02.2014