Форум программистов, компьютерный форум, киберфорум
C#: Базы данных, ADO.NET
Войти
Регистрация
Восстановить пароль
Карта форума Темы раздела Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.93/15: Рейтинг темы: голосов - 15, средняя оценка - 4.93
Модератор
Эксперт .NET
15466 / 10712 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
1

Получение связанных данных (свойство навигации) в SQLite

09.01.2020, 13:46. Показов 2780. Ответов 9

Author24 — интернет-сервис помощи студентам
Первый опыт работы с SQLite и FW.

Создал базу из двух таблиц
Во второй (Rooms) Вторичный ключ по ID первой (Dormitories)
SQL
1
2
3
4
5
CREATE TABLE "Dormitories" (
    "ID"    INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
    "Title" TEXT NOT NULL,
    "Address"   TEXT NOT NULL
);
SQL
1
2
3
4
5
6
CREATE TABLE "Rooms" (
    "ID"    INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
    "DormitoryID"   INTEGER NOT NULL,
    "Number"    INTEGER NOT NULL,
    FOREIGN KEY("DormitoryID") REFERENCES "Dormitories"("ID")
);


Создал классы для контекстной Модели и саму Модель
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
using CommLibrary;
using StDorModelLibrary.DTOClasses;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
 
namespace SQLiteModel
{
    /// <summary>Класс для представления Общежития из БД</summary>
    [Table("Dormitories")]
    public class DormitoryBD : ICopy<DormitoryDTO>
    {
        /// <summary>Идентификатор</summary>
        [Key]
        [Column("ID")]
        public int ID { get; set; }
        /// <summary>Название</summary>
        [Required]
        [Column("Title")]
        [ConcurrencyCheck]
        [Index]
        public string Title { get; set; }
        /// <summary>Адрес</summary>
        [Required]
        [Column("Address")]
        [ConcurrencyCheck]
        public string Address { get; set; }
 
        /// <summary>Коллекция комнат</summary>
        public IList<RoomBD> Rooms { get; set; } = new List<RoomBD>();
 
        public DormitoryDTO Copy() => new DormitoryDTO(ID, Title, Address);
 
        public void CopyFrom(DormitoryDTO other)
        {
            ID = other.ID;
            Title = other.Title;
            Address = other.Address;
        }
 
        public void CopyTo(DormitoryDTO other)
        {
            throw new NotImplementedException();
        }
    }
}
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
using CommLibrary;
using StDorModelLibrary.DTOClasses;
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
 
namespace SQLiteModel
{
    /// <summary>Класс для представления Комнаты из БД</summary>
    [Table("Rooms")]
    public class RoomBD : ICopy<RoomDTO>
    {
        /// <summary>Идентификатор</summary>
        [Key]
        [Column("ID")]
        public int ID { get; set; }
 
        /// <summary>ID Общежития - Внешний ключ</summary>
        [Required]
        [Column("DormitoryID")]
        [ConcurrencyCheck]
        [Index]
        [ForeignKey("Dormitory")]
        public int DormitoryID { get; set; }
 
        /// <summary>Номер комнаты</summary>
        [Required]
        [Column("Number")]
        [ConcurrencyCheck]
        public int Number { get; set; }
 
        /// <summary>Связанное общежитие - Свойство навигации</summary>
        [ForeignKey("DormitoryID")]
        public DormitoryBD Dormitory { get; set; }
 
        public RoomDTO Copy() => new RoomDTO(ID, DormitoryID, Number);
 
        public void CopyFrom(RoomDTO other)
        {
            ID = other.ID;
            DormitoryID = other.DormitoryID;
            Number = other.Number;
        }
 
        public void CopyTo(RoomDTO other)
        {
            throw new NotImplementedException();
        }
    }
}
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
using System.Data.Entity;
 
namespace SQLiteModel
{
    public class StudentDormitoriesContext : DbContext
    {
        public StudentDormitoriesContext() : base("DefaultConnection")
        {
        }
        public DbSet<RoomBD> Rooms { get; set; }
        public DbSet<DormitoryBD> Dormitories { get; set; }
 
