Как исправить ошибку 'N' в EF Core SQLite JSON
Исправление ошибки JsonReaderException 'N' является недопустимым началом значения в EF Core SQLite. Диагностика данных в Tags, SQL-фиксы, настройка HasConversion для string[]. Пошаговый чеклист и примеры кода для .NET MAUI.
Как исправить ошибку запроса EF Core SQLite: ‘N’ является недопустимым началом значения
Я столкнулся с ошибкой при запросе к SQLite с помощью Entity Framework Core в моем приложении .NET 9 Maui. Сообщение об ошибке:
'N' является недопустимым началом значения. LineNumber: 0 | BytePositionInLine: 0.
Это исключение выбрасывается из:
System.Text.Json.JsonReaderExceptionв System.Text.Json.dllSystem.Text.Json.JsonReaderExceptionв Microsoft.EntityFrameworkCore.Relational.dll
Мой метод репозитория
public void GetMoviesAsync()
{
try
{
using (var movieContext = new MovieContext())
{
var movies = movieContext.Movies
.AsNoTracking()
.ToList<Movie>();
//return movies ?? new List<Movie>();
}
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
Дополнительная информация
Когда я попытался выполнить запрос в виде необработанного SQL:
movieContext.Database.ExecuteSqlRaw("Select * From Movies");
Он вернул -1, что указывает на то, что не было возвращено ни одной строки.
Определение класса Movie
public class Movie
{
[Key]
public int MovieId { get; set; }
[Required]
public string Title { get; set; }
public DateOnly ReleaseDate { get; set; }
public string Genre { get; set; }
public string[] Tags { get; set; }
public string MovieLocation { get; set; }
public string ImageLocation { get; set; }
public int GroupId { get; set; }
}
Конфигурация контекста данных
public class MovieContext : DbContext
{
public DbSet<Movie> Movies { get; set; }
public DbSet<Group> Groups { get; set; }
public MovieContext()
{
SQLitePCL.Batteries_V2.Init();
this.Database.EnsureCreated();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
optionsBuilder.UseSqlite("Data Source = D:\\Repos\\MovieManager\\MovieManager.db3");
}
Данные в базе данных
База данных содержит одну строку данных, но я не могу ее получить из-за ошибки. Похоже, ошибка указывает на проблему с разбором JSON, возможно связанную с символом “N” в данных.
Что я уже пробовал
- Использование
AsNoTracking()для лучшей производительности - Попытка выполнения необработанного SQL-запроса
- Проверка подключения к базе данных и наличия данных
Вопрос
Что может вызывать эту ошибку запроса EF Core SQLite, и как ее можно исправить? Есть ли проблема с данными в моей базе данных, или это проблема конфигурации моей настройки EF Core?
Ошибка 'N' является недопустимым началом значения' — это json ошибка, возникающая при попытке System.Text.Json десериализовать невалидный текст из колонки (в вашем случае — Tags) при чтении через EF Core/SQLite. Чаще всего причина — некорректные данные (например, значение начинается с N из-за экспорта/префикса N'...', или хранится строка "NULL"/"N'[]'"), либо отсутствие корректного конвертера для string[] в модели EF Core; решение — сначала нормализовать данные в БД, затем добавить надежный HasConversion/ArrayToJsonConverter или обработку ошибок десериализации в модели EF Core.
Содержание
- Почему возникает ошибка
Nи что она значит - Как быстро найти «плохие» строки в базе (поиск и диагностика)
- Исправление данных (SQL и C#): примеры и безопасные шаги
- Настройка EF Core: конвертеры для string[] и устойчивость к ошибкам
- Миграции, EnsureCreated и поведение ExecuteSqlRaw
- Пошаговый чеклист восстановления и тестирования
- Источники
- Заключение
Почему возникает json ошибка ‘N’ является недопустимым началом значения
Ошибка приходит от System.Text.Json: парсер ожидает допустимый JSON (начало может быть [ — массив, " — строка, { — объект, n — null, цифра, t/f и т.д.). Символ N (заглавная буква) не входит в допустимые первые символы JSON, поэтому парсер падает с JsonReaderException.
Типичные причины в вашей ситуации:
- Поле
Tagsв таблице содержит невалидный текст — например, значения пришли из экспорта SQL Server и записаны какN'["a","b"]'(вставлен префиксN'...') или как строка"NULL"/"Null"/"N"— такие варианты не являются корректным JSON. Это частая причина (см. похожую задачу на StackOverflow) — пример обсуждения на StackOverflow. - EF Core/поставщик SQLite ожидает JSON-представление для массива (в новых версиях EF Core массивы могут храниться как JSON в TEXT), поэтому при чтении EF вызывает десериализацию и ошибку, если значение невалидно. Официальные ограничения и рекомендации по работе с SQLite описаны в документации EF Core — SQLite limitations.
- Реже: проблема с EnsureCreated() / инициализацией БД или с версией библиотек; похожие баги обсуждались в issue-трекере EF Core (например, ошибки при EnsureCreated и JSON-колонках) — см. обсуждение.
Кратко: прежде чем менять код EF Core, сначала проверьте содержимое столбца Tags — чаще всего проблема в данных.
Как найти и проверить повреждённые данные в SQLite
-
Создайте резервную копию файла базы (копируйте .db3) — обязательно.
-
Быстро посмотреть содержимое колонки
Tags(CLI sqlite3 или DB Browser):
- sqlite3:
sqlite3 MovieManager.db3 "SELECT MovieId, Title, Tags, substr(Tags,1,30) AS snippet FROM Movies;"
- Найти строки, начинающиеся с
N:
SELECT MovieId, Tags FROM Movies WHERE Tags LIKE 'N%';
- Найти строки с текстом
"NULL"/пустые:
SELECT MovieId, Tags FROM Movies WHERE Tags IS NULL OR trim(Tags) = '' OR lower(Tags) = 'null';
- Смотреть первые байты в hex (полезно при подозрении на невидимые символы/кодировку):
SELECT MovieId, hex(substr(Tags,1,10)) FROM Movies WHERE MovieId = 1;
- Прочитать “сырые” значения из C# (не через EF, а через Microsoft.Data.Sqlite), чтобы увидеть, что реально хранится:
using Microsoft.Data.Sqlite;
using var conn = new SqliteConnection("Data Source=D:\\Repos\\MovieManager\\MovieManager.db3");
conn.Open();
using var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT MovieId, Tags FROM Movies";
using var rdr = cmd.ExecuteReader();
while (rdr.Read())
{
var id = rdr.GetInt32(0);
var tagsRaw = rdr.IsDBNull(1) ? null : rdr.GetString(1);
Console.WriteLine($"Id={id} TagsRaw='{tagsRaw}'");
}
Этот код обходит EF и покажет фактическую строку, из-за которой падает парсер.
Исправление данных (SQL / C#): примеры и безопасные шаги
Шаги (рекомендация):
- Сделайте резервную копию БД.
- Найдите проблемные строки (см. выше).
- Исправьте их либо SQL-скриптом, либо C#-скриптом.
Некоторые полезные SQL-примеры (тестируйте на копии БД):
- Нормализовать пустые / literal NULL значения в
[]:
UPDATE Movies
SET Tags = '[]'
WHERE Tags IS NULL
OR trim(Tags) = ''
OR lower(Tags) = 'null';
- Если увидите значение вида
N'["a","b"]'(экспорт из SQL Server оставил префиксN'), можно удалить префикс и завершающую кавычку:
-- Предварительно посмотреть, что получится:
SELECT MovieId, substr(Tags, 3, length(Tags) - 3) AS fixed_preview FROM Movies WHERE Tags LIKE 'N''%';
-- Если результат верный — выполнить обновление:
UPDATE Movies
SET Tags = substr(Tags, 3, length(Tags) - 3)
WHERE Tags LIKE 'N''%';
(Обратите внимание на одинарные кавычки в SQL — для литерала N' нужно писать N'' внутри строки шаблона: LIKE 'N''%'.)
- Если данные очень разношерстные, корректнее сделать скрипт на C#, который читает сырые строки и “лечит” их логикой (убирает
N'…', заменяет"NULL"на[], пробует десериализовать и при ошибке ставит[]), затем обновляет строки через параметризованный UPDATE. Такой код более контролируем и безопасен.
Пример корректирующей функции на C# (упрощённо):
string NormalizeTags(string raw)
{
if (string.IsNullOrWhiteSpace(raw)) return "[]";
if (string.Equals(raw, "NULL", StringComparison.OrdinalIgnoreCase)) return "[]";
if (raw.StartsWith("N'") && raw.EndsWith("'")) raw = raw.Substring(2, raw.Length - 3);
try {
// если валидный JSON-массив, оставляем как есть
JsonSerializer.Deserialize<string[]>(raw);
return raw;
} catch {
// пробуем заменить одинарные кавычки на двойные и снова десериализовать
var cleaned = raw.Replace('\'', '"');
try {
JsonSerializer.Deserialize<string[]>(cleaned);
return cleaned;
} catch {
// окончательная падение — безопасно вернуть пустой массив
return "[]";
}
}
}
После исправления данных обязательно проверьте, что первые символы Tags — это [, " или n (lowercase null) — тогда System.Text.Json не будет жаловаться.
Настройка EF Core: конвертеры для string[] и устойчивость к ошибкам
Независимо от очищения данных, в модели стоит явно указать, как EF должен сериализовать/десериализовать string[] в SQLite (SQLite не поддерживает массивы нативно). Есть два варианта.
- EF Core 8+ — использовать готовый ArrayToJsonConverter (упрощает код):
// Пример (EF Core 8+)
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Movie>()
.Property(e => e.Tags)
.HasConversion(new ArrayToJsonConverter<string[]>());
}
Подробный разбор маппинга массивов — в официальном блоге .NET: https://devblogs.microsoft.com/dotnet/array-mapping-in-ef-core-8/
- Универсальный вариант (работает в старших версиях тоже): ручная сериализация/десериализация:
using System.Text.Json;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Movie>()
.Property(e => e.Tags)
.HasConversion(
v => JsonSerializer.Serialize(v ?? Array.Empty<string>()),
v => string.IsNullOrEmpty(v) ? Array.Empty<string>() : JsonSerializer.Deserialize<string[]>(v) ?? Array.Empty<string>()
);
}
Важно: перед применением такого кода очистите текущие данные — иначе десериализация упадёт на уже имеющихся некорректных строках.
Рекомендации по устойчивости:
- Можно добавить ValueComparer для массивов, чтобы корректно работала отслеживаемость изменений.
- Если хотите не падать на любых ошибках десериализации, валидируйте/исправляйте сырые строки перед десериализацией (см. C#-функцию Normalize выше).
- Также нужно обработать
DateOnly ReleaseDate— SQLite хранит дату как TEXT в ISO-формате; при необходимости добавьтеHasConversionдля DateOnly (см. ограничения SQLite в docs).
Официальная документация по ограничениям SQLite и советам по конвертерам: https://learn.microsoft.com/en-us/ef/core/providers/sqlite/limitations
Миграции, EnsureCreated и поведение ExecuteSqlRaw
- ExecuteSqlRaw возвращает количество затронутых строк для DML:
ExecuteSqlRaw("SELECT ...")вернёт -1 — это нормальное поведение, SELECT через этот метод не предназначен для получения результата. Для чтения используйте: context.Movies.FromSqlRaw("SELECT * FROM Movies").ToList()— EF вернёт сущности (и применит конвертеры);- или raw ADO.NET / Microsoft.Data.Sqlite, как показано выше, чтобы увидеть «сырые» значения.
- Для управления схемой используйте миграции (
Database.Migrate()), а неEnsureCreated()в production —EnsureCreatedможет приводить к разным сюрпризам при сложных типах/JSON-столбцах (см. обсуждения в issue-трекере EF Core) — issue про EnsureCreate и JSON. - Убедитесь, что вы используете совместимые версии EF Core / Microsoft.Data.Sqlite для .NET 9 и при необходимости обновите пакеты.
Пошаговый чеклист восстановления и тестирования
- Сделать резервную копию базы (копировать файл .db3).
- Прочитать сырые значения
Tagsчерез sqlite3/DB Browser или ADO.NET (не через EF). - Найти строки с
Tags LIKE 'N%'илиlower(Tags) = 'null'и просмотреть их. - На копии БД выполнить SQL- или C#-скрипт для нормализации (заменить на
[]или убратьN'...). - В коде добавить HasConversion (или ArrayToJsonConverter для EF8+).
- Убрать/заменить использование EnsureCreated() на миграции, если нужно.
- Запустить
context.Movies.AsNoTracking().ToList()и убедиться, что исключение исчезло. - После успешного теста применить изменения к основной БД.
Источники
- How can I solve an EF Core error querying SQLite? - Stack Overflow
- SQLite Database Provider - Limitations - EF Core
- A beginner’s guide to mapping arrays in EF Core 8 - .NET Blog
- Support the new BCL DateOnly and TimeOnly structs for Sqlite - EF Core issue
- Error during EnsureCreate on sqlite db with json columns - EF Core issue
- Документация EF Core (рус.) — ограничения SQLite
Заключение
Коротко: причина — невалидный JSON в колонке Tags (значение начинается с N/NULL/префикса N'...') или отсутствие явного конвертера для string[]. Решение — сначала диагностировать и поправить данные (или заменить на []), затем настроить сериализацию/десериализацию в EF Core (HasConversion или ArrayToJsonConverter) и по возможности перейти на миграции вместо EnsureCreated. После этого System.Text.Json.JsonReaderException с 'N' должен исчезнуть.