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

Лекции / Глава 17.2 Entity Framework 2

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

ГЛАВА 17. ENTITY FRAMEWORK

 

Оглавление

 

§17.7 Основные операции с данными ...................................................................

1

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

7

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

13

§17.10 Связь один ко многим...............................................................................

25

§17.11 Миграции ...................................................................................................

30

§17.7 Основные операции с данными

 

Большинство операций с данными представляют собой

CRUD-

операции (Create, Read, Update, Delete), то есть получение данных, создание,

обновление и удаление. Entity Framework позволяет легко производить данные операции.

 

 

Для примеров с операциями возьмем модель User, созданную в

предыдущем параграфе:

 

 

Листинг 17.7

 

 

 

 

 

1

public partial class User

 

 

2

{

 

 

3

public int Id { get; set; }

 

 

4

public string Log { get; set; }

 

 

5

public string Password { get; set; }

 

 

6

}

 

 

 

И следующий класс контекста данных:

 

 

Листинг 17.8

 

 

 

1

public partial class UserContainer : DbContext

 

 

2

{

 

 

3

public UserContainer() : base("name=UserContainer")

 

 

{}

 

 

 

 

 

4

protected override void

 

 

OnModelCreating(DbModelBuilder modelBuilder)

 

 

 

 

 

5

{

 

 

6

throw new UnintentionalCodeFirstException();

 

 

7

}

 

 

8

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

 

 

9

}

 

 

 

1

 

Добавление (CREATE)

Создадим форму добавления новых объектов:

 

Рисунок 1

 

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

 

Листинг 17.9

 

 

 

1

using (UserContainer db = new UserContainer())

 

2

{

 

3

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

 

Password = textBoxPassword.Text };

 

 

 

4

db.Users.Add(user);

 

5

db.SaveChanges();

 

6

}

 

7

listBoxUsers.Items.Clear();

 

8

using (UserContainer db = new UserContainer())

 

9

{

 

10

foreach (var item in db.Users)

 

11

{

 

12

listBoxUsers.Items.Add(item.Log);

 

13

}

 

14

}

 

После добавления надо сохранить все изменения с помощью метода

SaveChanges(). В строках 7-13 происходит обновление списка – в него загружаются все элементы из таблицы Users.

2

Рисунок 2

Обновление (UPDATE)

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

Users:

Рисунок 3

Контекст данных способен отслеживать изменения объектов, поэтому для редактирования объекта достаточно изменить его свойства и после этого вызвать метод SaveChanges():

 

Листинг 17.10

 

 

 

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

}

 

 

 

 

11

user.Log = textBoxNewLog.Text;

 

 

 

 

 

3

 

12

user.Password = textBoxNewPassword.Text;

 

 

13

db.SaveChanges();

 

 

14

}

 

 

Здесь user – объект, который выбирается из списка listBoxUsers для дальнейшего редактирования.

Для поиска искомого объекта в базе данных используется метод Find

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

Рассмотрим ситуацию, где объект User будет получен в одном контексте, а изменять свои значения будет для другого контекста, который его не отслеживает:

 

Листинг 17.11

 

 

 

 

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

}

 

 

11

}

 

 

12

using (UserContainer db = new UserContainer())

 

13

{

 

 

14

user.Log = textBoxNewLog.Text;

 

 

15

user.Password = textBoxNewPassword.Text;

 

 

16

db.SaveChanges();

 

17}

Витоге при попытке обновления изменения не сохранятся. Чтобы

изменения сохранились, нам явным образом надо установить для состояния

объекта значение EntityState.Modified:

4

 

Листинг 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:

 

Рисунок 4

 

Для удаления объекта применяется метод 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

}

 

 

5

 

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 = "Пользователь ";

 

 

6

 

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:

7

Листинг 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 (наименование темы дипломного проекта), здесь также определен внешний ключ. Внешний ключ состоит из обычного свойства и навигационного.

Свойство

8

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?, то есть

9

допускает значения 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();

 

 

10