        //protected override void OnModelCreating(DbModelBuilder modelBuilder)
        //{
        //    modelBuilder.Entity<Room>().ToTable("Rooms");
        //    modelBuilder.Entity<Dormitory>().ToTable("Dormitories");
        //    base.OnModelCreating(modelBuilder);
        //}
    }
}
Интерфейсы и методы копирования нужны в дальнейшем.

Теперь пытаюсь получить данные из Базы.
Получение общего списка Dormitories и Rooms - без проблем.
А вот свойство-навигации RoomBD.Dormitory получается не всегда.

Рабочий вариант
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
using StDorModelLibrary.DTOClasses;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
 
namespace SQLiteModel
{
    class Program
    {
        static void Main(string[] args)
        {
 
            List<RoomDTO> Rooms;
            List<DormitoryDTO> Dormitories;
            List<DormitoryDTO> RoomsDormitories;
            List<List<RoomDTO>> DormitoriesRooms;
            int rCount;
            int dCount;
            using (StudentDormitoriesContext sdc = new StudentDormitoriesContext())
            {
 
                sdc.Dormitories.Load();
                Dormitories = new List<DormitoryDTO>();
                DormitoriesRooms = new List<List<RoomDTO>>();
                foreach (DormitoryBD dorm in sdc.Dormitories)
                {
                    Dormitories.Add(dorm.Copy());
                    DormitoriesRooms
                        .Add(new List<RoomDTO>(dorm.Rooms.Select(rm => rm.Copy()))); 
                }
 
                sdc.Rooms.Load();
                Rooms = new List<RoomDTO>();
                RoomsDormitories = new List<DormitoryDTO>();
 
                foreach (RoomBD room in sdc.Rooms)
                {
                    Rooms.Add(room.Copy());
                    RoomsDormitories.Add(room.Dormitory.Copy()); // Здесь room.Dormitory заполнено
                }
 
                rCount = Rooms.Count();
                dCount = Dormitories.Count();
            }
 
 
        }
    }
}
Нерабочий вариант
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using StDorModelLibrary.DTOClasses;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
 
namespace SQLiteModel
{
    class Program
    {
        static void Main(string[] args)
        {
 
            List<RoomDTO> Rooms;
            List<DormitoryDTO> Dormitories;
            List<DormitoryDTO> RoomsDormitories;
            List<List<RoomDTO>> DormitoriesRooms;
            int rCount;
            int dCount;
            using (StudentDormitoriesContext sdc = new StudentDormitoriesContext())
            {
 
                sdc.Rooms.Load();
                Rooms = new List<RoomDTO>();
                RoomsDormitories = new List<DormitoryDTO>();
 
                foreach (RoomBD room in sdc.Rooms)
                {
                    Rooms.Add(room.Copy());
                    RoomsDormitories.Add(room.Dormitory.Copy()); // Здесь room.Dormitory = null
                }
 
                sdc.Dormitories.Load();
                Dormitories = new List<DormitoryDTO>();
                DormitoriesRooms = new List<List<RoomDTO>>();
                foreach (DormitoryBD dorm in sdc.Dormitories)
                {
                    Dormitories.Add(dorm.Copy());
                    DormitoriesRooms
                        .Add(new List<RoomDTO>(dorm.Rooms.Select(rm => rm.Copy()))); 
                }
 
                rCount = Rooms.Count();
                dCount = Dormitories.Count();
            }
 
        }
    }
}
Теперь вопрос.
По алгоритму работы мне нужно будет иногда получать Комнаты, но не нужно Общежития.
Но как я понял по экспериментам такое не возможно.

Или я что-то упускаю? Надо добавить какие-то настройки, методы, атрибуты?

Архив решения на всякий случай приложил.
Вложения
Тип файла: 7z SQLiteModel.7z (193.0 Кб, 3 просмотров)
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
09.01.2020, 13:46
Ответы с готовыми решениями:

Редактирование связанных объектов через EF в SQLite, C#, EF 6.2, SQLite, C# Winforms
Здравствуйте. Суть: используя вышеперечисленные технологии, при чтении из базы данных связанных...

Получение данных по id пользователя SQLite
Можно ли при авторизации записать данные пользователя в какую-нибудь область, чтобы эти данные...

