برنامه نویسی

REST API با ASP.NET Core 7 و SQL Server

این ادامه پست قبلی REST API با ASP.NET Core 7 و InMemory Store است. در این آموزش من سرویس را برای ذخیره داده ها در سرور مایکروسافت SQL گسترش خواهم داد، من از تصاویر مبتنی بر مایکروسافت SQL Server – Ubuntu برای این نمونه استفاده خواهم کرد. من از Docker برای اجرای SQL Server و از همان برای اجرای مهاجرت های پایگاه داده استفاده خواهم کرد.

راه اندازی سرور پایگاه داده

من از یک docker-compose برای اجرای SQL Server در ظرف docker استفاده خواهم کرد. این به ما امکان می دهد خدمات بیشتری را اضافه کنیم که api استراحت ما به عنوان مثال سرور redis برای کش توزیع شده به آن وابسته است.

بیایید با افزودن یک فایل جدید با کلیک راست بر روی Solution name در ویژوال استودیو و افزودن فایل جدید شروع کنیم. من دوست دارم فایل را به عنوان نامگذاری کنم docker-compose.dev-env.yml، با خیال راحت آن را هر طور که دوست دارید نام ببرید. برای افزودن یک نمونه پایگاه داده برای api استراحت فیلم، محتوای زیر را اضافه کنید.

version: '3.7'

services:
  movies.db:
    image: mcr.microsoft.com/mssql/server:2022-latest
    environment:
      - ACCEPT_EULA=Y
      - MSSQL_SA_PASSWORD=Password123
      - MSSQL_PID=Express
    volumes:
      - moviesdbdata:/var/opt/mssql/
    ports:
      - "1433:1433"

volumes:
  moviesdbdata:
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

یک ترمینال را در ریشه راه حلی که فایل docker-compose در آن قرار دارد باز کنید و دستور زیر را برای راه اندازی سرور پایگاه داده اجرا کنید.

docker-compose -f docker-compose.dev-env.yml up -d
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

مهاجرت های پایگاه داده

قبل از شروع استفاده از SQL Server باید یک پایگاه داده و جدولی برای ذخیره داده های خود ایجاد کنیم. من از سیستم استقرار پایگاه داده گرد خانه عالی برای اجرای مهاجرت پایگاه داده استفاده خواهم کرد.

من معمولاً کانتینری ایجاد می‌کنم که همه مهاجرت‌های پایگاه داده و ابزاری برای اجرای آن مهاجرت‌ها داشته باشد. من مهاجرت ها را به عنوان نام می برم [yyyyMMdd-HHmm-migration-name.sql] اما لطفاً از هر طرح نامگذاری استفاده کنید، به خاطر داشته باشید که چگونه ابزار چندین فایل را برای اجرای آن مهاجرت ها سفارش می دهد. الف را هم اضافه کردم wait-for-db.csx فایلی که من از آن به عنوان نقطه ورودی برای کانتینر مهاجرت پایگاه داده استفاده می کنم. این یک است dotnet-script فایل و با استفاده از dotnet-script اجرا می شود. من نسخه هایی را که با .net sdk 3.1 سازگار هستند پین کرده ام به عنوان این نسخه roundhouse در زمان نگارش بر خلاف ساخته شده است.

Dockerfile برای اجرای مهاجرت های پایگاه داده

FROM mcr.microsoft.com/dotnet/sdk:3.1-alpine

ENV PATH="$PATH:/root/.dotnet/tools"

RUN dotnet tool install -g dotnet-script --version 1.1.0
RUN dotnet tool install -g dotnet-roundhouse --version 1.3.1

WORKDIR /db

# Copy all db files
COPY . .

ENTRYPOINT ["dotnet-script", "wait-for-db.csx", "--", "rh", "--createdatabasescript", "database_movies_create.sql", "--silent", "-cs"]
CMD ["Server=movies.db;Database=master;User ID=sa;Password=Password123;"]
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

من یک اسکریپت برای ایجاد یک پایگاه داده سفارشی اضافه کرده ام db پوشه

  • database_movies_create.sql
USE Movies
GO

IF NOT EXISTS (SELECT * FROM sysobjects WHERE name='Movies' and xtype='U')
BEGIN
    CREATE TABLE Movies (
        Id          UNIQUEIDENTIFIER    NOT NULL PRIMARY KEY,
        Title       VARCHAR(100)        NOT NULL,
        Director    VARCHAR(100)        NOT NULL,
        ReleaseDate DateTimeOffset      NOT NULL,
        TicketPrice DECIMAL(12, 4)      NOT NULL,
        CreatedAt   DateTimeOffset      NOT NULL,
        UpdatedAt   DateTimeOffset      NOT NULL
    )
END
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

موارد زیر را اضافه کنید docker-compose.dev-env.yml فایل برای اضافه کردن محفظه مهاجرت و اجرای مهاجرت در هنگام راه اندازی. لطفاً به یاد داشته باشید که اگر مهاجرت‌های جدیدی اضافه کنید، باید کانتینر و را حذف کنید movies.db.migrations تصویر برای افزودن فایل های مهاجرتی جدید در ظرف.

  movies.db.migrations:
    depends_on:
      - movies.db
    image: movies.db.migrations
    build:
      context: ./db/
      dockerfile: Dockerfile
    command: '"Server=movies.db;Database=master;User ID=sa;Password=Password123;"'
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

یک ترمینال را در ریشه راه حل باز کنید که در آن فایل docker-compose مکان است و دستور زیر را برای راه اندازی سرور پایگاه داده و اعمال مهاجرت ها برای ایجاد طرح واره اجرا کنید. movies جدول.

docker-compose -f docker-compose.dev-env.yml up -d
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

فروشگاه فیلم های SqlServer

من از Dapper استفاده خواهم کرد – یک نگاشت شی ساده برای Net به همراه Microsoft.Data.Sql.

برپایی

  • بیایید با افزودن بسته های nuget شروع کنیم
dotnet add package Microsoft.Data.SqlClient --version 5.1.1
dotnet add package Dapper --version 2.0.123
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

  • به روز رسانی IMovieStore و همه روش ها را بسازید async.
  • به روز رسانی Controller برای ساختن روش ها async و await فراخوانی برای ذخیره متدها
  • به روز رسانی InMemoryMoviesStore برای ساختن روش ها async

SqlHelper

من یک کلاس کمکی در زیر اضافه کردم Store پوشه به نام SqlHelper. این منابع جاسازی شده را در زیر بارگذاری می کند Sql پوشه با پسوند .sql جایی که کلاس حاوی نمونه the helper است. دلیل این امر این است که من دوست دارم هر کدام را داشته باشم SQL پرس و جو در فایل خودش به راحتی می توانید پرس و جو را مستقیماً در روش ها قرار دهید.

کلاس و سازنده

یک پوشه جدید در زیر اضافه کنید Store، اسمش را گذاشتم SqlServer و یک فایل به نام اضافه کنید SqlServerMoviesStore.cs. این کلاس یک را می پذیرد IConfiguration به عنوان پارامتری که برای بارگیری رشته اتصال SQL Server از پیکربندی دات نت استفاده می کنیم. مقداردهی اولیه می کردیم connectionString و sqlHelper متغیرهای عضو در سازنده

