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 در مخزن نمونه کدهای وبلاگ میزبانی می شود.
منابع
بدون ترتیب خاصی