Получение данных из файла sqlite *.db в режиме он-лайн
по самому соединению с SQLite нашел много инфы, но хотелось бы уяснить ещё 1 момент Есть модем с...

[EF] Указан недопустимый путь Include. Тип не объявляет свойство навигации
Есть модель, сгенерированная из базы: public class Judge { public int id { get;...

9
Эксперт .NET
12079 / 8388 / 1281
Регистрация: 21.01.2016
Сообщений: 31,601
10.01.2020, 07:22 2
Лучший ответ Сообщение было отмечено Элд Хасп как решение

Решение

Цитата Сообщение от Элд Хасп Посмотреть сообщение
Вторичный ключ по ID первой (Dormitories)
Это называется "внешний ключ".

Цитата Сообщение от Элд Хасп Посмотреть сообщение
sdc.Rooms.Load();
Я не понимаю откуда люди берут такой пример. В большинстве случаев метод Load трогать не надо. DbSet<T> реализует набор методов для описания того что и как вы хотите достать.

C#
1
2
3
var rooms = sdc.Rooms
        .Include(x => x.Dormitory)
        .ToList();
Выгрузит все Room и для каждой подтянет связанную Dormitory.

C#
1
2
3
var rooms = sdc.Dormitories
        .Include(x => x.Rooms)
        .ToList();
Выгрузит все Dormitories и для каждой подтянет список Rooms.

Цитата Сообщение от Элд Хасп Посмотреть сообщение
По алгоритму работы мне нужно будет иногда получать Комнаты, но не нужно Общежития.
Но как я понял по экспериментам такое не возможно.
Да всё возможно. Посмотрите документацию и примеры. Если что, то спрашивайте. Не делайте выводов на базе каких-то экспериментов)
1
Модератор
Эксперт .NET
15466 / 10712 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
10.01.2020, 12:06  [ТС] 3
Цитата Сообщение от Usaga Посмотреть сообщение
Это называется "внешний ключ".
ОК. Только изучаю. Пока сбиваюсь с терминологии.

Цитата Сообщение от Usaga Посмотреть сообщение
Я не понимаю откуда люди берут такой пример. В большинстве случаев метод Load трогать не надо.
Откуда... Ну, как обычно из самых распространенных источников - Metanit, professorWeb и т.п.

Цитата Сообщение от Usaga Посмотреть сообщение
Выгрузит все Room и для каждой подтянет связанную Dormitory.
....
Выгрузит все Dormitories и для каждой подтянет список Rooms.
То есть правильное использование это так:
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
            List<RoomDTO> Rooms;
            List<DormitoryDTO> Dormitories;
            List<DormitoryDTO> RoomsDormitories;
            List<List<RoomDTO>> DormitoriesRooms;
            int rCount;
            int dCount;
            using (StudentDormitoriesContext sdc = new StudentDormitoriesContext())
            {
 
                //sdc.Rooms.Load();
                Rooms = new List<RoomDTO>();
                RoomsDormitories = new List<DormitoryDTO>();
 
                foreach (RoomBD room in sdc.Rooms.Include(x => x.Dormitory))
                {
                    Rooms.Add(room.Copy());
                    RoomsDormitories.Add(room.Dormitory.Copy());
                }
 
 
                //sdc.Dormitories.Load();
                Dormitories = new List<DormitoryDTO>();
                DormitoriesRooms = new List<List<RoomDTO>>();
                foreach (DormitoryBD dorm in sdc.Dormitories.Include(x => x.Rooms))
                {
                    Dormitories.Add(dorm.Copy());
                    DormitoriesRooms
                        .Add(new List<RoomDTO>(dorm.Rooms.Select(rm => rm.Copy())));
                }
 
 
                rCount = Rooms.Count();
                dCount = Dormitories.Count();
            }
Цитата Сообщение от Usaga Посмотреть сообщение
DbSet<T> реализует набор методов для описания того что и как вы хотите достать.
.....
Посмотрите документацию и примеры.
Спасибо!
Пока делаю только первые шаги и не знаю что даже искать....
Чуток какое-то представление в голове появится дальше станет намного проще.

Цитата Сообщение от Usaga Посмотреть сообщение
Если что, то спрашивайте.
Пару вопросов сразу.
Таким образом мы получаем ВСЕ Room и Dormitory.

А как быть если нужен только 1 из коллекции?

Допустим, такой метод
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
        /// <summary>Получение списка комнат одного общежития</summary>
        /// <param name="dormitoryID">ID общежития</param>
        /// <returns>Список комнат</returns>
        public static IEnumerable<RoomDTO> GetDormitoryRooms(int dormitoryID)
        {
            using (StudentDormitoriesContext sdc = new StudentDormitoriesContext())
            {
                return sdc.Dormitories.Include(x => x.Rooms)
                    .FirstOrDefault(x => x.ID == dormitoryID)?
                    .Rooms
                    .Select(rm => rm.Copy())
                    .ToImmutableArray();
            }
        }
Но ради ОДНОГО общежития приходится загружать все!
Как правильно в этом случае быть?
0
Эксперт .NET
12079 / 8388 / 1281
Регистрация: 21.01.2016
Сообщений: 31,601
10.01.2020, 12:17 4
Цитата Сообщение от Элд Хасп Посмотреть сообщение
То есть правильное использование это так:
Не совсем. Правильнее сначала материализовать ваш запрос (получить данные), а уже потом с ними работать.

C#
1
2
3
var rooms = sdc.Rooms.Include(x => x.Dormitory)
        .ToList(); // <-- материализация, на этом этапе будет сформирован SQL-запрос
foreach (RoomBD room in rooms) // А дальше уже работаем с обычным List<Room>
Цитата Сообщение от Элд Хасп Посмотреть сообщение
А как быть если нужен только 1 из коллекции?
Вы можете указать, что вам надо, просто подойдя к данным с другого конца:

C#
1
2
3
                return sdc.Rooms
                    .Where(x => x.DormitoryID == dormitoryID)
                    .ToImmutableArray();
Добавлено через 2 минуты
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Но ради ОДНОГО общежития приходится загружать все!
В вашем примере загружается только одно общежитие. Просто из него отдельно вытаскиваются комнаты.
0
Модератор
Эксперт .NET
15466 / 10712 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
10.01.2020, 12:41  [ТС] 5
Цитата Сообщение от Usaga Посмотреть сообщение
В вашем примере загружается только одно общежитие. Просто из него отдельно вытаскиваются комнаты.
То есть
C#
1
2
3
4
5
6
7
8
9
10
11
12
                return sdc.Dormitories
 
                     // Здесь только формируется запрос, но данные во все Dormitories не загружаются
                    .Include(x => x.Rooms)
 
                    // Здесь получаем нужный item и только здесь в него загружаются  Rooms
                    .FirstOrDefault(x => x.ID == dormitoryID)? 
 
                     // Дальше уже преобразование типов и к запросу отношение не имеет
                    .Rooms
                    .Select(rm => rm.Copy())
                    .ToImmutableArray();
Добавлено через 1 минуту
Цитата Сообщение от Usaga Посмотреть сообщение
Вы можете указать, что вам надо, просто подойдя к данным с другого конца:
Вариантов, конечно, может быть несколько.
Но в данном случае ищу понимания взаимодействия EF с базой.
0
Эксперт .NET
12079 / 8388 / 1281
Регистрация: 21.01.2016
Сообщений: 31,601
10.01.2020, 13:52 6
Цитата Сообщение от Элд Хасп Посмотреть сообщение
То есть
Да. Так не делается)
0
Модератор
Эксперт .NET
15466 / 10712 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
10.01.2020, 14:16  [ТС] 7
Цитата Сообщение от Usaga Посмотреть сообщение
Да. Так не делается)
Вы имеете виду, что как этот вариант работает я правильно понял
C#
1
2
3
4
5
6
7
8
9
10
11
12
                return sdc.Dormitories
 
                     // Здесь только формируется запрос, но данные во все Dormitories не загружаются
                    .Include(x => x.Rooms)
 
                    // Здесь получаем нужный item и только здесь в него загружаются  Rooms
                    .FirstOrDefault(x => x.ID == dormitoryID)? 
 
                     // Дальше уже преобразование типов и к запросу отношение не имеет
                    .Rooms
                    .Select(rm => rm.Copy())
                    .ToImmutableArray();
