Jump to content

Malboro

Members
  • Posts

    60
  • Joined

  • Last visited

  • Days Won

    2

Posts posted by Malboro

  1. На работе у меня часто бывает время для занятия какой-то херней (а хули "совок") и я  могу иногда потратить его на что-то полезное.  Предлагайте темы(С#, HTML, CSS, JS, Vue, php...)

  2. Всем привет!!

    Увидел тутер на английском по Entity Framework на MySql и решил сообразить свой вариант на Великом и Могучем, только для базы данных Postgresql. Надеюсь это кому-то будет полезным. Работать мне еще 3 часа, а значит должен успеть.

    Поехали

    Нам потребуется:

    • Visual Studio
    • Голова
    • Руки 
    • Немного времени

    Установка необходимых библиотек

         Первое что нам нужно сделать это открыть наш Visual Studio и загрузить необходимые библиотеки. Для этого открываем диспетчер пакетов NuGet, ищем и устанавливаем следующие библиотеки:

    • gtanetwork.api
    • Microsoft.EntityFrameworkCore
    • Microsoft.EntityFrameworkCore.Tools
    • Npgsql
    • Npgsql.EntityFrameworkCore.PostgreSQL
    • Newtonsoft.Json

    nuget-libs.png

         Вот общая картина:

    Screenshot-7.png

    Создание структуры классов

     Создадим следующие классы:

    • Character  - основной класс, модель которого мы и будем сохранять в базе
    •  Finances - будет хранить в себе информацию о финансах нашего игрока. 
    • States - будет содержать информацию о состоянии персонажа
    • Item - будет представлять класс вещи из нашего инвентаря
    • Login - отвечающий за авторизацию персонажа
    • Registration - ... за его регистрацию
    • Db - будет хранить подключение к базе данных
    • AppDbContext - основной класс который и будет содержать практически все настройки для нашей базы данных

         Посмотреть более подробно содержимое можно скачав проект с  репозитория на github.

         Для тех кто в теме:

    git clone https://github.com/SirEleot/EFCore_Npgsql.git

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

        class AppDbContext : DbContext
        {
    
            //сторка подключения к бд.
            private static string ConnectionString = "Host=localhost;Port=5432;Database=test;Uid=test;Password=test;";
    
            //настройка подключения к базе данных
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseNpgsql(ConnectionString);
            }
            //Добавляем класс для сохранения в дб
            //дочерние классы добавлять не нужно EFCore сам их подхватит
            public DbSet<Character> Characters { get; set; }
    
            // тут находится конфигурация наших сохроняемых классов
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                //настроим игнорирование свойства Data  из класса Character
                //вот так игнорируются свойства класса
                modelBuilder.Entity<Character>().Ignore(c => c.Date);
    
                //также добавим будем игнорировать класс Item 
                //так как он входит в состав нашего класса, то EFCore попытается для него создать таблицу
                //мы же решили что будем сохраять инвентарь в виде json строки
                //вот так игнорируются классы
                modelBuilder.Ignore<Item>();
    
                //здесь здесь пример того как обработать данные при сохранении
                //мы будем преобразовывать массив с вещамивв строку json 
                modelBuilder
                   .Entity<Character>()//выбираем объект из контекста у нас он 1
                   .Property(c => c.Inventory)//выбираем свойство 
                   .HasConversion( //определяем кастомные методы для обработки данных
                       i => JsonConvert.SerializeObject(i), // при сохранении
                       i => JsonConvert.DeserializeObject<List<Item>>(i) // при загрузке
                   );
    
                //рассмотрим как сохранить отдельный объект в нашем случае Finance
                //в одной таблице с основным классом
                modelBuilder.Entity<Character>()//выбираем объект из контекста у нас он 1
                    .OwnsOne(c => c.Finance);//определяем свойство которое мы хотим добавить к текущей таблице в базе дбазе данных    }
    
            }
        }

       Настройка базы данных

     Думаю что с ConnectionString проблемы возникнуть не должно.  Хотя :

    • Host=localhost;  - расположение базы данных
    • Port=5432; - прослушиваемый порт
    • Database=test; - название базы данных
    • Uid=test; - имя пользователя 
    • Password=test; - и соответственно пароль

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

        Давайте разберем содержимое нашего основного класса:

        class Character
        {
            //для элементов хранящихся в отдельных таблицах обязательно наличие поля с атрибутом primary key
            //но в EFCore достаточно создать для класса свойство  Id и он сделает все за вас
            public int Id { get; set; }
            public string Social { get; set; }
            public string Name { get; set; }
            public string Lasname { get; set; }
            public string Password { get; set; }
    
            // поместим финансовую модель в одну таблицу с персонажем 
            //состояние персонажа будет автоматом помещено в отдельную таблицу
            //все настройки производятся в классе AppDbContext 
            public Finances Finance { get; set; }
            public States State { get; set; }
    
            //так же мы сохраним список вещей в основную таблицу персонажа в виде строки JSON
            //настройка так же в классе AppDbContext 
            public List<Item> Inventory { get; set; }
    
            //это свойство создано для примера, мы его будем игнорировать при сохранении данных в базу
            //не поверите но это тоже настроим в  классе AppDbContext 
            public DateTime Date { get; set; }
        }

         Тут мы видим различные свойства персонажа: логин, имя, фамилия и пароль (не забывайте шифровать пароль при сохранении). Стоит обратить внимание на обязательный параметр Id, он необходим для корректного сохранения в базе данных. Все классы, которые предполагают сохранение в отдельной таблице, должны содержать свойство Id, либо любое другое предназначенное для хранения уникального ключа(но это другая история).

        Перейдем непосредственно к настройкам. Для начала нужно оповестить EF Core о том что мы собираемся сохранить данный класс, для этого добавим свойство Characters типа DbSet<T>, где Т - класс нашей модели Character

    public DbSet<Character> Characters { get; set; }

     

         Составим небольшой план действий. Допустим, что мы хотим чтобы информация о финансах находилась в одной таблице с нашим персонажем, а его состояние наоборот было вынесено в отдельную таблицу. Так же мы хотим что бы коллекция Inventory  хранилась в виде строки Json. Еще у нас есть свойство Date и мы не хотим сохранять его в базе данных.

         План "накидали", теперь давайте попробуем воплотить его в код. Итак по порядку:

    Первым рассмотрим включаемый класс Finances:

        class Finances
        {
            //для элементов сохраняющихся в оттдельных таблицах обязательно поле с primary key
            //Достаточно создать свойство  Id и EFCore сделает все за вас
            //В конкретном случае мы не добавляем свойство Id так как Finance будет помещен в одну таблицу с Character
            //смотрите настройки в классе AppDbContext
            //public int Id { get; set; }
            public int Bank { get; set; } = 5000;
            public int Cash { get; set; } = 500;
    
            //добавить деньги на счет
            public bool AddBank(int amount)
            {
                Bank += amount;
                return true;
            }
    
            //списать деньги со счета
            public bool SubBank(int amount)
            {
                if (amount > Bank) return false;
                Bank -= amount;
                return true;
            }
            //......
        }

         Наш класс содержит информацию о счете игрока, а так же методы взаимодействия со счетом. Наличие методов никак не повлияет на корректность сохраняемых данных. Мы решили что класс Finances будет сохранятся в одной таблице с персонажем, а это значит что свойство Id можно опустить.

          Настройки записываются в переопределенном методе OnModelCreating класса AppDbContext:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
    	modelBuilder. .......
    }

         Перейдем непосредственно к настройке Finance:

    modelBuilder.Entity<Character>()//выбираем объект из контекста у нас он 1
         .OwnsOne(c => c.Finance);//определяем свойство которое мы хотим добавить к текущей таблице в базе дбазе данных

    тут мы при помощи метода OwnsOne(c => c.Finance) говорим EF Core, что свойство Finance следует сохранять в одной таблице с Character.

    Screenshot-15-1.png

         Обратите внимание на 6 и 7 колонки это и есть свойства нашего класса Finances включенного в состав таблицы Characters. Так же обратите внимание на 1 колонку Id с пометкой [PK], это и есть наше обязательное одноименное свойство.

         Дальше у нас идет класс States:

        class States
        {
            //для элементов сохраняющихся в отдельных таблицах обязательно поле с primary key
            //Достаточно создать свойство  Id и EFCore сделает все за вас
            public int Id { get; set; }
            public int Health { get; set; } = 100;
            public int Armor { get; set; } = 100;
        }

         Ef Core, по умолчанию, пытается создать отдельную таблицу для каждого класса, включенного в состав нашего основного объекта, поэтому нам остается только создать уникальное свойство Id.

    Screenshot-16.png

         Для нашего класса States была автоматически создана соответствующая таблица в базе данных, а ссылка на нее была добавлена в основную таблицу в колонку 8 с названием StateId

         Далее давайте рассмотрим пользовательские преобразование данных. У нас есть свойство Inventory которое является коллекцией классов Item, и мы решили сохраить его в виде строки json. Для этого в метод OnModelCreating класса AppDbContext добавим следующие строки кода:

     modelBuilder
    	.Entity<Character>()//выбираем объект из контекста у нас он 1
    	.Property(c => c.Inventory)//выбираем свойство 
    	.HasConversion( //определяем кастомные методы для обработки данных
    		i => JsonConvert.SerializeObject(i), // при сохранении
    		i => JsonConvert.DeserializeObject<List<Item>>(i) // при загрузке
    	);

      За это отвечает метод HasConversion(),  где первым параметром передается метод обработки данных при сохранении, а вторым при загрузке. В нашем случае это будет сериализация коллекции в строку при сохранении и обратно при загрузке. Для примера мы использовали методы из библиотеки Newtonsoft.Json

    Screenshot-15-1.png

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

         Последним пунктом по настройке базы данных у нас будет исключение ненужных нам свойства Date, и класса Item.  Так как класс Item включен в состав нашего основного класса Character, то EF Core попытается создать для него отдельную таблицу, а она нам не нужна.  Тут все просто:

    modelBuilder.Ignore<Item>();

         Вот так мы добавляем класс Item в список игнорируемых классов

     modelBuilder.Entity<Character>().Ignore(c => c.Date);

          А вот так Свойство Date класса Character

         Так выглядит общая структура созданной нами базы данных:

    Screenshot-12.png

     

    Миграции базы данных

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

      Для начала нам нужно отобразить окно с названием "Консоль диспетчера пакетов" для этого жмякаем : Вид -> Другие окна -> Консоль диспетчера пакетов

    Screenshot-8.png

     

         В левом нижнем углу появится окно с названием, как ни странно "Консоль диспетчера пакетов". Давайте там пропишем команду для создания миграции: add-migration test_001

     где add-migration это сама команда а test_001 это название будущей миграции и нажмем Enter

    Screenshot-9.png

         Если мы все сделали правильно, мы должны увидеть сообщение следующего характера повествующее нам о том, что отменить это действие можно введя  в консоли команду  remove-migration:

    Screenshot-10.png

         В проекте будет создана папка с файлами миграции

    Screenshot-2.png

    Далее, чтобы развернуть саму базу данных введем команду update-database. Если строка подключения настроена корректна и база данных доступна вы увидите следующее сообщение об успешной миграции:

    Screenshot-3.png

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

    Screenshot-12.png

          Дальнейшая работа с миграциями отличается больше чем никак: При изменении структуры класса мы создаем новую миграцию при помощи команды add-migration изменяя только имя самой миграции test_002 к примеру. Имя может быть произвольным, но уникальным. Для обновления базы введите команду update-database . В папке  Migrations будет вестись история ваших миграций и вы в любой момент сможете откатить базу данных к любому этапу, используя команду update-database с именем миграции до которой нужно произвести откат изменений. Например последняя миграция у нас test_002 а нам нужно откатить до версии test_001, для этого мы должны ввести команду: update-database test_001

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

     

    Работа с базой данных

    Сохранение

         Для того чтобы добавить данные о персонаже в базу, нам нужно создать экземпляр нашего класса и добавить его в контекст при помощи метода Add(), после чего нужно сохранить все изменения из контекста данных непосредственно в базу при помощи метода SaveChanges()

            public void OnRegistration(Client client, string name, string lastname, string password) {
    
                //не забываем про хеширование пароля перед сохранением в бд
                //создаем нового персонажа 
                Character Char = new Character
                {
                    Social = client.SocialClubName,
                    Name = name,
                    Lasname = lastname,
                    Password = password,
                    Finance = new Finances(),
                    State = new States(),
                    Inventory = new List<Item>(),
                    Date = DateTime.Now
                };
    
                //добавляем его в контекст данных
                Db.Instance.Add(Char);
    
                    //сохранянем изменения в базе данных
                Db.Instance.SaveChanges();
            }

         Для обновления данных нужно воспользоваться методом Update() для обновления данных о персонаже, и так же зафиксировать их при помощи метода SaveChanges()

    //обновляем данные в контексте
    Db.Instance.Update(Char);
    //и сохраняем в бд
    Db.Instance.SaveChanges();

    Загрузка

         С загрузкой дела немного обстоят по другому. Для начала нам не нужны данные находящиеся в другой таблице, будь то состояние персонажа в нашем примере, его кастомизация или что-то еще. Нам будет достаточно информации о его логине и пароле. Рассмотрим пример загрузки данных из бд:

     

            public void OnRegistration(Client client, string pwd)
            {
    
                //Получаем персонажа из бд 
                Character Char = Db.Instance.Characters.SingleOrDefault(c=>c.Social == client.SocialClubName);
                //если записи соответствующей кретерию нашего запроса нет вернется Null
                if (Char == null) return;
                //проверяем пароль на совпадение (не забываем про хеш)
                if (Char.Password == pwd)
                {
                    //подгружаем зависимые классы вынексеные в отдельную таблицу
                    Db.Instance.Entry(Char).Reference(c => c.State).Load();
                    //создаем ссылку на нашу модель игрока
                    client.SetData("Character", Char);
                    //загружаем игрока
                    //..............
                }
                else
                {
                    //если не прошел проверку отправляем на повторный логин
                    //................
                }
                
            }

         В этом методе, в первую очередь, мы получаем первое значение соответствующее критерию нашего запроса и возвращаем экземпляр объекта Character со свойствами взятыми из бд. Свойство State, хранящееся в отдельно таблице, будет иметь значение null,его нужно будет явно запросить из базы, но на данном этапе нам достаточно данных чтобы сверить полученный от клиента пароль с паролем из бд (не забывайте про шифрование паролей). Если ни одна одна запись не будет соответствовать  критерию нашего запроса вернется Null, это скажет нам о том что пользователя с данным логином не существует. 

        Если значение пароля совпадает то пришло время подгрузить недостающие данные. Делаем это мы при помощи метода Load() для необходимого свойства - в нашем случае это State

     Db.Instance.Entry(Char).Reference(c => c.State).Load();

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

     

    На этом думаю закончу, если что-то непонятно пишите, постараюсь дополнить. А я  устал - я ухожу...

    • Like 2
  3. Всем привет!!

    Увидел тутер на английском по Entity Framework на MySql и решил сообразить свой вариант на Великом и Могучем, только для базы данных Postgresql. Надеюсь это кому-то будет полезным. Работать мне еще 3 часа, а значит должен успеть.

    Поехали

    Нам потребуется:

    • Visual Studio
    • Голова
    • Руки 
    • Немного времени

    Установка необходимых библиотек

         Первое что нам нужно сделать это открыть наш Visual Studio и загрузить необходимые библиотеки. Для этого открываем диспетчер пакетов NuGet, ищем и устанавливаем следующие библиотеки:

    • gtanetwork.api
    • Microsoft.EntityFrameworkCore
    • Microsoft.EntityFrameworkCore.Tools
    • Npgsql
    • Npgsql.EntityFrameworkCore.PostgreSQL
    • Newtonsoft.Json

    nuget-libs.png

         Вот общая картина:

    Screenshot-7.png

     

    Создание структуры классов

     

     Создадим следующие классы:

    • Character  - основной класс, модель которого мы и будем сохранять в базе
    •  Finances - будет хранить в себе информацию о финансах нашего игрока. 
    • States - будет содержать информацию о состоянии персонажа
    • Item - будет представлять класс вещи из нашего инвентаря
    • Login - отвечающий за авторизацию персонажа
    • Registration - ... за его регистрацию
    • Db - будет хранить подключение к базе данных
    • AppDbContext - основной класс который и будет содержать практически все настройки для нашей базы данных

         Посмотреть более подробно содержимое можно скачав проект с  репозитория на github.

         Для тех кто в теме:

    git clone https://github.com/SirEleot/EFCore_Npgsql.git

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

        class AppDbContext : DbContext
        {
    
            //сторка подключения к бд.
            private static string ConnectionString = "Host=localhost;Port=5432;Database=test;Uid=test;Password=test;";
    
            //настройка подключения к базе данных
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder.UseNpgsql(ConnectionString);
            }
            //Добавляем класс для сохранения в дб
            //дочерние классы добавлять не нужно EFCore сам их подхватит
            public DbSet<Character> Characters { get; set; }
    
            // тут находится конфигурация наших сохроняемых классов
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                //настроим игнорирование свойства Data  из класса Character
                //вот так игнорируются свойства класса
                modelBuilder.Entity<Character>().Ignore(c => c.Date);
    
                //также добавим будем игнорировать класс Item 
                //так как он входит в состав нашего класса, то EFCore попытается для него создать таблицу
                //мы же решили что будем сохраять инвентарь в виде json строки
                //вот так игнорируются классы
                modelBuilder.Ignore<Item>();
    
                //здесь здесь пример того как обработать данные при сохранении
                //мы будем преобразовывать массив с вещамивв строку json 
                modelBuilder
                   .Entity<Character>()//выбираем объект из контекста у нас он 1
                   .Property(c => c.Inventory)//выбираем свойство 
                   .HasConversion( //определяем кастомные методы для обработки данных
                       i => JsonConvert.SerializeObject(i), // при сохранении
                       i => JsonConvert.DeserializeObject<List<Item>>(i) // при загрузке
                   );
    
                //рассмотрим как сохранить отдельный объект в нашем случае Finance
                //в одной таблице с основным классом
                modelBuilder.Entity<Character>()//выбираем объект из контекста у нас он 1
                    .OwnsOne(c => c.Finance);//определяем свойство которое мы хотим добавить к текущей таблице в базе дбазе данных    }
    
            }
        }

       

    Настройка базы данных

     

     Думаю что с ConnectionString проблемы возникнуть не должно.  Хотя :

    • Host=localhost;  - расположение базы данных
    • Port=5432; - прослушиваемый порт
    • Database=test; - название базы данных
    • Uid=test; - имя пользователя 
    • Password=test; - и соответственно пароль

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

        Давайте разберем содержимое нашего основного класса:

        class Character
        {
            //для элементов хранящихся в отдельных таблицах обязательно наличие поля с атрибутом primary key
            //но в EFCore достаточно создать для класса свойство  Id и он сделает все за вас
            public int Id { get; set; }
            public string Social { get; set; }
            public string Name { get; set; }
            public string Lasname { get; set; }
            public string Password { get; set; }
    
            // поместим финансовую модель в одну таблицу с персонажем 
            //состояние персонажа будет автоматом помещено в отдельную таблицу
            //все настройки производятся в классе AppDbContext 
            public Finances Finance { get; set; }
            public States State { get; set; }
    
            //так же мы сохраним список вещей в основную таблицу персонажа в виде строки JSON
            //настройка так же в классе AppDbContext 
            public List<Item> Inventory { get; set; }
    
            //это свойство создано для примера, мы его будем игнорировать при сохранении данных в базу
            //не поверите но это тоже настроим в  классе AppDbContext 
            public DateTime Date { get; set; }
        }

         Тут мы видим различные свойства персонажа: логин, имя, фамилия и пароль (не забывайте шифровать пароль при сохранении). Стоит обратить внимание на обязательный параметр Id, он необходим для корректного сохранения в базе данных. Все классы, которые предполагают сохранение в отдельной таблице, должны содержать свойство Id, либо любое другое предназначенное для хранения уникального ключа(но это другая история).

        Перейдем непосредственно к настройкам. Для начала нужно оповестить EF Core о том что мы собираемся сохранить данный класс, для этого добавим свойство Characters типа DbSet<T>, где Т - класс нашей модели Character

    public DbSet<Character> Characters { get; set; }

     

         Составим небольшой план действий. Допустим, что мы хотим чтобы информация о финансах находилась в одной таблице с нашим персонажем, а его состояние наоборот было вынесено в отдельную таблицу. Так же мы хотим что бы коллекция Inventory  хранилась в виде строки Json. Еще у нас есть свойство Date и мы не хотим сохранять его в базе данных.

         План "накидали", теперь давайте попробуем воплотить его в код. Итак по порядку:

    Первым рассмотрим включаемый класс Finances:

        class Finances
        {
            //для элементов сохраняющихся в оттдельных таблицах обязательно поле с primary key
            //Достаточно создать свойство  Id и EFCore сделает все за вас
            //В конкретном случае мы не добавляем свойство Id так как Finance будет помещен в одну таблицу с Character
            //смотрите настройки в классе AppDbContext
            //public int Id { get; set; }
            public int Bank { get; set; } = 5000;
            public int Cash { get; set; } = 500;
    
            //добавить деньги на счет
            public bool AddBank(int amount)
            {
                Bank += amount;
                return true;
            }
    
            //списать деньги со счета
            public bool SubBank(int amount)
            {
                if (amount > Bank) return false;
                Bank -= amount;
                return true;
            }
            //......
        }

         Наш класс содержит информацию о счете игрока, а так же методы взаимодействия со счетом. Наличие методов никак не повлияет на корректность сохраняемых данных. Мы решили что класс Finances будет сохранятся в одной таблице с персонажем, а это значит что свойство Id можно опустить.

          Настройки записываются в переопределенном методе OnModelCreating класса AppDbContext:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
    	modelBuilder. .......
    }

         Перейдем непосредственно к настройке Finance:

    modelBuilder.Entity<Character>()//выбираем объект из контекста у нас он 1
         .OwnsOne(c => c.Finance);//определяем свойство которое мы хотим добавить к текущей таблице в базе дбазе данных

    тут мы при помощи метода OwnsOne(c => c.Finance) говорим EF Core, что свойство Finance следует сохранять в одной таблице с Character.

    Screenshot-15-1.png

         Обратите внимание на 6 и 7 колонки это и есть свойства нашего класса Finances включенного в состав таблицы Characters. Так же обратите внимание на 1 колонку Id с пометкой [PK], это и есть наше обязательное одноименное свойство.

         Дальше у нас идет класс States:

        class States
        {
            //для элементов сохраняющихся в отдельных таблицах обязательно поле с primary key
            //Достаточно создать свойство  Id и EFCore сделает все за вас
            public int Id { get; set; }
            public int Health { get; set; } = 100;
            public int Armor { get; set; } = 100;
        }

         Ef Core, по умолчанию, пытается создать отдельную таблицу для каждого класса, включенного в состав нашего основного объекта, поэтому нам остается только создать уникальное свойство Id.

    Screenshot-16.png

         Для нашего класса States была автоматически создана соответствующая таблица в базе данных, а ссылка на нее была добавлена в основную таблицу в колонку 8 с названием StateId

         Далее давайте рассмотрим пользовательские преобразование данных. У нас есть свойство Inventory которое является коллекцией классов Item, и мы решили сохраить его в виде строки json. Для этого в метод OnModelCreating класса AppDbContext добавим следующие строки кода:

     modelBuilder
    	.Entity<Character>()//выбираем объект из контекста у нас он 1
    	.Property(c => c.Inventory)//выбираем свойство 
    	.HasConversion( //определяем кастомные методы для обработки данных
    		i => JsonConvert.SerializeObject(i), // при сохранении
    		i => JsonConvert.DeserializeObject<List<Item>>(i) // при загрузке
    	);

      За это отвечает метод HasConversion(),  где первым параметром передается метод обработки данных при сохранении, а вторым при загрузке. В нашем случае это будет сериализация коллекции в строку при сохранении и обратно при загрузке. Для примера мы использовали методы из библиотеки Newtonsoft.Json

    Screenshot-15-1.png

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

         Последним пунктом по настройке базы данных у нас будет исключение ненужных нам свойства Date, и класса Item.  Так как класс Item включен в состав нашего основного класса Character, то EF Core попытается создать для него отдельную таблицу, а она нам не нужна.  Тут все просто:

    modelBuilder.Ignore<Item>();

         Вот так мы добавляем класс Item в список игнорируемых классов

     modelBuilder.Entity<Character>().Ignore(c => c.Date);

          А вот так Свойство Date класса Character

         Так выглядит общая структура созданной нами базы данных:

    Screenshot-12.png

     

    Миграции базы данных

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

      Для начала нам нужно отобразить окно с названием "Консоль диспетчера пакетов" для этого жмякаем : Вид -> Другие окна -> Консоль диспетчера пакетов

    Screenshot-8.png

     

         В левом нижнем углу появится окно с названием, как ни странно "Консоль диспетчера пакетов". Давайте там пропишем команду для создания миграции: add-migration test_001

     где add-migration это сама команда а test_001 это название будущей миграции и нажмем Enter

    Screenshot-9.png

         Если мы все сделали правильно, мы должны увидеть сообщение следующего характера повествующее нам о том, что отменить это действие можно введя  в консоли команду  remove-migration:

    Screenshot-10.png

         В проекте будет создана папка с файлами миграции

    Screenshot-2.png

    Далее, чтобы развернуть саму базу данных введем команду update-database. Если строка подключения настроена корректна и база данных доступна вы увидите следующее сообщение об успешной миграции:

    Screenshot-3.png

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

    Screenshot-12.png

          Дальнейшая работа с миграциями отличается больше чем никак: При изменении структуры класса мы создаем новую миграцию при помощи команды add-migration изменяя только имя самой миграции test_002 к примеру. Имя может быть произвольным, но уникальным. Для обновления базы введите команду update-database . В папке  Migrations будет вестись история ваших миграций и вы в любой момент сможете откатить базу данных к любому этапу, используя команду update-database с именем миграции до которой нужно произвести откат изменений. Например последняя миграция у нас test_002 а нам нужно откатить до версии test_001, для этого мы должны ввести команду: update-database test_001

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

     

    Работа с базой данных

    Сохранение

         Для того чтобы добавить данные о персонаже в базу, нам нужно создать экземпляр нашего класса и добавить его в контекст при помощи метода Add(), после чего нужно сохранить все изменения из контекста данных непосредственно в базу при помощи метода SaveChanges()

            public void OnRegistration(Client client, string name, string lastname, string password) {
    
                //не забываем про хеширование пароля перед сохранением в бд
                //создаем нового персонажа 
                Character Char = new Character
                {
                    Social = client.SocialClubName,
                    Name = name,
                    Lasname = lastname,
                    Password = password,
                    Finance = new Finances(),
                    State = new States(),
                    Inventory = new List<Item>(),
                    Date = DateTime.Now
                };
    
                //добавляем его в контекст данных
                Db.Instance.Add(Char);
    
                    //сохранянем изменения в базе данных
                Db.Instance.SaveChanges();
            }

         Для обновления данных нужно воспользоваться методом Update() для обновления данных о персонаже, и так же зафиксировать их при помощи метода SaveChanges()

    //обновляем данные в контексте
    Db.Instance.Update(Char);
    //и сохраняем в бд
    Db.Instance.SaveChanges();

    Загрузка

         С загрузкой дела немного обстоят по другому. Для начала нам не нужны данные находящиеся в другой таблице, будь то состояние персонажа в нашем примере, его кастомизация или что-то еще. Нам будет достаточно информации о его логине и пароле. Рассмотрим пример загрузки данных из бд:

     

            public void OnRegistration(Client client, string pwd)
            {
    
                //Получаем персонажа из бд 
                Character Char = Db.Instance.Characters.SingleOrDefault(c=>c.Social == client.SocialClubName);
                //если записи соответствующей кретерию нашего запроса нет вернется Null
                if (Char == null) return;
                //проверяем пароль на совпадение (не забываем про хеш)
                if (Char.Password == pwd)
                {
                    //подгружаем зависимые классы вынексеные в отдельную таблицу
                    Db.Instance.Entry(Char).Reference(c => c.State).Load();
                    //создаем ссылку на нашу модель игрока
                    client.SetData("Character", Char);
                    //загружаем игрока
                    //..............
                }
                else
                {
                    //если не прошел проверку отправляем на повторный логин
                    //................
                }
                
            }

         В этом методе, в первую очередь, мы получаем первое значение соответствующее критерию нашего запроса и возвращаем экземпляр объекта Character со свойствами взятыми из бд. Свойство State, хранящееся в отдельно таблице, будет иметь значение null,его нужно будет явно запросить из базы, но на данном этапе нам достаточно данных чтобы сверить полученный от клиента пароль с паролем из бд (не забывайте про шифрование паролей). Если ни одна одна запись не будет соответствовать  критерию нашего запроса вернется Null, это скажет нам о том что пользователя с данным логином не существует. 

        Если значение пароля совпадает то пришло время подгрузить недостающие данные. Делаем это мы при помощи метода Load() для необходимого свойства - в нашем случае это State

     Db.Instance.Entry(Char).Reference(c => c.State).Load();

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

     

    На этом думаю закончу, если что-то непонятно пишите, постараюсь дополнить. А я  устал - я ухожу...

    • Like 6
    • Take2 1
  4. Если актуально:

     Оградить игроков при помощи свойства dimension :

    https://wiki.rage.mp/index.php?title=Entity::dimension 

    Интерьеры некоторые нуждаются в загрузке:

    https://wiki.rage.mp/index.php?title=Interiors_and_Locations

     

    • Like 2
×
×
  • Create New...