Entity Framework. Часть 5 (продолжение) – Связи в Fluent API

Завершим изучение Entity Framework Fluent API рассмотрением возможностей настройки связей.

Настройка связей с использованием Fluent API

Fluent API позволяет указать связи, которые должны быть установлены между классами Модели и, как следствие, между таблицами, создаваемыми в базе данных. Это может потребоваться в том случае, если соглашения по умолчанию не подходят или могут быть использованы.

Установка связи типа One-to–Zero-or-One (одна-к-нулю-или-одной)

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

Для примера возьмем ситуацию, когда каждый судья (класс Arbitter) может или быть назначен на одну игру (класс Game) или вообще не участвовать в соревнованиях. Но при этом на любой игре должен быть судья. В этом случае связь будет задана следующим образом:

modelBuilder.Entity<Game>()
    .HasRequired(g => g.Arbitter)
    .WithOptional(a => a.Game);

Установка связи типа One-to–One (одна-к-одной)

В прошлом варианте связь была не обязательной. Но что если необходимо чтобы записи существовали с обоих сторон отношения? Такой вариант также легко описать с помощью Fluent API.

Однако здесь есть один интересный момент. В ситуации, когда обе стороны связи обязательны, Entity Framework не может определить, какая из них является главной (principal), а какая зависимой (dependent). Для уточнения существуют методы WithRequiredPrincipal() и WithRequiredDependent(), соответственно. Они вызываются после HasRequired(), который указывает требуемое свойство в выбранном классе:

modelBuilder.Entity<Arbitter>()
    .HasRequired(a => a.Game)
    .WithRequiredPrincipal(g => g.Arbitter);

Кроме того, аналогично можно указать и вариант отношений, когда обе стороны его опциональны. Но в этом случае используются WithOptionalPrincipal() и WithOptionalDependent() после вызова HasOptional():

modelBuilder.Entity<Arbitter>()
    .HasOptional(a => a.Game)
    .WithOptionalPrincipal(g => g.Arbitter);

Установка связи типа Many-to–Many (многие-ко-многим)

В создаваемом проекте любой из книг может быть присвоено любое число ключевых слов. НО и само ключевое слово может быть назначено любому числу книг. Такой тип отношений описывается с помощью методов HasMany() и WithMany():

modelBuilder.Entity<BookDetails>()
    .HasMany(b => b.Tags)
    .WithMany(t => t.Books);

Однонаправленные отношения одна-к-одному

Если с одной из сторон отсутствует навигационное свойство, то такое отношение называют однонаправленным. Соглашения по умолчанию в Code First считают их типом "одна-ко-многим". При необходимости это можно переопределить для конкретной связи:

modelBuilder.Entity<Arbitter>()
    .HasRequired(a => a.Game)
    .WithRequiredPrincipal();

Настройка каскадного удаления

Если внешний ключ не может содержать значение null, то для таких колонок Code First указывает флаг каскадного удаления записей. Это означает, что если в главной таблице будет удалена строка с определенным Id, то из зависимых таблиц будут удалены все записи, у которых есть внешний ключ, указывающий на неё. В некоторых ситуациях это не только не желательно, но и может привести к ошибке или зацикливанию удалений.

Такое поведение можно переопределить двумя способами. Первый п��дразумевает полное отключение данного соглашения по умолчанию с помощью вызовов:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();

Кроме того, можно отменить это поведение для конкретной связи, используя WillCascadeOnDelete():

modelBuilder.Entity<BookDetails>()
    .HasRequired(b => b.Language)
    .WithMany(l => l.Books)
    .WillCascadeOnDelete(false);

В данном случае, при удалении записи из таблицы Language автоматического удаления всех книг, написанных на нем, не произойдёт.

Указание внешнего ключа

Иногда имя свойства, предназначенного для содержания внешнего ключа, не подходит под соглашения по умолчанию. В этом случае его можно отметить самостоятельно с помощью метода HasForeignKey():

modelBuilder.Entity<Game>()
    .HasRequired(g => g.GameTypes)
    .WithMany(g => g.Games)
    .HasForeignKey(g => g.GTId);

Создаем в базе данных записи по умолчанию

Перед тем как перейти к изучению контроля данных в ASP.NET MVC 3 давайте сделаем небольшое изменение в создаваемом веб-приложении, которое упростит работу в дальнейшем. Как было уже отмечено, при изменении классов Модели необходимо пересоздавать базу данных, что приводит к потере уже существующих в ней данных. Это не страшно, так как на этапе разработки важных данных в таблицах нет. Но вот сама необходимость каждый раз создавать записи может сильно изматывать.

Поэтому создадим метод, который будет наполнять базу значениями, но только в случае если она была только что создана. Такую возможность предоставляет Entity Framework.

Для этого в папке DbContext создадим класс CatalogInitializer, который должен быть унаследован от одного из следующих классов:

  • DropCreateDatabaseIfModelChanges<TContext> – обеспечивает автоматическое удаление базы данных в случае изменения любого класса Модели.
  • DropCreateDatabaseAlways<TContext> – всегда перед запуском удаляет базу данных и воссоздается её с определенными пользователем данными.
  • CreateDatabaseIfNotExists<TContext> – создает базу данных и заполняет её значениями только если она отсутствует.

В качестве параметра шаблона TContext необходимо указать тип контекста базы данных.

Выберем DropCreateDatabaseIfModelChanges<T>. Теперь, в созданном CatalogInitializer, необходимо переопределить метод Seed(). Он и будет отвечать за добавление новых записей в пустые таблицы. Давайте посмотрим на исходный код:

namespace BookCatalog.Models.DbContext
{
    using System;
    using System.Data.Entity;

    public class CatalogInitializer : DropCreateDatabaseIfModelChanges<CatalogContext>
    {
        protected override void Seed(CatalogContext context)
        {
            base.Seed(context);

            var englishLang = new Language() { Name = "English" };
            context.Languages.Add(englishLang);

            var russianLang = new Language() { Name = "Russian" };
            context.Languages.Add(russianLang);

            var microsoftPress = new Publisher() {
                Title = "Microsoft Press",
                Homepage = @"http://www.microsoft.com/mspress/"
            };
            context.Publishers.Add(microsoftPress);

            var tagVS2010 = new Tag() { Text = "VS2010" };
            context.Tags.Add(tagVS2010);

            var tagASPNET = new Tag() { Text = "ASP.NET" };
            context.Tags.Add(tagASPNET);

            var tagMVC = new Tag() { Text = "MVC" };
            context.Tags.Add(tagMVC);

            var tagCSharp = new Tag() { Text = "C#" };
            context.Tags.Add(tagCSharp);

            var tagWP7 = new Tag() { Text = "WP7" };
            context.Tags.Add(tagWP7);

            context.Books.Add(new BookDetails() {
                Title = "ASP.NET Web Pages with Razor Syntax",
                Author = "P. Pelland, P. Pare, Ken Haines",
                Language = englishLang,
                Publisher = microsoftPress,
                PublishedAt = new DateTime(2010, 09, 10),
                Url = @"http://blogs.msdn.com/b/microsoft_press/archive/2010/09/13/free-ebook-moving-to-microsoft-visual-studio-2010.aspx",
                Description = "For developers moving from Visual Studio 2003,2005,2008 to Visual Studio 2010.",
                Rating = 4,
                Tags = new Tag[1] { tagVS2010 },
                IsFree = true,
                IsVisible = true
            });

            context.Books.Add(new BookDetails() {
                Title = "Programming Windows Phone 7",
                Author = "Charles Petzold",
                Language = englishLang,
                Publisher = microsoftPress,
                PublishedAt = new DateTime(2010, 10, 28),
                Url = @"http://www.charlespetzold.com/phone/",
                Description = "This book is a gift from the Windows Phone 7 team at Microsoft to the programming community.",
                Rating = 5,
                Tags = new Tag[3] { tagVS2010, tagCSharp, tagWP7 },
                IsFree = true,
                IsVisible = true
            });
        }
    }
}

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

Остается только указать ядру Entity Framework на необходимость использование созданного инициализатора. Для этого в методе Application_Start() вызовем Database.SetInitializer():

protected void Application_Start()
{
    System.Data.Entity.Database.SetInitializer(
        new BookCatalog.Models.DbContext.CatalogInitializer());    

    AreaRegistration.RegisterAllAreas();

    RegisterGlobalFilters(GlobalFilters.Filters);
    RegisterRoutes(RouteTable.Routes);
}

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

Теперь можно запустить веб-приложение. В открывшейся таблице будут видно две записи о книгах.

На этом завершим краткий обзор Entity Framework и вернемся к рассмотрению ASP.NET MVC.


Исходный код проекта (C#, Visual Studio 2010): mvc3-in-depth-ef-05.zip

Комментарии (42) -

Игорь 02.08.2011 19:36:10

Установка связи типа One-to–One (одна-к-одной).

По-моему, не совсем точно. Это пример связи со shared primary key - первичным ключом Game станет первичный ключ Arbitter.

Можете показать примеры для случаев:
1. Когда внешний ключ составной
2. Как сделать так, чтобы one-to-one и one-to-many мепились через третью таблицу

1) Примерно вот так
// Composite primary key
modelBuilder.Entity<Department>()
.HasKey(d => new { d.DepartmentID, d.Name });

// Composite foreign key
modelBuilder.Entity<Course>()
    .HasRequired(c => c.Department)
    .WithMany(d => d.Courses)
    .HasForeignKey(d => new { d.DepartmentID, d.DepartmentName });


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

Первым делом, хотелось бы поблагодарить Вас за прекрасные статьи - спасибо.

У меня есть вопрос, может он и не совсем по теме Code first, но все таки про EF...

У меня есть 2 таблица отображающие одну сущность в коде:

Item
  Id
  Price
  ....
ItemLocalization
  ItemId
  LanguageId
  Name
  Description
  ....

В первой таблице хранятся все не переводимые свойства сущности, а во второй соответственно - переводимые, где первичным ключом являются Id сущности и Id языка.
Таким образом у меня получается отношения "один к многим", хотя для конкретного Id + LanguageId ответ будет ��сегда один экземпляр.

Теперь собственно вопрос, возможно ли в EF определить связи так, чтоб экземпляр класса был один, а таблиц при этом несколько и EF понимал бы, как сделать join при select и как сделать update на две таблицы?

Спасибо

@ Саша: В предыдущей части есть пример "Разделение одного типа по нескольким таблицам". Как я понял – это то что вам надо.

Я читал предыдущую часть, но если делать по примеру "Разделение одного типа по нескольким таблицам", то при добавлении объекта , в БД всегда добавляется по одной строчке на каждый объект, а в моем случае в таблице "мастер" должна быть одна запись, а в языковой таблице несколько с ключом языка.

Видимо я не так понял. Тогда так:


public class LocalizedItem
{
   public int Id { get; set; }
   public int LanguageId { get; set; }
   public string Name { get; set; }
   public string Description { get; set; }
}

public class Item
{
   public int Id { get; set; }
   public float Price { get; set; }

   public virtual LocalizedItem[] LocalizedItems { get; set; }  
}


При этом в контекст достаточно добавить только Item. На выходе вы получите 2 таблицы со связью 1 Item ко многим LocalizedItem.

Алексей 04.05.2012 16:50:46

Есть у нас два класса

    public class Account
    {
        public int Id { get; set; }
        
        public virtual ICollection<User> Users { get; set; }
    }

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

        public int AccountId { get; set; }
        public virtual Account Account { get; set; }

        [Required]
        public string Email { get; set; }

    }

  далее пишет вот такой код

  var account = new Account();
  db.Accounts.Add(account);
  var user = new User() { Email = "mail@ru.ru" };

  если мы попытаемся теперь сделать
  account.Users.Add( user );
  то у нас выдастся ошибка "Ссылка на объект не указывает на экземпляр объекта."
  при этом если делаем
  
  user.Account = account;
  db.Users.Add ( user);
  var user2 = new User() { Email = "mail@ru.ru" };
  account.Users.Add( user2 );
  
  то второй User добавляется нормально.

  
  Можно ли как-то сделать чтобы и для первого добавления было так же?

Потому что value-type поля (и int в частности) всегда считаются как [Required]. В перовом случае user не связан ни с одним аккаунтом. Надо или указать его явно, или заменить int на int? (т.е. тип, который может принимать null). Как быть - на 100% зависит от бизнес-логики приложения.

Алексей 05.05.2012 16:49:24

Andrey :
Потому что value-type поля (и int в частности) всегда считаются как [Required]. В перовом случае user не связан ни с одним аккаунтом. Надо или указать его явно, или заменить int на int? (т.е. тип, который может принимать null). Как быть - на 100% зависит от бизнес-логики приложения.

Второй пользователь также не связан ни с одним аккаунтом, но добавился корректно.

решил проблему через конструктор
public Account()
{
  this.Users = new List<User>();
}
Вроде бы все работает, но насколько так корректно делать?

Извините, пропустил один момент. Вы же user добавляете не сразу в таблицу, а в аккаунт. Так что правильно что возникает ошибка. Ведь на момент вызова account.Users.Add(user); значение account.Users == null.

Но я бы вообще сделал по другому (и отказался бы от конструктора).

var db = new AppContext();
var account = new Account();
db.Accounts.Add(account);
db.SaveChanges();

// после SaveChanges свойство account.Id получит значение

var user = new User() {
    Email = "mail@ru.ru",
    AccountId = account.Id
};
db.Users.Add(user);

var user2 = new User() {
    Email = "mail@ru.ru",
    AccountId = account.Id
};
db.Users.Add(user);
db.SaveChanges();


Ну и кроме того, можно все это обернуть в транзакции по необходимости.

Алексей 05.05.2012 18:44:14

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

Вообще если бы написали статью о транзакциях в EF CF было бы вообще замечательно. У вас очень полезный блог.

Пример все же не для комментариев (форматом). О статье я подумаю, возможно будет такая.

Далее, по поводу вашего варианта:
1) Код более запутан. Попробуйте прочитать его и мой вариант, как бы не зная особенностей. Вопрос у вас и возник - в какой момент было инициализировано account.Users. IMHO в итоге можно сказать, что тут очень сильно привязываемся к особенностям реализации.

2) Создание объектов в конструкторе решает проблему с исключением. Но не решает проблему читаемости кода.

3) Несмотря на использование интерфейса ICollection<T> тип свойства Users жестко задан (а, например, EF туда бы подставила HashSet). Да и насколько часто потребуется после создания аккаунта добавлять пользователей пачками.

Если уж сильно хочется сделать все за один SaveChanges(), то можно так:

var account = new Account() {
    Users = new List<User>()
};

account.Users.Add(
    new User() { Email = "mail1@ru.ru" }
);

account.Users.Add(
    new User() { Email = "mail2@ru.ru" }
);

db.Accounts.Add(account);
db.SaveChanges();

Согласитесь, опять вышло более читаемо, чем ваш исходный код.

Игорь 12.07.2012 18:22:19

Если не затруднит, можете ответить на мой вопрос:
Навигационное поля связывают таблицы по ID и ForeignKey
class Tabel
{
   public int ID{get;set;}
   public int Value{get;set;}
   public string Data{get;set;}
}
class Table2
{
   public int ID{get;set;}
   public int Value{get;set;}
   public virtuald Table2{get;set;}
}
Будет связано так: в table2 будет создано поле table1_id и связано будет с tabe1.ID.
Мне же нужно сделать так: чтобы выборка поисзводилась по приципу Table2.Value==Table1.Value, такое можно как-то организовать или нет, если да то как?

Прошу прощения за задержку с ответом. Занят был очень сильно. По вопросу - код с ошибкой, но как я понимаю вам надо просто join в запросе. Это уже вопрос не к CodeFirst а к построению самого запроса.

Игорь 18.07.2012 12:28:56

C ошибкой, только сейчс заметил:

class Table2

{
   public int ID{get;set;}
   public int Value{get;set;}
   public virtual ICollection<Table1> T1{get;set;}//Нужно, чтобы сюда выбралось все значения из Table1 где Table1.Value==Table2.Value
}

Я спрашивал, можно ли сделать так, чтобы навигационное свойство связывало по Table1.Value == Table2.Value, я нашел только как настроить.
Чтобы было понятнее:
class Tabl2

{
   public int ID{get;set;}
   public int Value{get;set;}
   [ForeignKey("Value")]//Теперь связь таблиц по Table2.Value==Table1.ID
   public virtual Table1 T1{get;set;}
}

@ Игорь: К сожалению, не подскажу. С ходу придумать способ задать такое с помощью FluentAPI не придумал. Сам подобное как правило вытаскивал с помощью linq.

Игорь 19.07.2012 17:11:29

Andrey, ясно, благодарю за внимание.
По все видимости такого способа нет, я уже все гугл облазил, задам еще этот вопрос на msdn'e, если решение подскажут, то напишу сдесь.

Андрей 26.05.2013 23:45:13

Добрый вечер, Андрей.
Во-первых спасибо Вам за Ваши статьи.
А во-вторых, запутался я что то со всем относительно связей один-к-одному.
вот у меня есть 2 сущности :
public class Arbiter
    {                
        public int Id { get; set; }
        public string Soname { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }

        
        public int? GameId { get; set; }
        public virtual Game Game { get; set; }
    }


public class Game
    {                  
       public int Id { get; set; }
       [MaxLength (50)]
        [Required]
        public string Owners { get; set; }
        [MaxLength(50)]
        [Required]
        public string Guests { get; set; }
    
        public int? ArbiterId { get; set; }      
        public virtual Arbiter Arbiter { get; set; }          
    }  


Сделал, как Вы описывали выше
modelBuilder.Entity<Game>().HasRequired(x => x.Arbiter).WithOptional(t => t.Game);  

Теперь собственно сам вопрос : как мне осущиствить добавление в сущность Game так что бы я из Arbiter мог видеть в каких Game они участвуют.
Спасибо.

@ Андрей: Два варианта
1) У экземпляра Game указать ArbiterId чтобы связать с существующим арбитром
2) Или указать у экземпляра Game экземпляр Arbiter чтобы добавить нового арбитра.

Кстати, если Game обязательно требует арбитра, то у ней ArbiterId надо поставить int (а не int?). Тут достаточно самих моделей, EF поймет тип связи.

Павел 15.12.2013 3:52:20

Привет. Прошу помощи.
Не могу понять как может работать
HasRequired(x =>x.SingleNavSrcProp1).WithRequiredPrincipal(x => x.SingleNavTgtProp2) (или WithRequiredDependant)
прошу показать пример, когда это вообще работает?
у меня exception (как тут stackoverflow.com/.../entity-framework-entities-in-xxcontext-x-participate-in-the-x-y-relationship), при создании записи, ведь как можно требовать наличие записи на "обоих концах связи" и c одной и с другой стороны одновременно, ведь нельзя создать записи одновременно, а по одной нельзя из-за ограницения (Required). Это обсуждается и тут stackoverflow.com/.../entity-framework-code-first-one-to-one-required-required-relationship (и там написано что, это не должно работать.)
это one-to-one?
я хочу one-to-one, чтобы FK был на одной или двух сторонах, и навиг. свойства были по обе стороны. Как это сделать в последней редакции EF?

Почему везде предлагают вместо one-to-one сделать one-to-many с uniq индексом (через seed) или shared PK (что это такое на примере 2х таблиц?). Почему-то везде где написано про one-to-one или shared PK показывают 2таблицы!

Вот-тут blog.bennymichielsen.be/.../
имхо вообще бред, т.к. у меня разные варианты маппинга создавали одни и теже (!) связи в БД, но р��ботали они по разному в коде.

Павел 15.12.2013 6:41:36

Моя проблема, как я понял, была в том, что я использовал
HasRequired(x =>x.SingleNavSrcProp1).WithRequiredPrincipal(x => x.SingleNavTgtProp2).Map(m=>m.MapKey("SingleNavSrcProp1Id"))
а этого, как раз нельзя было делать. Если убрать Map(...), то как раз все работает : получается как раз Shared PK (dependent.Id является одновременно PK и FK и сопоставляется с principal.Id, одновременно энфорсится ограничение уникальности) и я могу сохранить сначала principal entity, затем dependent. Это (в моем понимании) и есть связь one-to-one и такая связь устанавливается в случае когда EF маппит наследование (table per type). Зачем тогда предлагается использовать вместо этого связь one-to-many с отдельным FK (weblogs.asp.net/.../...reign-key-associations.aspx), что это дает? и зачем Shared PK объяскняется на примере 3-х таблиц (weblogs.asp.net/.../...imary-key-associations.aspx) мне непонятно - это только запутывает.

Павел 15.12.2013 6:43:40

И зачем разрешать делать Map(m=>m.MapKey(...)) в этом случает, если это не имеет смысла?

Павел 15.12.2013 7:08:34

Shared PK позволяет сделать Table-Splitting (не путать с противоположным Entity-splitting, как попутал сначала я). Но и там конструкция  Map(m=>m.MapKey(...)) тоже не имеет смысла, так как FK нельзя заменить, он же является PK

@ Павел: Добрый день. Насколько запутался в ваиших пояснениях. Для начала вопрос - что именно вы пытаетесь сделать (какая связь между какими таблицами нужна, учитываем только нужные для связи поля)

Дообырй день, подскажите. Как сделать простую ссылку на справочник без обратной коллекции типа

public class Nomenclature
{
  public Int64 ID { get; set; }
  public string Name { get; set; }
}


public class Order
{
  public Int64 ID { get; set; }
  public Nomenclature Nomenclature { get; set; }
}

Не могу понять, как правильно настроить свойство Nomenclature, замапив его на поле NomenclatureID

@ Anton:
C помощью EF FluentAPI такое не возможно (2 связанные таблицы без навигационных свойств). Можно попробовать это сделать в Migration API. Там в методе Up использовать вызовы AddForeignKey/CreateIndex для создания ключей и индексов.

Андрей, подскажите, пожалуйста, как правильно сконфигурировать модель следующего вида.

Например, мы имеем тип "Категория".

- Каждая категория может иметь (а может и не иметь) родительскую Категорию.
- Кроме того, каждая Категория может иметь дочерние Категории (т.е. те Категории, которые в нее вложены). P.S. Дочерних Категории так же может и не быть.

В коде это можно все представить примерно так.
class Category
{
       public int ID {get; set;}

       // Родительская категория.
       public Category ParentCategory {get; set;}

       //  Дочерние категории.
       public virtual List<Category> ChildsCategory {get; set;}
}

Но вот ни как не могу сообразить, как это правильно сконфигурировать используя FluentAPI. Т.е. в итоге я хочу:

- обратившись к свойству Category.ParentCategory  - получить родительскую Категорию, если такая есть.
- обратившись к свойству Category.ChildsCategory - получить все дочерние категории, если они есть.

Андрей, дополнение к посту выше. Хотелось бы не использовать автовычисление свойств на клиенте (как в примере ниже). А сконфигурировать как-то модель и сделать так, чтобы Entity Framework все делала за нас (т.е. мы обращаемся к нужным свойствам, а EF - возвращает нам требуемый результат). Такое реально вообще или я зря стараюсь?

class Category
{
       public int ID {get; set;}
      
       // ID родительской категории.
       public Category ParentCategoryID {get; set;}

       // Родительская категория. Будем вычислять
       // исходя из ParentCategoryID
       [NotMapped]
       public Category ParentCategory
       {
             get { /* Логика вычисления родительской категории */}
             set { /* Какая-то другая логика, которая может пригодиться */}
       }

       //  Дочерние категории.
       //  Будем вычислять
       // исходя из ParentCategoryID
       [NotMapped]
       public virtual List<Category> ChildsCategory
       {
             get { /* Логика вычисления дочерних категории */}
             set { /* Какая-то другая логика, которая может пригодиться */}
       }  

Родион 21.06.2015 2:52:13

Вопрос по инициализаторам, забивающим таблицу значениями по умолчанию. Из вашего примера видно, что нет необходимости использовать SaveChange(). Почему?

Родион: Потому что после вызова Seed() метод SaveChange() будет вызван автоматически. В некоторых случаях можно вызывать его вручную, если необходимо сохранять данные по частям (например из-за ограничения по внешнему ключу).

Привет! Можно как то через Fluent API организовать связь одик-ко-многим только не напрямую между классами, а через класс родителя. То есть у меня есть 3 класса(Company,Asset,Unit), есть класс родитель(Asset), класс наследник(Unit) и 3-й класс(Company), который связан с классом родителем(Asset) по внешним ключам. Вот мне надо организовать связь между 3-м классом(Company) через родителя(Asset) со 2-м классом(Unit).  Весь этот гемор нужен для того, чтоб не создавать в Unit поле для связи с Company, так как это поле есть уже в Asset.

Ну если нужна загрузка, то можно использовать Include().
Вторая мысль, можно ведь хранить Asset и Unit в одной таблице. Это не подходит?

Родион 17.08.2015 0:21:02

Я забыл уже что спрашивал - комментарий пропал. Вроде бы про генераторы. Или вы сейчас не мне отвечаете?

Извиняюсь, забыл утвердить комментарий (пришлось включить из-за спама, но теперь все на месте).

Игорь 15.10.2015 15:05:43

Можно ли сконфигурировать связь one-to-one через join table?

Виталий 13.04.2016 11:01:41

Андрей, спасибо большое за статьи.
Очень структурировано написано.  Использую в качестве справочника при проектировании бд по ef cf.
Столкнулся с одним вопросом:
modelBuilder.Entity<Game>()
    .HasRequired(g => g.Arbitter)
    .WithOptional(a => a.Game);
Если я хочу определить связь 1 - 0 или 1 - 1. Можно ли обойтись только нотацией атрибутов и решить вопрос только в poco? То есть в модели Game объявить поле и свойство для арбитра, а в другой модели Arbitter только свойство для Game?  Таким образом в бд создастся только колонка ArbitterId. Или флюент апи из примера выше обязателен?

Виталий Только на уровне POCO не выйдет, т.к. EF не узнает кто ведущий в этой связи. А вот с атрибутами уже можно. Для этого на principal свойство (тут, как я понимаю, это Game) необходимо добавить [Required]

Родион 28.04.2016 22:16:16

Добрый вечер.

Столкнулся с такой проблемой. Есть две таблицы, основная и зависимая, но связанны они по не ключевым полям (хоть и уникальным). Можно ли такую связь задать через fluint? или хотя бы через аннотации.

Родион Не совсем понял вашу задачу. В принципе ничего не мешает объявить соответствующие поля в каждой модели.

Родион 29.04.2016 21:35:38

Связь один-к-одному-или-к-нулю как я понял внешний ключ можно указать через Map. Но как указать какое свойство является ключом из внешней таблицы?

public class Restriction
    {
        public virtual long Id { get; set; }
        public virtual string FileName { get; set; }
        public virtual string District { get; set; }
        public virtual long ParamId { get; set; }
    }

    public class Report
    {
        public virtual long Id { get; set; }
        public virtual long AnswId { get; set; }
        public virtual string Description { get; set; }
        public virtual string ReportType { get; set; }

        public virtual Restriction AnswIds { get; set; }
   }

Restriction и Report связаны по полям ParamId и AnswId.

Ну если правильно понял, то:

public long ParamId { get; set; }
[ForigenKey("ParamId")]
public virtual Report Report { get; set; }

Укажет что ParamId это ключ для связи с Report, в другой сущности аналогично.

Кстати зачем везде virtual? Достаточно только на навигации.

Родион 05.05.2016 22:32:48

Пример из nHibernat'а взял, забыл исправить.
Кстати у вас капча всегда 3+3

Добавить комментарий