Но предпочтительней
C#
1
2
3
                return sdc.Rooms
                    .Where(x => x.DormitoryID == dormitoryID)
                    .ToImmutableArray();
Если не трудно - причины такого предпочтения?
0
Эксперт .NET
12079 / 8388 / 1281
Регистрация: 21.01.2016
Сообщений: 31,601
10.01.2020, 14:22 8
Цитата Сообщение от Элд Хасп Посмотреть сообщение
Если не трудно - причины такого предпочтения?
Очень просто: первый вариант вытаскивает больше данных, чем вам реально нужно. Вы вытаскиваете сущность Dormitory со всеми её полями (не связанными данными, а собственными), но из неё берёте только коллекцию связанных комнат. Да ещё какой-то Copy() добавили неуместный.

Второй вариант сразу берёт только коллекцию комнат сущности Dormitory без её выгрузки. Намного более эффективная работа с данными.
0
Модератор
Эксперт .NET
15466 / 10712 / 2786
Регистрация: 21.04.2018
Сообщений: 31,531
Записей в блоге: 2
10.01.2020, 14:43  [ТС] 9
Цитата Сообщение от Usaga Посмотреть сообщение
Да ещё какой-то Copy() добавили неуместный.
Это временный метод для преобразования из типов EF в неизменяемый тип DTO.
Потом метод будет изменён.

