Добавил:
Преподаватель Колледжа информационных технологий Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Лекции / Глава 17. Entity Framework

.pdf
Скачиваний:
68
Добавлен:
08.05.2022
Размер:
1.63 Mб
Скачать

 

Листинг 17.12

 

 

 

1

using (UserContainer db = new UserContainer())

 

2

{

 

3

user.Log = textBoxNewLog.Text;

 

4

user.Password = textBoxNewPassword.Text;

 

5

db.Entry(user).State = EntityState.Modified;

 

6

db.SaveChanges();

 

7

}

 

Метод Entry получает объект DbEntityEntry для данной сущности из контекста UserContainer, предоставляющий доступ к информации о сущности и возможность выполнять действия с этой сущностью. В нашем случае с помощью свойства State изменяется состояние измененной сущности для возможности присоединения ее к контексту и дальнейшей ее отправки в базу данных при вызове метода SaveChanges.

Удаление (DELETE)

Создадим форму удаления объектов, хранящихся в таблице Users:

 

Рисунок 36

 

Для удаления объекта применяется метод Remove() объекта DbSet:

 

Листинг 17.13

 

 

 

1

User user = null;

 

2

using (UserContainer db = new UserContainer())

 

3

{

 

4

foreach (var item in db.Users)

 

5

{

 

6

if (item.Log == (string)listBoxUsers.SelectedItem)

 

7

{

 

8

user = db.Users.Find(item.Id);

 

9

}

 

10

}

 

 

31

 

11db.Users.Remove(user);

12db.SaveChanges();

13}

Метод Remove помечает сущность, переданную в параметры, как удаленную, так что она будет удалена из базы данных при вызове

SaveChanges. Обратите внимание, что перед вызовом этого метода объект должен существовать в контексте в каком-то другом состоянии.

Но, как и в случае с обновлением здесь мы можем столкнуться с похожей проблемой, когда объект получаем из базы данных в пределах одного контекста, а пытаемся удалить в другом контексте. И в этом случае нам надо установить вручную у объекта состояние EntityState.Deleted:

 

Листинг 17.14

 

 

 

1

using(UserContainer db = new UserContainer())

 

2

{

 

3

db.Entry(user).State = EntityState.Deleted;

 

4

db.SaveChanges();

 

5

}

 

В данном случае состояние сущности изменяется на “Удалено” для ее удаления из базы данных и отсоединения из контекста после вызова метода

SaveChanges.

Присоединение существующей сущности к контексту

Если у вас есть сущность, которая уже существует в базе данных, но в настоящий момент не отслеживается контекстом, можно указать контексту отслеживание сущности с помощью метода Attach в DbSet. Сущность будет находиться в неизмененном состоянии в контексте.

Например:

 

Листинг 17.15

 

 

 

1

User user = new User() { Log = textBoxLog.Text, Password =

 

 

textBoxPassword.Text };

 

2

using (UserContainer db = new UserContainer())

 

3

{

 

4

db.Users.Attach(user);

 

5

string tempLog = db.Entry(user).Entity.Log;

 

6

db.Entry(user).Entity.Log = "Пользователь ";

 

 

32

 

7

db.Entry(user).Entity.Log += tempLog;

8

db.Users.Add(db.Entry(user).Entity);

9

db.SaveChanges();

10

}

Встроке 4 объект user помещается в контекст данных UserContainer

внеизмененном состоянии. Далее, в строке 5 в строковую переменную

tempLog сохраняется логин, полученный от объекта, который был ранее помещен в контекст. В строках 6-7 происходит изменение структуры логина – в начало помещается слово “Пользователь”, после которого идет старый логин, записанный в поле ввода при создании пользователя. После этого, в

строке 8, происходит добавление объекта в контекст для дальнейшего добавления в базу данных после вызова метода SaveChanges.

Обратите внимание, что в базу данных не вносятся изменения, если метод SaveChanges вызывается без выполнения других манипуляций с присоединенной сущностью. Это вызвано тем, что сущность находится в

неизмененном состоянии.

§17.8 Навигационные свойства и загрузка данных

В предыдущей теме в качестве модели был использован класс User,

представляющий пользователя и содержащий три свойства. Однако эта была очень простая модель. В реальности в нашей базе данных может быть не одна,

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

Рассмотрим создание модели данных для информационной системы по поиску дипломных руководителей для студентов отделений среднего профессионального образования. Допустим, для каждого дипломного руководителя может быть определен набор примерных тем для дипломного проекта, которые он предлагает студентам. И, наоборот, одна примерная тема дипломного проекта может быть предложена только одним дипломным руководителем. То есть в данном случае у нас связь один-ко-многим (one-to- many).

Например, у нас определен следующий класс футбольной команды

Professor:

33

Листинг 17.16

1

public class Professor

2

