Как я могу получить Foo с одним столбцом в EF 5 Code First?

Я разболтался, пытаясь сначала получить код EF, чтобы позволить мне сделать то, что я мог бы сделать за 2 минуты в SQL. Если бы я не потратил 5 дней, пытаясь заставить его работать, я бы просто кодировал свою базу данных в DDL и использовал ADO.NET. Но я отвлекся...

Я хочу иметь 2 таблицы, где каждая запись в A имеет соответствующую запись в B. Они оба являются частью одного и того же объекта; они должны быть в отдельных таблицах по причинам, в которые я не буду вдаваться (но они действительно есть, так что не ходите туда). Если бы я проектировал его с конца базы данных, у меня просто были бы отношения FK от B до A. Работа сделана.

В EF Code First я попытался использовать как метод общего первичного ключа, так и метод сопоставления внешнего ключа " один к одному", но ни один из них не работает для меня. Я также попробовал 100 или около того комбинаций всех вариантов, которые я могу придумать, и я не буду дальше двигаться вперед.

Как я уже сказал, все, что я хочу, - это иметь навигационные отношения от A до B (и обратно было бы неплохо, но я читал, что это невозможно), и чтобы эти отношения были загружены лениво, чтобы я мог сказать, a.b и иметь доступ к полям б.

Я не могу перечислить все, что я пробовал, поэтому позвольте мне привести пример того, что почти работает:

class Foo
{
    public int Id { get; set; }
    public string FooProperty { get; set; }

    public virtual Bar Bar { get; set; }
}

class Bar
{
    public int Id { get; set; }
    public string BarProperty { get; set; }
}

Обратите внимание, что обратной ссылки от Bar к Foo нет, поскольку (a) SQL Server будет жаловаться на несколько путей каскадного удаления, и (b) EF будет жаловаться на то, что не знает, какая сторона является основным концом ассоциации. Так что... хорошо - я могу жить без этого.

Что это дает мне в базе данных является Foos стол с Id, FooProperty а также Bar_Id поля и Bars стол с Id а также BarProperty поля. Это довольно близко к тому, как я смоделировал бы его в SQL, хотя я бы, вероятно, поместил поле FK в Bar скорее, чем Foo, Но так как это 1:1, это не имеет большого значения, я думаю.

Причина, по которой я говорю, что это почти работает, заключается в том, что если я добавлю Bar и связанные Foo а затем загрузить их обратно, Bar собственность Foo объект является нулевым.

using (var dbContext = new MyDbContext())
{
    var foo = dbContext.Foos.Create();
    foo.FooProperty = "Hello";
    dbContext.Foos.Add(foo);

    var bar = dbContext.Bars.Create();
    bar.BarProperty = "world";

    foo.Bar = bar;

    dbContext.SaveChanges();
}

using (var dbContext = new MyDbContext())
{
    foreach (var foo in dbContext.Foos)
        Console.WriteLine(foo.Bar.Id); // BOOM! foo.Bar is null
}

Я обычно ожидаю оценку foo.Bar вызвать ленивую загрузку Bar объект, но это не так - это свойство остается null,

Как я могу это исправить?

4 ответа

Решение

Это должно работать:

контекст

public class FoobarCtx : DbContext
{
    public DbSet<Bar> Bars { get; set; }
    public DbSet<Foo> Foos { get; set; }

    public FoobarCtx()
    {

    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Bar>()
            .HasRequired(f => f.Foo)
            .WithRequiredDependent(b => b.Bar)
            .Map(x => x.MapKey("FooId"))
            .WillCascadeOnDelete(true);
    }
}

юридические лица

public class Foo
{
    public int Id { get; set; }

    public string Foo1 { get; set; }
    public string Foo2 { get; set; }

    public virtual Bar Bar { get; set; }
}

public class Bar
{
    public int Id { get; set; }
    public string Bar1 { get; set; }
    public string Bar2 { get; set; }

    public virtual Foo Foo { get; set; }
}

Я проверял это в EF 4.3, но я думаю, что это должно работать и в EF5. Ключ - это метод OnModelCreating. Там вы можете определить одного или другого в качестве принципала / потомка и избавиться от ограничения Microsoft SQL.

Для получения дополнительной информации см. Этот пост в блоге. Для получения дополнительной информации о построителе моделей (свободно API), перейдите по этой ссылке.

Чтобы включить отложенную загрузку, используйте метод DbContext.FooSet.Create(). Пример тут.

Для отношения 1:1 в дополнение к отношению 1: многие (Foo имеет несколько столбцов, а также текущий столбец):

        modelBuilder.Entity<Foo>()
            .HasOptional(f => f.CurrentBar)
            .WithMany()
            .HasForeignKey(f => f.CurrentBarId);

        modelBuilder.Entity<Bar>()
            .HasRequired(b => b.Foo)
            .WithMany(f => f.Bars)
            .HasForeignKey(b => b.FooId);

Как напоминание самому себе, если ничего больше...

LueTm пришел к решению, которое создало структуру таблицы, которую я изначально имел в виду (со столбцом FooId в таблице Bar), и независимые PK для двух таблиц. Было все еще невозможно получить доступ к свойству Foo.Bar без предварительной загрузки его с помощью dbContext.Foos.Include(f => f.Bar),

Я также смог заставить все работать довольно хорошо с общим первичным ключом (обе таблицы имеют PK, но только в Foos есть столбец идентификаторов (с автоинкрементом), и есть отношение FK от Id в Bars до Идентификатор в Фоос.

Для этого у меня было свойство Bar в классе Foo и свойство Foo в классе Bar (так что работает двухсторонняя навигация), и я добавил следующее в мой OnModelCreating.

modelBuilder.Entity<Bar>()
                .HasRequired(x => x.Foo)
                .WithRequiredDependent(x => x.Bar)
                .WillCascadeOnDelete(true);

modelBuilder.Entity<Foo>()
                .HasRequired(x => x.Bar)
                .WithRequiredPrincipal(x => x.Foo)
                .WillCascadeOnDelete(true);

(Я не уверен, что второй звонок здесь действительно что-то делает).

Но опять же, вам все еще нужно Include() позвоните, чтобы получить доступ foo.Bar,

Я только что прошел через это сам. У меня есть FoodEntry, с Food, а у Food есть FoodGroup.

public class FoodEntry
{
    public int      Id { get; set; }
    public string   User { get; set; }
    public Food     Food { get; set; }
    public decimal  PortionSize { get; set; }
    public Portion  Portion { get; set; }
    public int      Calories { get; set; }
    public Meal  Meal { get; set; }
    public DateTime? TimeAte { get; set; }
    public DateTime EntryDate { get; set; }
}

public class Food
{
    public int       Id { get; set; }
    public string    Name { get; set; }
    public FoodGroup FoodGroup { get; set; }
}

public class FoodGroup
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Сначала я использую код, и мои классы POCO определены так же, как ваши. У меня нет свойств, помеченных как виртуальные.

В любом случае база данных генерируется, как я и ожидал, с внешними ключами, как вы описали.

Но этот запрос привел просто к получению коллекции FoodEntry с нулевыми значениями в свойствах Food, Portion и Meal:

var foodEntries = db.FoodEntries
            .Where(e => e.User == User.Identity.Name).ToList();

Я изменил запрос на этот, и весь график загружен для моей коллекции:

var foodEntries = db.FoodEntries
            .Include( p => p.Food)
            .Include(p => p.Food.FoodGroup)
            .Include(p => p.Portion)
            .Include( p => p.Meal)
            .Where(e => e.User == User.Identity.Name).ToList()
            ;
Другие вопросы по тегам