public SqlServerMoviesStore(IConfiguration configuration)
{
    var connectionString = configuration.GetConnectionString("MoviesDb");
    if (connectionString == null)
    {
        throw new InvalidOperationException("Missing [MoviesDb] connection string.");
    }

    this.connectionString = connectionString;
    sqlHelper = new SqlHelper<SqlServerMoviesStore>();
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

من این را در آن مشخص کرده ام appsettings.json فایل پیکربندی. این برای توسعه قابل قبول است، اما هرگز یک رشته اتصال تولید/stagging را در یک فایل پیکربندی قرار ندهید. این را می توان در طاق امن مانند AWS Parameter Store یا Azure KeyVault قرار داد و از برنامه قابل دسترسی است. خط لوله CD همچنین می تواند پیکربندی شود تا این مقدار را از یک مکان امن بارگیری کند و به عنوان یک متغیر محیطی برای کانتینری که برنامه را اجرا می کند تنظیم شود.

ايجاد كردن

ما یک نمونه جدید از SqlConnection، تنظیم پارامترها برای ایجاد و اجرای پرس و جو با استفاده از Dapper برای درج یک رکورد جدید، ما در حال رسیدگی به یک SqlException و سفارش ما را بیندازیم DuplicateKeyException اگر Number استثنا است 2627.

ایجاد تابع به نظر می رسد

public async Task Create(CreateMovieParams createMovieParams)
{
    await using var connection = new SqlConnection(this.connectionString);
    {
        var parameters = new
        {
            createMovieParams.Id,
            createMovieParams.Title,
            createMovieParams.Director,
            createMovieParams.ReleaseDate,
            createMovieParams.TicketPrice,
            CreatedAt = DateTime.UtcNow,
            UpdatedAt = DateTime.UtcNow,
        };

        try
        {
            await connection.ExecuteAsync(
                this.sqlHelper.GetSqlFromEmbeddedResource("Create"),
                parameters,
                commandType: CommandType.Text);
        }
        catch (SqlException ex)
        {
            if (ex.Number == 2627)
            {
                throw new DuplicateKeyException();
            }

            throw;
        }
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و پرس و جو sql مربوطه از Create.sql فایل

INSERT INTO Movies(
    Id,
    Title,
    Director,
    ReleaseDate,
    TicketPrice,
    CreatedAt,
    UpdatedAt
)
VALUES (
    @Id,
    @Title,
    @Director,
    @ReleaseDate,
    @TicketPrice,
    @CreatedAt,
    @UpdatedAt
)
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

لطفاً توجه داشته باشید که نام ستون‌ها و نام پارامترها باید مطابق با حروف کوچک تعریف شده در پایگاه داده باشد up اسکریپت ها

GetAll

ما یک نمونه جدید از SqlConnection، استفاده کنید Dapper برای اجرای پرس و جو، dapper ستون ها را به ویژگی ها نگاشت می کند.

public async Task<IEnumerable<Movie>> GetAll()
{
    await using var connection = new SqlConnection(this.connectionString);
    return await connection.QueryAsync<Movie>(
        sqlHelper.GetSqlFromEmbeddedResource("GetAll"),
        commandType: CommandType.Text
        );
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و پرس و جو sql مربوطه از GetAll.sql فایل

SELECT
    Id,
    Title,
    Director,
    ReleaseDate,
    TicketPrice,
    CreatedAt,
    UpdatedAt
FROM Movies
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

GetById

ما یک نمونه جدید از SqlConnection، استفاده کنید Dapper برای اجرای پرس و جو با ارسال id، dapper ستون ها را به ویژگی ها نگاشت می کند.

public async Task<Movie?> GetById(Guid id)
{
    await using var connection = new SqlConnection(this.connectionString);
    return await connection.QueryFirstOrDefaultAsync<Movie?>(
        sqlHelper.GetSqlFromEmbeddedResource("GetById"),
        new { id },
        commandType: System.Data.CommandType.Text
        );
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و مربوط به sql از GetById.sql فایل

SELECT
    Id,
    Title,
    Director,
    ReleaseDate,
    TicketPrice,
    CreatedAt,
    UpdatedAt
FROM Movies
WHERE Id = @Id
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

به روز رسانی

ما یک نمونه جدید از SqlConnection، پارامترها را برای پرس و جو تنظیم کنید و با استفاده از پرس و جو را اجرا کنید Dapper برای به روز رسانی یک رکورد موجود

عملکرد به روز رسانی به نظر می رسد

public async Task Update(Guid id, UpdateMovieParams updateMovieParams)
{
    await using var connection = new SqlConnection(this.connectionString);
    {
        var parameters = new
        {
            Id = id,
            updateMovieParams.Title,
            updateMovieParams.Director,
            updateMovieParams.ReleaseDate,
            updateMovieParams.TicketPrice,
            UpdatedAt = DateTime.UtcNow,
        };

        await connection.ExecuteAsync(
            this.sqlHelper.GetSqlFromEmbeddedResource("Update"),
            parameters,
            commandType: CommandType.Text);
    }
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و پرس و جو sql مربوطه از Update.sql فایل

UPDATE Movies
SET
    Title = @Title,
    Director = @Director,
    ReleaseDate = @ReleaseDate,
    TicketPrice = @TicketPrice,
    UpdatedAt = @UpdatedAt
WHERE id = @id
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

حذف

ما یک نمونه جدید از SqlConnection، استفاده کنید Dapper برای اجرای پرس و جو با ارسال id.

public async Task Delete(Guid id)
{
    await using var connection = new SqlConnection(this.connectionString);
    await connection.ExecuteAsync(
        sqlHelper.GetSqlFromEmbeddedResource("Delete"),
        new { id },
        commandType: CommandType.Text
        );
}
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

و پرس و جو sql مربوطه از Delete.sql فایل

DELETE
FROM Movies
WHERE Id = @Id
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

لطفا توجه داشته باشید که ما پرتاب نمی کنیم RecordNotFoundException استثنا همانطور که ما در آن انجام می دادیم InMemoryMoviesStore، دلیل آن تلاش برای حذف یک رکورد با یک کلید غیر موجود در Postgres خطا در نظر گرفته نمی شود.

راه اندازی تزریق وابستگی

مرحله آخر این است که ظرف تزریق وابستگی را برای سیم کشی فروشگاه ایجاد شده جدید راه اندازی کنید. به روز رسانی Program.cs همانطور که در زیر نشان داده شده است

// builder.Services.AddSingleton<IMoviesStore, InMemoryMoviesStore>();
builder.Services.AddScoped<IMoviesStore, SqlServerMoviesStore>();
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

برای سادگی من غیر فعال کردم InMemoryMoviesStore، می توانیم یک پیکربندی اضافه کنیم و بر اساس آن تصمیم بگیریم که از کدام سرویس در زمان اجرا استفاده کنیم. این می تواند تمرین خوبی باشد، اما ما عملاً آن را انجام نمی دهیم. با این حال، برای سرویس‌های سنگین ترافیک، حافظه پنهان یا حافظه پنهان توزیع شده برای ذخیره کردن نتایج برای بهبود عملکرد استفاده می‌شود.

تست

من هیچ واحد یا تست ادغام را برای این آموزش اضافه نمی کنم، شاید یک آموزش زیر. اما تمام نقاط پایانی را می توان با اجرای برنامه یا با استفاده از Postman توسط Swagger UI آزمایش کرد.

منبع

کد منبع برای برنامه آزمایشی در GitHub در مخزن نمونه کدهای وبلاگ میزبانی می شود.

منابع

بدون ترتیب خاصی

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا