Программирование

Как исправить ошибку '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.dll
  • System.Text.Json.JsonReaderException в Microsoft.EntityFrameworkCore.Relational.dll

Мой метод репозитория

csharp
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:

csharp
movieContext.Database.ExecuteSqlRaw("Select * From Movies");

Он вернул -1, что указывает на то, что не было возвращено ни одной строки.

Определение класса Movie

csharp
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; }
}

Конфигурация контекста данных

csharp
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” в данных.

Что я уже пробовал

  1. Использование AsNoTracking() для лучшей производительности
  2. Попытка выполнения необработанного SQL-запроса
  3. Проверка подключения к базе данных и наличия данных

Вопрос

Что может вызывать эту ошибку запроса 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.


Содержание


Почему возникает json ошибка ‘N’ является недопустимым началом значения

Ошибка приходит от System.Text.Json: парсер ожидает допустимый JSON (начало может быть [ — массив, " — строка, { — объект, nnull, цифра, 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

  1. Создайте резервную копию файла базы (копируйте .db3) — обязательно.

  2. Быстро посмотреть содержимое колонки Tags (CLI sqlite3 или DB Browser):

  • sqlite3:
bash
sqlite3 MovieManager.db3 "SELECT MovieId, Title, Tags, substr(Tags,1,30) AS snippet FROM Movies;"
  • Найти строки, начинающиеся с N:
sql
SELECT MovieId, Tags FROM Movies WHERE Tags LIKE 'N%';
  • Найти строки с текстом "NULL"/пустые:
sql
SELECT MovieId, Tags FROM Movies WHERE Tags IS NULL OR trim(Tags) = '' OR lower(Tags) = 'null';
  • Смотреть первые байты в hex (полезно при подозрении на невидимые символы/кодировку):
sql
SELECT MovieId, hex(substr(Tags,1,10)) FROM Movies WHERE MovieId = 1;
  1. Прочитать “сырые” значения из C# (не через EF, а через Microsoft.Data.Sqlite), чтобы увидеть, что реально хранится:
csharp
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#): примеры и безопасные шаги

Шаги (рекомендация):

  1. Сделайте резервную копию БД.
  2. Найдите проблемные строки (см. выше).
  3. Исправьте их либо SQL-скриптом, либо C#-скриптом.

Некоторые полезные SQL-примеры (тестируйте на копии БД):

  • Нормализовать пустые / literal NULL значения в []:
sql
UPDATE Movies
SET Tags = '[]'
WHERE Tags IS NULL
 OR trim(Tags) = ''
 OR lower(Tags) = 'null';
  • Если увидите значение вида N'["a","b"]' (экспорт из SQL Server оставил префикс N'), можно удалить префикс и завершающую кавычку:
sql
-- Предварительно посмотреть, что получится:
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# (упрощённо):

csharp
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 не поддерживает массивы нативно). Есть два варианта.

  1. EF Core 8+ — использовать готовый ArrayToJsonConverter (упрощает код):
csharp
// Пример (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/

  1. Универсальный вариант (работает в старших версиях тоже): ручная сериализация/десериализация:
csharp
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 и при необходимости обновите пакеты.

Пошаговый чеклист восстановления и тестирования

  1. Сделать резервную копию базы (копировать файл .db3).
  2. Прочитать сырые значения Tags через sqlite3/DB Browser или ADO.NET (не через EF).
  3. Найти строки с Tags LIKE 'N%' или lower(Tags) = 'null' и просмотреть их.
  4. На копии БД выполнить SQL- или C#-скрипт для нормализации (заменить на [] или убрать N'...).
  5. В коде добавить HasConversion (или ArrayToJsonConverter для EF8+).
  6. Убрать/заменить использование EnsureCreated() на миграции, если нужно.
  7. Запустить context.Movies.AsNoTracking().ToList() и убедиться, что исключение исчезло.
  8. После успешного теста применить изменения к основной БД.

Источники

  1. How can I solve an EF Core error querying SQLite? - Stack Overflow
  2. SQLite Database Provider - Limitations - EF Core
  3. A beginner’s guide to mapping arrays in EF Core 8 - .NET Blog
  4. Support the new BCL DateOnly and TimeOnly structs for Sqlite - EF Core issue
  5. Error during EnsureCreate on sqlite db with json columns - EF Core issue
  6. Документация EF Core (рус.) — ограничения SQLite

Заключение

Коротко: причина — невалидный JSON в колонке Tags (значение начинается с N/NULL/префикса N'...') или отсутствие явного конвертера для string[]. Решение — сначала диагностировать и поправить данные (или заменить на []), затем настроить сериализацию/десериализацию в EF Core (HasConversion или ArrayToJsonConverter) и по возможности перейти на миграции вместо EnsureCreated. После этого System.Text.Json.JsonReaderException с 'N' должен исчезнуть.

Авторы
Проверено модерацией
Модерация
Как исправить ошибку 'N' в EF Core SQLite JSON