Как я могу получить 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()
;