Цитата Сообщение от Usaga Посмотреть сообщение
Второй вариант сразу берёт только коллекцию комнат сущности Dormitory без её выгрузки. Намного более эффективная работа с данными.
Ок!
А как быть при наличии связи "Многие ко многим"?

На словах опишу. Если будет непонятно - добавлю код.
Допустим, есть список инвентаря комнаты Inventories.
В списке инвентаря Inventory {ID=1, Name = "Люстра", Rooms = {....}}.
Мне надо получить все комнаты в которых есть этот инвентарь.

Если делать
C#
1
2
3
                return sdc.Rooms
                    .Where(x => x.Inventories.Contains(inventory ))
                    .ToImmutableArray();
Так не выйдет потому, что Inventory - ссылочный тип.
Надо делать так
C#
1
2
3
                return sdc.Rooms
                    .Where(x => x.Inventories.FirstOrDefault(inv => inv.ID == inventory.ID) != null)
                    .ToImmutableArray();
Это и слишком замудрёно.
И, по-моему, всё равно работать не будет, так как коллекции Inventories не загружены.
И придётся их все загружать.

Как правильно?
Или в каждом случае надо делать по разному?
0
Эксперт .NET
12079 / 8388 / 1281
Регистрация: 21.01.2016
Сообщений: 31,601
10.01.2020, 16:56 10
Лучший ответ Сообщение было отмечено Элд Хасп как решение

Решение

Цитата Сообщение от Элд Хасп Посмотреть сообщение
И, по-моему, всё равно работать не будет, так как коллекции Inventories не загружены.
А они и не должны быть загружены. Методы-расширения интерфейса IQueryable (это те, которые идут после sdc.Rooms) принимают на вход Expression. EF анализирует эти Expression и на основе этого анализа строит SQL-запрос. В эти Expression можно передавать и IQueryable от других DbSet<T>. EF это поймёт и сможет построить весьма сложный SQL-запрос.

С многие-ко-многим EF 6 дружит очень хорошо. Может работать даже без введения промежуточной сущсности-связи.

C#
1
2
3
return sdc.Rooms
    .Where(x => x.Inventories.Any(y => y.Name == "Люста"))
    .ToList();
1
10.01.2020, 16:56
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
10.01.2020, 16:56
Помогаю со студенческими работами здесь

Поиск в документе Word и получение информации из окна навигации
Добрый день! Возникла необходимость в получении информации из окна НАВИГАЦИИ WORD (скрин), должно...

Получение записей связанных с таблицей из другой таблицы
Доброе время суток! Имеется энтити, поле которой указывает на объекты (один ко многим) другого...

Получение коллекции из бд sqlite
Доброго времени суток. Есть класс бд: package ru.myscanner.scannerth; import...

Получение длинной строки из SQLite
Здравствуйте. Делаю проект на Unity, но работа идет с библиотекой Mono, поэтому пишу в данный...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
10
Ответ Создать тему
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru