.NET хорошо работает со всеми тремя. У SQL Server самые развитые инструменты и провайдер, который Microsoft пишет сама, у PostgreSQL есть Npgsql - отличный и не уступающий по скорости ничему в экосистеме, а у MySQL есть MySqlConnector под общественным провайдером Pomelo для Entity Framework Core. Выбор касается базы данных, а не драйвера: SQL Server Express бесплатен, но ограничен 10 GB на базу и примерно 1.4 GB памяти буфера; у PostgreSQL нет ограничений редакций и самый богатый набор возможностей; MySQL есть везде, и его легче всего запускать. Когда выбор сделан, проблемы доставляют одни и те же места для каждой: строка подключения, размер пула, поведение дат и строк и привязка миграций к конкретному провайдеру. Эта статья разбирает всё это бок о бок.
Провайдеры и драйверы#
У каждого стека баз данных в .NET два слоя. Внизу - драйвер ADO.NET, который говорит на сетевом протоколе базы и реализует DbConnection и DbCommand. Сверху, по желанию, - провайдер Entity Framework Core, который переводит LINQ в диалект SQL этой базы. Dapper и написанный вручную SQL используют драйвер напрямую.
| База данных | Драйвер ADO.NET | Провайдер EF Core | Кто поддерживает |
|---|---|---|---|
| PostgreSQL | Npgsql | Npgsql.EntityFrameworkCore.PostgreSQL | Проект Npgsql |
| MySQL | MySqlConnector | Pomelo.EntityFrameworkCore.MySql | Сообщество (Pomelo, MySqlConnector) |
| MySQL (альтернатива) | MySql.Data | MySql.EntityFrameworkCore | Oracle |
| SQL Server | Microsoft.Data.SqlClient | Microsoft.EntityFrameworkCore.SqlServer | Microsoft |
Несколько замечаний к этой таблице:
- `System.Data.SqlClient` - старый драйвер SQL Server, и он устарел. Новый код и все текущие версии EF Core используют
Microsoft.Data.SqlClient. Скопированный пример со старым пространством имён по-прежнему компилируется и даёт вам драйвер со старыми значениями по умолчанию и без новых исправлений. - Для MySQL предпочитайте MySqlConnector. Он под лицензией MIT, по-настоящему асинхронный (
MySql.Dataот Oracle исторически реализовывал свои async-методы синхронно), и именно его использует Pomelo. Пакеты Oracle распространяются под GPL с исключением FOSS, что для некоторых коммерческих проектов имеет значение. - Релизы Pomelo следуют за мажорными версиями EF Core, иногда с отставанием в несколько месяцев. Прежде чем обновлять EF Core до новой мажорной версии, проверьте, что соответствующий релиз Pomelo существует. Npgsql обычно выпускал свой провайдер EF Core близко к релизу Microsoft.
Подключение каждой базы через EF Core#
Регистрация выглядит почти одинаково для всех трёх - в этом и смысл EF Core:
// PostgreSQLbuilder.Services.AddDbContext<AppDbContext>(o => o.UseNpgsql(builder.Configuration.GetConnectionString("Default")));// MySQL via Pomelovar mysql = builder.Configuration.GetConnectionString("Default");builder.Services.AddDbContext<AppDbContext>(o => o.UseMySql(mysql, ServerVersion.AutoDetect(mysql)));// SQL Serverbuilder.Services.AddDbContext<AppDbContext>(o => o.UseSqlServer(builder.Configuration.GetConnectionString("Default")));Pomelo нужна версия сервера, потому что MySQL и MariaDB различаются в том, какой SQL принимают. ServerVersion.AutoDetect при запуске открывает соединение, чтобы это узнать, и если база в этот момент недоступна, запуск приложения проваливается; new MySqlServerVersion(new Version(8, 4, 0)) обходится без этого обращения и в продакшене лучше.
Для Npgsql начиная с версии 7 рекомендуется один раз построить NpgsqlDataSource и передать его EF Core или использовать напрямую - именно там настраиваются сопоставления типов, enum и логирование:
var dataSource = new NpgsqlDataSourceBuilder( builder.Configuration.GetConnectionString("Default")).Build();builder.Services.AddDbContext<AppDbContext>(o => o.UseNpgsql(dataSource));GetConnectionString("Default") читает ConnectionStrings:Default из конфигурации, а в продакшене вы задаёте её переменной окружения с именем ConnectionStrings__Default. Строка подключения - это секрет; ей не место в appsettings.json в репозитории. Слои конфигурации разобраны в статье Конфигурация и секреты в ASP.NET Core.
Строки подключения бок о бок#
PostgreSQL (Npgsql)Host=db.example.net;Port=5432;Database=app;Username=app;Password=...;SSL Mode=PreferMySQL (MySqlConnector)Server=db.example.net;Port=3306;Database=app;User ID=app;Password=...;SslMode=PreferredSQL Server (Microsoft.Data.SqlClient)Server=tcp:db.example.net,1433;Database=app;User ID=app;Password=...;Encrypt=True;TrustServerCertificate=TrueРазличия, на которых спотыкаются:
- SQL Server ставит порт после запятой, а не двоеточия, и отдельного ключевого слова
Portу него нет.db.example.net:1433читается как имя хоста и не работает. Та же запятая используется в поле имени сервера в SQL Server Management Studio. - SQL Server шифрует по умолчанию. Начиная с
Microsoft.Data.SqlClient4.0 (а значит, с EF Core 7)Encryptпо умолчанию равенTrue, и драйвер проверяет сертификат сервера. Сервер с самоподписанным сертификатом - а именно такой SQL Server генерирует себе сам, если никакой не настроен, - падает с ошибкой «The certificate chain was issued by an authority that is not trusted», пока вы не добавитеTrustServerCertificate=Trueили не установите доверенный сертификат. Вариант этого для каждого драйвера разобран в статье Строки подключения SQL Server. - MySQL 8.4 аутентифицирует через `caching_sha2_password`. По незашифрованному соединению первому входу нужен публичный RSA-ключ сервера, и MySqlConnector отказывает с «Retrieval of the RSA public key is not enabled for insecure connections», если вы не используете TLS (
SslMode=Required) или не добавитеAllowPublicKeyRetrieval=True. Предпочитайте TLS. - В Npgsql по умолчанию `SSL Mode=Prefer`, то есть TLS используется, когда сервер его предлагает, без проверки сертификата. Используйте
Require, чтобы отказываться от открытого текста, иVerifyFullс доверенным CA, когда сертификат нужно проверять.
Пароли, содержащие ; или =, в строках подключения ADO.NET нужно брать в кавычки - Password="a;b=c" - или собирать через класс ConnectionStringBuilder драйвера, который экранирует за вас.
Пулы соединений и таймауты#
Все три драйвера по умолчанию держат пул соединений - по одному пулу на каждую отдельную строку подключения в процессе. Открытие соединения берёт его из пула; dispose возвращает обратно. Поэтому стандартный паттерн - DbContext на запрос, уничтожаемый в конце, - стоит дёшево.
| Настройка | Npgsql | MySqlConnector | SqlClient |
|---|---|---|---|
| Максимальный размер пула | Maximum Pool Size=100 | MaximumPoolSize=100 | Max Pool Size=100 |
| Минимальный размер пула | Minimum Pool Size=0 | MinimumPoolSize=0 | Min Pool Size=0 |
| Таймаут подключения | Timeout=15 | ConnectionTimeout=15 | Connect Timeout=15 |
| Таймаут команды | Command Timeout=30 | DefaultCommandTimeout=30 | 30 с (на команду) |
| Удаление простаивающих соединений | Connection Idle Lifetime=300 | ConnectionIdleTimeout=180 | Управляет драйвер |
Значения в секундах. Сто соединений на процесс - намного больше, чем небольшой сервер баз данных может толково использовать. PostgreSQL в особенности тратит реальную память на каждое соединение, а у базы на тарифе с 1 или 2 GB max_connections заметно меньше, чем могли бы открыть три экземпляра приложения с пулами по умолчанию. Подбирайте пул под базу, а не наоборот: двадцати-тридцати соединений хватает большинству приложений на небольшом сервере, а приложение, которому нужно больше, обычно слишком долго держит соединения. Почему меньший пул часто быстрее, объясняет статья Пулы соединений и лимиты.
Когда пул исчерпан, запросы ждут таймаута подключения, а затем падают с ошибкой таймаута пула. Эта ошибка почти никогда не означает, что пул слишком мал. Она означает, что соединения утекают (не освобождаются) или удерживаются во время медленной работы - например, HTTP-вызова посреди транзакции.
У EF Core есть собственный, отдельный пул: AddDbContextPool<AppDbContext>() переиспользует экземпляры DbContext (по умолчанию до 1024), экономя на аллокациях. Он не меняет количество соединений с базой и требует, чтобы ваш контекст не хранил состояние конкретного запроса.
Повторы и временные сбои#
Сети теряют пакеты, базы перезапускаются. EF Core умеет автоматически повторять неудавшиеся операции:
o.UseSqlServer(conn, sql => sql.EnableRetryOnFailure( maxRetryCount: 5, maxRetryDelay: TimeSpan.FromSeconds(10), errorNumbersToAdd: null));o.UseNpgsql(conn, pg => pg.EnableRetryOnFailure());o.UseMySql(conn, version, my => my.EnableRetryOnFailure());При включённых повторах транзакция, которую вы открываете сами через BeginTransactionAsync, выбрасывает исключение, потому что EF Core не может воспроизвести половину транзакции. Вместо этого оберните всю единицу работы в execution strategy:
var strategy = db.Database.CreateExecutionStrategy();await strategy.ExecuteAsync(async () =>{ await using var tx = await db.Database.BeginTransactionAsync(); // ... several SaveChangesAsync calls await tx.CommitAsync();});Повторы скрывают кратковременные сбои. Базу, которая действительно лежит, они на время повторов превращают в очень медленное приложение, так что держите число попыток скромным и логируйте каждый повтор.
Различия, которые кусаются при переходе#
EF Core скрывает диалект SQL. Поведение базы он не скрывает, и вот с чем сталкиваются при переносе приложения с одного движка на другой или при тестах на SQLite с продакшеном на чём-то другом:
- Сравнение строк и регистр. Collation по умолчанию в SQL Server (
SQL_Latin1_General_CP1_CI_AS) и в MySQL 8 (utf8mb4_0900_ai_ci) сравнивают без учёта регистра, так чтоWHERE Email = 'Bob@x.com'находитbob@x.com. PostgreSQL сравнивает с учётом регистра. Поиск при входе, который работал на SQL Server, молча перестаёт находить пользователей на PostgreSQL. Нормализуйте email при записи или используйте регистронезависимый collation или типcitextв PostgreSQL. - Даты и часовые пояса. Npgsql 6 и новее сопоставляет
DateTimeсtimestamp with time zoneи отказывается записыватьDateTime, у которогоKindнеUtc, с ошибкой «Cannot write DateTime with Kind=Unspecified to PostgreSQL type 'timestamp with time zone'». Храните везде UTC или используйтеDateTimeOffset.datetime2в SQL Server иdatetimeв MySQL вообще не хранят часовой пояс, так что тот же код без возражений пишет местное время, а баг всплывает позже. - Unicode. В SQL Server EF Core сопоставляет
stringсnvarchar, то есть Unicode. Созданные вручную таблицы с колонкамиvarcharмогут терять символы, не входящие в кодовую страницу collation. В MySQL используйтеutf8mb4- старый псевдонимutf8трёхбайтовый и не может хранить emoji. Подробнее - в статьях Collation и Unicode в SQL Server и utf8mb4 и collation в MySQL. - Длина строк по умолчанию. Свойство
stringбез ограничения становитсяnvarchar(max)в SQL Server,longtextв MySQL иtextв PostgreSQL. В SQL Server колонкиnvarchar(max)не могут быть ключами индекса; задайте[MaxLength]свойствам, по которым будете фильтровать. - Значения identity. Все три генерируют ключи - через
IDENTITY,AUTO_INCREMENTили identity-колонки на основе последовательностей. Пропуски после откатов и перезапусков нормальны во всех трёх; никогда не используйте ключ как счётчик. - Миграции привязаны к провайдеру. Миграция, сгенерированная для SQL Server, содержит типы колонок и аннотации SQL Server. Смена движка означает генерацию миграций с нуля для нового провайдера, а не повторное использование папки.
Выбор базы данных для приложения .NET#
По одним только возможностям любая из трёх потянет типичное веб-приложение. Выбор обычно сводится к ограничениям, инструментам и тому, что вы уже знаете.
SQL Server Express подходит командам, которые и так живут в SQL Server Management Studio, приложениям, использующим возможности, специфичные для SQL Server (temporal tables, хранимые процедуры на T-SQL, полный набор инструментов SQL Server), и всему, что позже может переехать на платную редакцию SQL Server или в Azure SQL. Его ограничения вполне реальны: каждая база ограничена 10 GB данных, buffer pool - примерно 1,410 MB, а вычисления - меньшим из одного сокета или четырёх ядер, и нет SQL Server Agent для задач по расписанию. Когда этого достаточно, разбирает статья Хостинг SQL Server Express.
PostgreSQL - рекомендация по умолчанию для нового проекта без других ограничений: никаких ограничений редакций, отличная поддержка JSON, богатая индексация, а Npgsql первоклассный. Первое подключение разобрано в статье Удалённые подключения к PostgreSQL.
MySQL - самый лёгкий в эксплуатации из трёх и знакомый почти всем. Это хороший выбор, когда приложение уже на него рассчитано или делит базу с софтом на PHP.
Более подробное сравнение, включая MongoDB и Valkey, - в статье Какую базу данных выбрать.
В RE:NODE каждая из них - отдельный тариф базы данных с учётными данными, генерируемыми для каждого сервера, и доступом по хосту и порту тарифа: PostgreSQL от $6 в месяц, MySQL 8.4 LTS от $4 с генерируемыми паролем root, базой данных приложения и пользователем приложения, и SQL Server 2022 Express от $6 с паролем sa и созданной базой, назначенной базой по умолчанию для sa. У тарифов баз данных нет слота прокси - приложение подключается прямо к хосту и порту. Тарифы приложений C# / .NET тоже включают собственные слоты баз данных, которые создаются в панели с генерируемыми хостом, пользователем и паролем.
Решение проблем#
`A network-related or instance-specific error occurred` (SQL Server). Неправильный хост или порт, двоеточие вместо запятой перед портом или firewall. Сначала проверьте порт.
`The certificate chain was issued by an authority that is not trusted` (SQL Server). Шифрование включено по умолчанию, а сертификат сервера самоподписанный. Добавьте TrustServerCertificate=True или установите сертификат от доверенного CA.
`Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool`. Соединения утекают или удерживаются слишком долго. Ищите контексты или соединения, созданные вне using, и работу, выполняемую внутри открытых транзакций.
`53300: sorry, too many clients already` (PostgreSQL) или `Too many connections` (MySQL). Пулы всех ваших экземпляров приложения вместе превышают лимит сервера. Уменьшите Maximum Pool Size.
`Unable to connect to any of the specified MySQL hosts`. Хост, порт или firewall. Если хост правильный, проверьте, разрешено ли пользователю подключаться с вашего адреса.
FAQ#
Entity Framework Core медленнее Dapper?
На простых запросах современный EF Core близок к нему, особенно с AsNoTracking() и проекциями. Dapper по-прежнему выигрывает по накладным расходам на маппинг и даёт точный контроль над SQL. Многие приложения используют EF Core для записи и миграций, а Dapper - для нескольких тяжёлых запросов на чтение, с одной и той же строкой подключения.
Можно ли использовать SQLite в тестах и SQL Server в продакшене?
Можно, но это будет скрывать баги: чувствительность к регистру, обработка дат, поведение транзакций и трансляция SQL - всё это различается. Запускайте интеграционные тесты на том же движке, что и продакшен, при необходимости в контейнере.
Какой провайдер MySQL использовать с EF Core?
Pomelo поверх MySqlConnector. Он самый распространённый и самый полный. Прежде чем обновлять EF Core, проверьте, что его релиз для вашей мажорной версии EF Core существует.
Нужен ли TLS между приложением и базой данных?
Если соединение проходит через любую сеть, которую вы не контролируете, - а это включает интернет между сервером приложения и сервером базы, - да. Все три драйвера его поддерживают; драйвер SQL Server включает его по умолчанию.
Сколько соединений должен разрешать пул?
Меньше, чем вы думаете. Начните с двадцати-тридцати на экземпляр приложения для небольшой базы, убедитесь, что сумма по всем экземплярам остаётся ниже лимита базы, и поднимайте, только если можете показать, что запросы ждут пул, пока сама база простаивает.




Комментарии
Полностью анонимно: без аккаунта, без почты, без cookie. Мы храним имя, которое вы ввели, текст и время - больше ничего. Количество ссылок ограничено, разметка не отображается.