{

3

public int Id { get; set; }

4

public string Name { get; set; }

5

public string Position { get; set; }

6

public string PersonalData { get; set; }

7

public ICollection<Thesis> Theses { get; set; }

8

public Professor()

9

{

10

this.Theses = new List<Thesis>();

11

}

12

}

Здесь Name – это ФИО дипломного руководителя, Position – его должность, PersonalData – информация об его профессиональной деятельности, Theses – внешний ключ, представляющий собой обобщенную коллекцию ICollection<Thesis> для хранения тем дипломных проектов

Thesis, предлагаемых им.

Интерфейс обобщенной коллекции ICollection<T> представляет ряд общих свойств и методов для всех обобщенных коллекций (например, методы

CopyTo, Add, Remove, Contains, свойство Count).

А класс Thesis, описывающий дипломный проект, мог бы выглядеть

следующим образом:

Листинг 17.17

1

public class Thesis

 

2

{

 

3

public int Id { get; set;

}

4

public string Name { get;

set; }

5

public int? ProfessorId {

get; set; }

6

public Professor Professor { get; set; }

7

}

 

Кроме обычного свойства Name (наименование темы дипломного проекта), здесь также определен внешний ключ. Внешний ключ состоит из обычного свойства и навигационного.

Свойство

34

public Professor Professor { get; set; }

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

Аналогично в классе Professor также имеется навигационное свойство

- Theses, через которое мы можем получать темы дипломных проектов,

предлагаемые данным преподавателем.

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

1.Имя_навигационного_свойства + Имя ключа из связанной таблицы

- в нашем случае имя навигационного свойства Professor, а ключа из модели Professor Id, поэтому в нашем случае нам надо обозвать свойство ProfessorId, что собственно и было сделано в вышеприведенном коде.

2.Имя_класса_связанной_таблицы + Имя ключа из связанной таблицы - в нашем случае класс Professor, а ключа из модели Team -

Id, поэтому опять же в этом случае получается ProfessorId.

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

таблица Theses будет иметь следующее определение:

 

 

 

Листинг 17.18

 

 

 

1

CREATE TABLE [dbo].[Theses] (

 

2

[Id]

INT

IDENTITY (1, 1) NOT NULL,

 

3

[Name]

NVARCHAR (MAX) NULL,

 

4

[ProfessorId] INT

NULL,

 

5

CONSTRAINT [PK_dbo.Theses] PRIMARY KEY CLUSTERED ([Id] ASC),

 

6

CONSTRAINT [FK_dbo.Theses_dbo.Professors_ProfessorId] FOREIGN

 

 

KEY ([ProfessorId]) REFERENCES [dbo].[Professors] ([Id])

 

7

);

 

 

 

 

При определении внешнего ключа нужно иметь в виду следующее. Если

тип обычного свойства во внешнем ключе определяется как int?, то есть

35

допускает значения null, то при создании базы данных соответствующее поле так будет принимать значения NULL:

[ProfessorId] INT NULL.

Однако если мы изменим в классе Thesis тип ProfessorId на просто

int:

public int TeamId { get; set; },

то в этом случае соответствующее поле имело бы ограничение NOT NULL, а

внешний ключ определял бы каскадное удаление:

 

 

 

Листинг 17.19

 

 

 

1

CREATE TABLE [dbo].[Theses] (

 

2

[Id]

INT

IDENTITY (1, 1) NOT NULL,

 

3

[Name]

NVARCHAR (MAX) NULL,

 

4

[ProfessorId] INT

NOT NULL,

 

5

CONSTRAINT [PK_dbo.Theses] PRIMARY KEY CLUSTERED ([Id] ASC),

 

 

CONSTRAINT [FK_dbo.Theses_dbo.Professors_ProfessorId] FOREIGN

 

6

KEY ([ProfessorId]) REFERENCES [dbo].[Professors] ([Id]) ON

 

 

DELETE CASCADE

 

 

7

);

 

 

 

 

 

Способы загрузки и получения связанных данных

В Entity Framework есть три способа загрузки данных:

1.eager loading ("жадная загрузка")

2.explicit loading ("явная загрузка")

3.lazy loading ("ленивая загрузка")

Жадная загрузка

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

получим темы дипломных работ, предлагаемые выбранным в списке руководителем:

Листинг 17.20

1

using (UserContext db = new UserContext())

2

{

3

var professors =

db.Professors.Include("Theses").ToList();

 

 

36

4

foreach (Professor p in professors)

5

{

6

if (p.Name == name)

7

{

8

foreach (Thesis t in p.Theses)

9

{

10

listBox1.Items.Add(t.Name);

11

}

12

}

13

}

14

}

Без использования метода Include мы бы не могли бы получить связанные с руководителем дипломные проекты и его свойства.

Явная загрузка

Явная загрузка предусматривает применение метода Load() для загрузки данных в контекст. Например:

 

Листинг 17.21

 

 

 

1

using (UserContext db = new UserContext())

 

2

{

 

3

var FirstProfessor = db.Professors.FirstOrDefault();

 

4

db.Entry(FirstProfessor).Collection("Theses").Load();

 

5

foreach (Thesis t in FirstProfessor.Theses)

 

6

{

 

7

listBox1.Items.Add(t.Name);

 

8

}

 

9

}

 

В данной программе метод FirstOrDefault находит первый элемент из коллекции Professors и присваивает переменной FirstProfessor.

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

Если связанные объект не представляет коллекцию, то применяется метод Reference(), в который также передается навигационное свойство.

37

Ленивая загрузка

Еще один способ представляет так называемая «ленивая загрузка» или lazy loading. При таком способе подгрузки при первом обращении к объекту,

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

При использовании ленивой загрузки надо учитывать некоторые ограничении при описании классов. Так, классы, использующие ленивую загрузку должны быть публичными, а их свойства должны иметь модификаторы public и virtual. Например, классы Professor и Thesis

могут иметь следующее определение:

Листинг 17.22

1 public class Professor

2 {

3 public int Id { get; set; }

4 public string Name { get; set; }

5 public string Position { get; set; }

6 public string PersonalData { get; set; }

7 public virtual ICollection<Thesis> Theses { get; set; }

8 public Professor()

9 {

10 this.Theses = new List<Thesis>();

11 }

12 }

13 public class Thesis

14 {

15 public int Id { get; set; }

16 public string Name { get; set; }

17 public int? ProfessorId { get; set; }

18 public virtual Professor Professor { get; set; }

19 }

В этом случае нам не потребуется использовать какие-то дополнительные методы, как Include или Load:

Листинг 17.23

1 if (this.Professor.Theses.Count != 0)

38

2

{

3

for (int i = 0; i < this.Professor.Theses.Count; i++)

4

{

5

listBox1.Items.Add(this.Professor.Theses.ToList()[i].Name);

6

}

7

}

В данной программе происходит добавление в список listBox1

наименований тем дипломных проектов Professor.Theses.ToList()[i].Name,

закрепленных за преподавателем Professor.

§17.9 Связь один к одному

Строго говоря в Entity Framework нет как таковой связи один-к-одному,

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

Но все же нередко возникает потребность в наличие подобной связи между объектами в приложении, и в Entity Framework мы можем настроить данный тип отношений.

Рассмотрим стандартный пример подобных отношений: есть класс пользователя User, который хранит логин, пароль и роль, то есть данные учетных записей. А все данные профиля, такие как имя, номер группы (для студента), должность (для дипломного руководителя) и так далее, выделяются в класс профиля UserProfile.

 

Листинг 17.24

 

 

 

1

using System.Collections.Generic;

 

2

using System.ComponentModel.DataAnnotations;

 

3

using System.ComponentModel.DataAnnotations.Schema;

 

4

 

5

public class User

 

6

{

 

7

public int Id { get; set; }

 

8

public string Login { get; set; }

 

9

public string Password { get; set; }

 

10

public string Role { get; set; }

 

11

public UserProfile Profile { get; set; }

 

12

}

 

13

public class UserProfile

 

14

{

 

 

39

 

15

[Key]

16

[ForeignKey("User")]

17

public int Id { get; set; }

18

public string Name { get; set; }

19

public int NumberGroup { get; set; }

20

public string PersonalDate { get; set; }

21

public User User { get; set; }

22

public Professor Professor { get; set; }

23

}

В этой связи между классами класс UserProfile является дочерним или подчиненным по отношению к классу User. И чтобы установить связь одни к одному, у подчиненного класса устанавливается свойство идентификатора, которое называется также, как и идентификатор в основном классе. То есть в классе User свойство называется Id, то и в UserProfile

также свойство называется Id. Если бы в классе User свойство называлось бы

UserId, то такое же название должно было быть и в UserProfile.

И в классе UserProfile над этим свойством Id устанавливаются два атрибута: [Key], который показывает, то это первичный ключ, и

[ForeignKey], который показывает, что это также и внешний ключ. Причем

внешний ключ к таблице объектов User.

 

 

Соответственно классы User и UserProfile имеют ссылки друг на

друга.

 

 

В классе контекста определяются свойства для взаимодействия с

таблицами в базе данных:

 

 

Листинг 17.25

 

 

 

 

 

1

public class UserContext : DbContext

 

 

2

{

 

 

3

public UserContext()

 

 

4

{ }

 

 

5

public DbSet<User> Users { get; set; }

 

 

6

public DbSet<UserProfile> UserProfiles { get; set; }

 

 

7

public DbSet<Professor> Professors { get; set; }

 

 

8

public DbSet<Thesis> Theses { get; set; }

 

 

9

}

 

 

 

40