Это классический BFS, который гарантированно обходит все вершины связного неориентированного графа в порядке увеличения расстояния от стартовой вершины.
Не понимаю зачем использовать не официальные некачественные библиотеки. Это лишняя обертка, медленная и много потенциальных проблем.
Альтернатива
1. Через python-telegram-bot (telegram)
Это официальная библиотека от команды Telegram, простая и удобная.
from telegram import Update
from telegram.ext import Updater, CommandHandler, CallbackContext
def start(update: Update, context: CallbackContext):
update.message.reply_text("Привет! Я бот.")
updater = Updater("TOKEN")
updater.dispatcher.add_handler(CommandHandler("start", start))
updater.start_polling()
updater.idle()
2. Через requests (Raw API)
Telegram Bot API — это HTTP-интерфейс, и можно работать с ним напрямую.
import requests
TOKEN = "YOUR_BOT_TOKEN"
BASE_URL = f"https://api.telegram.org/bot{TOKEN}"
def send_message(chat_id, text):
requests.post(f"{BASE_URL}/sendMessage", json={"chat_id": chat_id, "text": text})
# Пример обработки входящих сообщений (например, через вебхук)
3. Другие библиотеки
pyTelegramBotAPI (telebot) – простая и популярная альтернатива.
3. Другие библиотеки
pyTelegramBotAPI (telebot) – простая и популярная альтернатива.
Алгоритм поиска в ширину (Breadth-First Search, BFS) используется для поиска или обхода узлов в графе или дереве. BFS начинает с корневого узла и исследует соседние узлы на каждом уровне, прежде чем переходить к узлам следующего уровня.
Ниже приведен простой пример реализации алгоритма BFS на C#. В этом примере мы будем использовать неориентированный граф, представленный в виде списка смежности.
Пример реализации BFS на C#
using System;
using System.Collections.Generic;
class Program
{
static void Main()
{
// Создаем граф как словарь списков смежности (adjacency list)
Dictionary<int, List<int>> graph = new Dictionary<int, List<int>>()
{
{0, new List<int> {1, 2}},
{1, new List<int> {0, 3, 4}},
{2, new List<int> {0, 5, 6}},
{3, new List<int> {1}},
{4, new List<int> {1}},
{5, new List<int> {2}},
{6, new List<int> {2}}
};
// Вызываем BFS начиная с узла 0
BFS(graph, 0);
}
static void BFS(Dictionary<int, List<int>> graph, int startNode)
{
// Очередь для хранения узлов, которые нужно посетить
Queue<int> queue = new Queue<int>();
// Хэш-набор для отслеживания посещенных узлов
HashSet<int> visited = new HashSet<int>();
// Начинаем с добавления начального узла в очередь и помечаем его как посещенный
queue.Enqueue(startNode);
visited.Add(startNode);
while (queue.Count > 0)
{
// Извлекаем узел из очереди
int currentNode = queue.Dequeue();
Console.WriteLine("Посещаем узел: " + currentNode);
// Проходим по всем соседям текущего узла
foreach (int neighbor in graph[currentNode])
{
if (!visited.Contains(neighbor))
{
// Если сосед не был посещен, добавляем его в очередь и помечаем как посещенный
queue.Enqueue(neighbor);
visited.Add(neighbor);
}
}
}
}
}
Объяснение кода
1. Граф как словарь списков смежности: Граф представлен как словарь, где ключом является узел, а значением — список соседних узлов. Например, узел 0 имеет соседей 1 и 2.
2. Функция BFS:
Принимает граф и начальный узел.
Использует очередь (Queue<int>) для хранения узлов, которые нужно посетить, и множество (HashSet<int>) для отслеживания посещенных узлов.
Начинает с добавления начального узла в очередь и помечает его как посещенный.
Пока очередь не пуста, узел извлекается из очереди, и его соседи проверяются. Если соседний узел еще не был посещен, он добавляется в очередь и помечается как посещенный.
3. Запуск программы:
Программа начинает обход графа с узла 0 и посещает все узлы в графе в порядке BFS.
В общем, задача протестировать MVP (minimum viable product), это специальный подход в it индустрии, когда делают простой проект быстро, тестируют на аудитории, получают результат, а дальше принимается решение расти или нет.
У нас была такая же мысль с MVP. Но где разворачивать сайт? Хотели использовать яндекс облако, но они откровенно не дешевые для MVP, в принципе есть бесплатный период теста, но опять же, крутить сайт, пусть даже за 5 тыс руб. в месяц без единого дохода так себе.
Решение — держать сайт на ноутбуке. Я считаю такой подход в разы лучше. Т.к. мы получаем больше ресурсов чем минимальный тариф в облаке. Более того такой вариант можно недорого масштабировать, конечно если вы готовы возиться с железками. Либо масштабировать до логического конца, а потом перейти в облако.
Нужно ли мне отчитываться перед государством, если я подниму сайт дома?
Скажу честно, не знаю откуда взялась эта довольно идиотская байка. Для государства нет никакой разницы арендуете ли вы сайт в облаке или развернули у себя. Если есть заработок, платите налоги — всё, никаких претензий.
Настройка ноутбука под сайт
Что необходимо:
Более менее нормальный интернет (я давно использую 300 Мбит/с)
Статический адрес (арендовать у провайдера за 150 руб. в месяц)
Старый ноутбук.
Домашний роутер (лучше гигабитный)
Статический адрес
Статический IP адрес — по сути адрес вашего домашнего/офисного интернет оборудования для общения с вашим провайдером. При первом подключении к провайдеру, провайдер не выдает индивидуальный IP адрес, он формируется динамически, время от времени менятся. Поэтому если настроить сайт на такой адрес, то после изменений ваш сайт просто не найдут. Но в сети есть варианты как настроить бесплатный статический адрес с хитростями. Но мне было лень это изучать и как-то не по-взрослому, ты зависишь от каких-то сторонних сервисов и о надежности не приходиться говорить. Мне проще заплатить 150 рублей в месяц.
Настройка статического адреса
Небольшая вводная история. Я еще года 2 — 3 назад хотел настроить себе статич. адрес для игры в CS 1.6 🙂 В общем думаю — “ну фигня вопрос!”. Заказываю статический IP адрес, как вы думаете у кого, у ростелекома. Ага, значит пытаюсь настроить, всё делаю по феншую, но никак не могу запустить. Звоню узнать — аХа, щас. Ихние спецы — полный ноль. Звонил раз 5. Короче “выдающиеся” ничем мне не помогли, переписывался даже в VK, к сожалению не могу приложить переписку, удалил от злости и заблокировал их. Короче, начал сомневаться в своих когнитивных способностях. Как это обычно бывает, решил прибегнуть к кардинальному решению — сменить к чертям «надежного» провайдера. В итоге всё заработало с первого раза.
В принципе роутер настраивается просто, сами интерфейсы роутера сильно разные, поэтому я просто опишу куда смотреть на примере tp-link.
1. Вам нужно добавить статический адрес, который либо выдает оператор в договоре либо можно посмотреть в личном кабинете провайдера или просто увидеть его через myIp.ru
2. Далее указываем внутри вашего роутера, куда переадресовываться трафик. В моём случае “Тип сервер” произвольное название. “Внешний порт” — это как раз потр для сайта, обычно это 80, указываем его. А вот для CS 1.6 порт по умолчанию = 27015. “Внутренний IP” — это IP вашей машины дома. Таким образом можно подключить любое количество компьютеров, которые будут “общаться” внутри только вашей сети, а внешний доступ иметь по единственному, дефолтному порту. Остальные настройки произвольные.
Внутренний IP машины на ms windows можно посмотреть через команду ipconfig.
3. Тут необязательные настройки. Чтобы внутренний адрес вашей машины не менялся произвольно, когда ему захочется. Привяжите MAC-адрес (уникальный адрес сетевого устройства, задается еще на заводе) своей машины и её текущий адрес
Поднимаем сайт на ноутбуке
Если у вас windows это делается очень просто. Всё что вам нужно это запустить веб-сервер (IIS). В windows он встроен. По-моему, его нет только в windows home basic. В русской версии это “IIS Службы”. Выберите Панель управления (Control Panel) > Программы и компоненты (Programs and Features) > Включение или отключение компонентов Windows (Turn Windows Features on or off). Разверните узел Службы IIS (Internet Information Services).
Нажмите окей, после некоторой загрузки откройте браузер и напишите в поисковой строке localhost или явно укажите порт 80 — localhost:80. Откроется тестовая страница. Поздравляю вы запустили сайт. С помощью IIS веб-сервера можно запускать сайты, например, на WordPress или много другое.
Мы же конечно используем более серьезный стек на сайте. Это Microsoft dotnet работает на linux-е. Кстати да, может вы всё еще не знаете, но Microsoft любит линукс. И уже с 2016-го года выпускают свой программный флагман dotnet под все платформы, т.е. кроссплатформенно. Для фронтенда у нас Angular последних версий. Также есть CI/CD на Azure DevOps, он бесплатный до 5 человек.
Если лень читать. Напишу сразу, используйте готовые решения, готовый дизайн такой как material design (https://material.io/develop/android). Его можно использовать как для мобильного приложения так и для веб-сайта.
Почему дизайн приложения — зло
Если вы решили сделать свое уникальное приложение вам необходимо посчитать цену и затраты. Вообще сделать дизайн относительно несложно, а визуального эффекта от него очень много. Как-то давно мой товарищ обратился в веб-студию заказать сайт, наподобие авто.ру (такой же сложный с технической точки зрения). Подписали договор и начали работу. Дизайн он получил очень быстро, ну казалось бы, эффектно и сделали быстро. Но когда дело дошло до технической реализации все затянулось на год и ничего не работало, как итог суд. Деньги отсудили назад. Поэтому дизайн легче и проще продать и тем более сделать. Его часто преподносят как уникальную и крайне важную часть вашего It проекта. Когда я слышу про: «аля уникальный дизайн приложения», «только вы и только для вас». Но по факту — это бессмысленная трата денег и времени. Пример дизайна из интернета — стандартные котролы для бизнеса, как видно, ничего уникального.
Чтобы не тянуть «яйца за кота» скажу сразу, что для дизайна бизнес приложения есть очень крутые, готовые котролы (можно сказать фреймворк дизайн, напримре material https://material.io/develop/android). Это можно сказать, дизайн от google. Но в любом случае, вы можете найти кучу готового дизайна. Просто скажите программисту: используй какой-нибудь готовый фреймворк дизайна, такой как — material design.
UX дизайн — не дизайн
Часто слышу такое словосочетание как «UX дизайн». UX (анг. user experience) если вкратце — это максимально удобное расположение которолов и максимально понятное использование. Но не внешний вид этих контролов. Например, уже все привыкли, что в операционной системе windows кнопочка «закрыть» всегда располагается в правом верхнем углу.
Если вы перейдете на Linux (Ubuntu) или MacOS, то будете сбиты столку тем, что кнопки «закрыть» в этих вариантах ОС слева.
Его тоже могут подсунуть вам как важную часть приложения. В принципе правильный UX очень важен. Но смотрите, все приложения более менее одинаковые в пользовательском смысле. Придерживайтесь стандартам и на первом этапе этого будет достаточно. И это правильно если вы купили BMW, а теперь передвигаетесь на Mercedes, то у вас не будет проблем с расположением руля.
Как сэкономить деньги при разработке приложения или сайта.
Как итог сэкономить деньги можно на дизайне и даже на UX дизайне. Не заказывайте дизайн вообще, уникальный дизайн приложению не нужен, а уникальное расположение котролов может даже вредить. Пользователи привыкли к одному, а вы им подсовываете другое.
В этой статье я покажу базовые навыки построения приложения в реальном времени используя SignalR.
Создаем веб-проект
Добавить клиентскую библиотеку SignalR
Добавить хаб SignalR
Сконфигурируем проект, чтобы запустить SignalR в классе Startup.
Добавим код, который отсылает сообщения клиентским приложениям.
Создаем веб-проект
В VisualStudio выберите File > New Project
В диалоговом окне «Create a new project» выберите «ASP.NET Core Web Application«, и нажмите Next.
В диалоговом окне «Configure your new project«, назовите проект, например «SignalRJavaScript«, и нажмите Create.
В диалоговом окне «Create a new ASP.NET Core web Application«, выберите .NET Core и ASP.NET Core 3.0.
Выберите Web Application чтобы создать проект на основе рейзор страниц (Razor Pages), и далее нажмите Create.
Добавить клиентскую библиотеку SignalR
Сама инфраструктура SignalR уже влючина в APS.NET Core 3.0. Но клиентской библиотеки нет. Поэтому запустите библиотечного менеджера (LibMan) и подгрузите библиотеку unpkg.
Перейдите на Обозреватель решений / Solution Explorer щелкните по проекту правой кнопкой и выберите Add > Client-Side Library.
В диалоге «Add Client-Side Library» в секции провайдер / Provider выберите «unpkg»
Чтобы подгрузить клиентскую библиотеку наберите название » @microsoft/signalr@latest«
Добавить хаб SignalR
Все классы унаследованые от класса Hub это высокоуровневая обертка над клиент-серверной комуникацией.
Добавим в корень проекта свой класс ChatHub.cs с кодом внутри.
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
namespace SignalRJavaScript
{
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
}
}
Сам класс Hub управляет соединениями, группами и сообщениями. Обратите внимание на метод SendMessage. Данный метод кастомный т.е. вы можете назвать его как угодно. Данный метод после компиляции появиться в JavaScript-е и из этого скрипта мы сможем вызвать его. Т.е. вызвать SendMessage на стороне клиента. В свою очередь метод SendAsync запускает событиеReceiveMessage к которому можно подписаться на стороне клиента.
Сконфигурируем проект, чтобы запустить SignalR в классе Startup.
Добавьте две строчки: 1. services.AddSignalR(); 2. endpoints.MapHub<ChatHub>(«/chatHub»);
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace SignalRJavaScript
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddSignalR(); <---------- 1.
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapHub<ChatHub>("/chatHub"); <---------- 2.
});
}
}
}
Добавим код, который отсылает сообщения клиентским приложениям.
В файл Pages/index.cshtml добавьте следующую разметкую
"use strict";
// Получим наш объект "соединение"
var connection = new signalR.HubConnectionBuilder().withUrl("/chatHub").build();
// Кнопка будет неактивная, до тех пор, пока нет соединения.
document.getElementById("sendButton").disabled = true;
// Навешиваем событие click на элемент с id = "sendButton"
// Т.е. когда мы кликаем по кнопке, то запускается наша функция обраного вызова.
document.getElementById("sendButton").addEventListener("click", function (event) {
// Берем тексты из контролов <input />
var user = document.getElementById("userInput").value;
var message = document.getElementById("messageInput").value;
// Тут черзе объект connection фактически запускаем метод "SendMessage" на сервере,
// который в свою очередь с сервера оповестит всех подписчиков методом "ReceiveMessage".
connection.invoke("SendMessage", user, message).catch(function (err) {
return console.error(err.toString());
});
event.preventDefault();
});
// Подписываемся к событию "ReceiveMessage".
// Данное событие запускается на сервере (в коде c# это метод "ReceiveMessage"), а js "слушает его"
connection.on("ReceiveMessage", function (user, message) {
// Как только событие отработает запуститься наша функция обратного вызова callback function
var msg = message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
var encodedMsg = user + " says " + msg;
var li = document.createElement("li");
li.textContent = encodedMsg;
document.getElementById("messagesList").appendChild(li);
});
// Запускаем соединение асинхронно.
connection.start().then(function () {
// Если соединение успешное установлено, то активируем кнопку
document.getElementById("sendButton").disabled = false;
}).catch(function (err) {
// Если соединение не удалось
return console.error(err.toString());
});
var connection = new signalR.HubConnectionBuilder().withUrl(«/chatHub»).build(); — получаем наш хаб.
connection.invoke(«SendMessage», user, message) — отправляем сообщение
connection.on(«ReceiveMessage», function(user, msg ){}) — слушаем событие ReceiveMessage, которое запускается на сервере.
При папытке работы с Yandex cloud postgresql. Постоянно получал ошибку » too many connections for role» или «The connection pool has been exhausted».
Причина
Терпеть немогу этот ваш ДевОпс. Так или иначе. Postgresql очень капризный или ленивый. После MS SQL сервера всё кажется излишним. Очень много вещей надо настраивать самому.
Приложения написанные на Python, PHP или в моем случае asp.net core. Часто создают одно или несколько подключений при запросе. Но стандартное количество соединений в пуле postgresql по умолчанию = 100. Это давольно оптимальное число для мелких и стредних приложений. А если вы арендуете сервер как я так это еще и выгодно. Но приложение ничего не знает про эту настройку, поэтому пытается создать больше 100. Чтобы обеспечить грамотный пулинг нужно испозоваться пул балансер, например, PgBouncer.
Лечение
Нужно установить PbBouncer между приложением и кластером. Оказывается в кластере Postgresql яндекс облака. Уже встроен балансер (PgBouncer).
Но не понятно как он работает и какой режим пулинга у него по умолчанию. Я думаю что SESSION, который не очень подходит для подобных приложений. К сожалению сейчас у них нет интерфейса для конфигурации этого балансера. Но можно воспользоваться команной сторокой YC (CLI).
yc managed-postgresql cluster get <CLUSTER NAME> --full
Вообще эта настройка у яндекс кластера просто дурацкая. Если есть балансер соединений, то почему я не могу увеличить соединения к PgBouncer-у? Это же блин PgBouncer в этом его суть!
Настройка Pooling Mode в TRANSACTION в Cloud.Yandex не помогло!
Тем не менее данная настройка не помогла. Как я уже писал встроенный PgBouncer в яндекс кластере не работает как PgBouncer т.е. вы не можете увелисить соединения.
У меня часто вылетало сообщение » The connection pool has been exhausted». Очевидно, что где-то утечкая соединений. Однако как найти. Для начала я попробовал посмотреть все ли соединнеия закрываються. В объекту соединения был добавлен дилегат.
private static NpgsqlConnection CreateConnection(string connectionString, ILogService logService)
{
var connection = new NpgsqlConnection(connectionString);
connection.StateChange += (sender, args) =>
{
logService.Info($"ConnectionState: {args.OriginalState}.
Current time: {DateTime.Now.ToShortTimeString()}");
};
return connection;
}
У данного подхода есть минус. Не понятно ID соединения, которое было открыто и закрыто. В общем данный подход скорее всего может помочь, но мне не очень понравился. Я не заметил соединение, которое вызывало утечку.
Как залогировать PostgreSQL через Npgsql.EntityFrameworkCore.PostgreSQL?
В конце концо я решил залогировать все что происходи с соединением моего кластера PostgreSQL. Чтобы залогировать все события Npgsql.EntityFrameworkCore.PostgreSQL можно добавить свой провайдер.
Перед тем как вызвать AddDbContext просто добавьте свой провайдер.
Npgsql.Logging.NpgsqlLogManager.Provider = new CustomNpgSqlLoggingProvider(logService);
public static IServiceCollection AddCustomContext
(this IServiceCollection services, IConfiguration configuration, bool useCluster = true)
{
var provider = services.BuildServiceProvider();
var logService = provider.GetService<ILogService>();
Npgsql.Logging.NpgsqlLogManager.Provider = new CustomNpgSqlLoggingProvider(logService); // На провайдер
var connectionString = configuration.GetConnectionString("DbCustomCluster");
logService.Info($"Use DbCustomCluster connection: ${connectionString.Substring(0, 19)}");
services.AddDbContext<DbCustomContext>(options =>
options.UseNpgsql(CreateConnection(connectionString, logService), builder =>
{
builder.EnableRetryOnFailure(3, TimeSpan.FromSeconds(10), new[] { "EnableRetryOnFailure" });
builder.RemoteCertificateValidationCallback(ValidateServerCertificate);
builder.ProvideClientCertificatesCallback(ProvideClientCertificates);
}));
return services;
}
/// <summary>
/// Source documentation https://www.npgsql.org/doc/logging.html
/// </summary>
public class CustomNpgSqlLoggingProvider : INpgsqlLoggingProvider
{
private readonly ILogService _logService;
public NpgSqlLoggingProvider(ILogService logService)
{
this._logService = logService;
}
public NpgsqlLogger CreateLogger(string name)
{
return new NpgsqlCustomLogger(_logService);
}
}
internal class NpgsqlCustomLogger : NpgsqlLogger
{
public ILogService LogService { get; }
public NpgsqlCustomLogger(ILogService logService)
{
this.LogService = logService;
}
public override bool IsEnabled(NpgsqlLogLevel level)
{
// Тут можно указать какой уровень логов показывать. У меня для простоты все логи.
return true; // all levels
}
public override void Log(NpgsqlLogLevel level, int connectorId, string msg, Exception exception = null)
{
// Я логирую только открытие, сам запрос и закрытие. Выглядит это так
// Level: Debug, connectorId: 1237043804, msg: Connection opened
// Level: Debug, connectorId: 1237043804, msg: Executing statement(s):\n UPDATE "Item..
// Level: Debug, connectorId: 1237043804, msg: Connection closed
if (msg.Contains("Connection opened") || msg.StartsWith("Executing statement") || msg.Contains("Connection closed"))
{
this.LogService
.Debug($"POSTGRESQL Level: {level}, connectorId: {connectorId}, msg: {msg.Substring(0, Math.Min(msg.Length, 250))}", exception);
}
}
}
В общем после долгого иследования я наконец нашел утечку. Это дейтсвительно было соединение, которое не закрывалось. Теперь я буду осторожнее писать запросы.
В этой статье за основу я возьму пример, который описывает сама Майкрософт.
Довайте добавим модель в Razor Pages. В этом примере мы будем работать со списком фильмов. База данных на основе ORM (в нашем случае Entity Framework core)
Используя Visual Studio 2019
Нажимите правой кнопкой по солюшену > Add > New Folder. Имя папки Models.
Далее тоже самое, но с папкой. Нажмите правой кнопкой по папке Models. Выберите Add > Class. Имя класса задайте как Movie.
Дабавьте свойства в класс Movie:
using System;
using System.ComponentModel.DataAnnotations;
namespace RazorPagesMovie.Models
{
public class Movie
{
public int ID { get; set; }
public string Title { get; set; }
[DataType(DataType.Date)]
public DateTime ReleaseDate { get; set; }
public string Genre { get; set; }
public decimal Price { get; set; }
}
}
Свойство ID обязательное. Оно требуется для нашей ORM. То есть когда ORM будет создавать таблицы на основе наших классов. Она будет проверять наличие ID или MovieId (в качестве альтернативы). Если ORM найдет одно из этих свойств, то создаст таблицу с первичным ключом ID или MovieId.
Атрибут [DataType(DataType.Date)] необязательный. В нашем примере он просто добавит html поле с типом дата. Т.е. создаст поле <input type=»date» />
Скафолдинг стринцы (page scaffold)
Нам надо добавить файл представления и файл класса. Но, чтобы не делать этого вручную VisualStudio умеет добавлять нужную инфраструктуру автоматически. Данный процесс можно назвать скофолдингом.
Чтобы воспользоваться скафолдингов в VS. Добавьте папку Pages и Movies внутри. Далее нажмем правой кнопкой Add -> New Scaffolded item как на картинке.
Как добавить CRUD действия на основе нашей модели.
После комадны New Scaffolded item добавим CRUD действия на основе нашей модели. (CRUD — Это основные действия create, read, update, delete)
Вы модальном окте «Add razor pages using entity framework (CRUD)». Выберите следуюшие позиции из списков.
Model class — укажите свой класс модели (Movie).
Date context class — нажмите на плюсик и укажите контекст RazorPagesMovies.Models.RazorPagesMovieContext (VS автоматически добавит нужный класс контекста с записимостями)
VisualStudio автоматически сгенерирует нам файлы.
Pages/Movies: Create, Delete, Details, Edit, and Index.