Как вы использовали PostgreSQL в проектах на C#?

Ответ

В нескольких production-проектах я использовал PostgreSQL как основную реляционную БД благодаря ее надежности, богатому функционалу и открытой лицензии. Работа строилась через Npgsql — официальный и высокопроизводительный ADO.NET-провайдер.

1. Подключение и конфигурация Устанавливается пакет Npgsql.EntityFrameworkCore.PostgreSQL для EF Core.

// В Startup.cs / Program.cs
services.AddDbContext<ApplicationDbContext>(options =>
    options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"))
           // Важная оптимизация для PostgreSQL
           .UseSnakeCaseNamingConvention() // Опционально: snake_case в БД
);

Строка подключения в appsettings.json:

"ConnectionStrings": {
  "DefaultConnection": "Host=localhost;Database=myapp;Username=postgres;Password=secret"
}

2. Работа с продвинутыми типами данных PostgreSQL

  • JSONB: Хранение и запросы к полуструктурированным данным.

    // Модель EF Core
    public class Product
    {
        public int Id { get; set; }
        // Словарь метаданных хранится как JSONB
        public Dictionary<string, object> Metadata { get; set; } = new();
    }
    // В DbContext
    modelBuilder.Entity<Product>()
                .Property(p => p.Metadata)
                .HasColumnType("jsonb");
    
    // Запрос с фильтрацией по JSONB полю
    var products = await dbContext.Products
        .Where(p => p.Metadata["color"].ToString() == "red")
        .ToListAsync();
  • Массивы и составные типы: Полезны для специализированных сценариев.

3. Выполнение сырых SQL-запросов Для сложных отчетов или оптимизированных запросов.

using var connection = new NpgsqlConnection(connectionString);
var sql = @"
    SELECT u.name, COUNT(o.id) as order_count
    FROM users u
    JOIN orders o ON u.id = o.user_id
    WHERE u.created_at > @cutoffDate
    GROUP BY u.id
    HAVING COUNT(o.id) > @minOrders";

var results = await connection.QueryAsync<UserStats>(sql, new {
    cutoffDate = DateTime.UtcNow.AddMonths(-1),
    minOrders = 5
});
// Используется Dapper для простого маппинга, но можно и через DataReader.

4. Ключевые практики

  • Индексы: Активно использовал GIN-индексы для JSONB и полнотекстового поиска, BRIN для временных рядов.
  • Конкурентность: Использование FOR UPDATE SKIP LOCKED для реализации надежных очередей задач.
  • Репликация: Настройка чтения из реплик для распределения нагрузки в веб-приложениях.

Ответ 18+ 🔞

Да ты послушай, как я с этой PostgreSQL работал на продакшене — просто песня, а не база данных! Ну реально, берёшь её как основную реляционку, и спишь спокойно: надёжная, как швейцарские часы, функционала — овердохуища, да ещё и лицензия открытая, не надо никому мозги выносить. А через Npgsql с ней общаешься — это официальный провайдер для .NET, и работает, как часы, без всяких костылей.

1. Подключиться — раз плюнуть Ставишь пакет Npgsql.EntityFrameworkCore.PostgreSQL для EF Core и вперёд.

// В Startup.cs / Program.cs
services.AddDbContext<ApplicationDbContext>(options =>
    options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"))
           // Важная оптимизация для PostgreSQL
           .UseSnakeCaseNamingConvention() // Опционально: snake_case в БД
);

А в appsettings.json строку подключения пишешь, как в аптеке:

"ConnectionStrings": {
  "DefaultConnection": "Host=localhost;Database=myapp;Username=postgres;Password=secret"
}

2. А вот тут начинается магия — продвинутые типы данных

  • JSONB: Это ж просто мечта для всякой полуструктурированной хуйни! Засунул словарь — и живи спокойно.

    // Модель EF Core
    public class Product
    {
        public int Id { get; set; }
        // Словарь метаданных хранится как JSONB
        public Dictionary<string, object> Metadata { get; set; } = new();
    }
    // В DbContext
    modelBuilder.Entity<Product>()
                .Property(p => p.Metadata)
                .HasColumnType("jsonb");
    
    // И запросы к этому полю делаешь, как к обычному!
    var products = await dbContext.Products
        .Where(p => p.Metadata["color"].ToString() == "red")
        .ToListAsync();
  • Массивы и составные типы: Для особо извращённых сценариев — самое то, но без фанатизма.

3. Когда EF Core не справляется — пускаем в ход сырые SQL-запросы Бывает, отчёты такие замороченные, что проще написать запрос вручную, чем городить LINQ-овод.

using var connection = new NpgsqlConnection(connectionString);
var sql = @"
    SELECT u.name, COUNT(o.id) as order_count
    FROM users u
    JOIN orders o ON u.id = o.user_id
    WHERE u.created_at > @cutoffDate
    GROUP BY u.id
    HAVING COUNT(o.id) > @minOrders";

var results = await connection.QueryAsync<UserStats>(sql, new {
    cutoffDate = DateTime.UtcNow.AddMonths(-1),
    minOrders = 5
});
// Тут я Dapper прикрутил для маппинга — удобно, но можно и через DataReader, если мазохист.

4. Без этих практик — никуда, запомни как "Отче наш"

  • Индексы: GIN-индексы для JSONB и полнотекстового поиска — просто огонь. А для временных рядов BRIN — экономия места и скорости, как будто тебе скидку сделали.
  • Конкурентность: Хочешь очередь задач сделать, чтобы не гонялись за одной записью? FOR UPDATE SKIP LOCKED — твой лучший друг, без него просто пиздец наступает.
  • Репликация: Чтение с реплик настраиваешь — и нагрузка распределяется, как масло по бутерброду. Главное, чтобы реплики не отставали, а то будет тебе хиросима в базе данных.

Вот так вот, коротко и без воды. PostgreSQL — это сила, а с Npgsql она становится твоей силой. Главное — не накосячить с транзакциями, а то потом будешь локти кусать.