متن خبر

نحوه استفاده از Dapper در پروژه های دات نت

نحوه استفاده از Dapper در پروژه های دات نت

شناسهٔ خبر: 446608 -




هنگامی که با دات نت کار می کنید، تعامل با پایگاه های داده – به ویژه پایگاه های داده SQL – اجتناب ناپذیر است. رویکردهای رایج شامل ابزارهای ORM (نگاشت رابطه ای شی) مانند Entity Framework (EF Core) یا Dapper است.

Dapper در سرعت و کنترل برای عملیات CRUD عالی است. این مقاله مناسب بودن Dapper را برای پروژه‌های دات‌نت حیاتی عملکرد با روابط پایگاه داده ساده‌تر، با استفاده از پرس‌وجوهای SQL خام تحلیل می‌کند.

با توجه به اتکای Dapper به SQL خام، این امر همچنین آن را برای یکپارچه سازی با پایگاه داده های موجود عالی می کند (برخلاف بزرگترین رقیب آن EF Core که عمدتاً روی رویکرد کد اول کار می کند).

Dapper به عنوان یک ORM سبک وزن و با کارایی بالا با مزایای متعدد برجسته می شود. این مقاله شما را در ساخت یک API وب دات نت سبک با استفاده از نقاط پایانی پایه Dapper برای SQLite راهنمایی می کند، ابزاری ایده آل برای برنامه های توسعه محلی یا POC (اثبات مفهوم).

پیش نیازها

آخرین .NET SDK و زمان اجرا (در زمان نوشتن این مقاله NET 8 بود). اگر قبلاً این را نصب نکرده‌اید، می‌توانید نصب‌کننده‌ها را اینجا بیابید.

ویرایشگر کد ترجیحی شما یا IDE (محیط توسعه یکپارچه) - برای مثال Visual Studio / VS Code / JetBrains Rider.

آشنایی با زبان برنامه نویسی C# و Dependency Injection

خط فرمان ویندوز / ترمینال سیستم عامل مک

در این آموزش موارد زیر را خواهید آموخت:

داپر چیست؟

نحوه راه اندازی یک Api وب دات نت با پشتیبانی پایگاه داده SQLite

استفاده از Dapper برای اقدامات اولیه CRUD و ادغام اولیه در یک پروژه Net Web Api با استفاده از الگوی مخزن

Dapper را برای خواندن و نوشتن از/به پایگاه داده Sqlite نصب و پیکربندی کنید

توجه: مطالب آموخته شده در این آموزش را می توان برای هر برنامه .Net که نیاز به تعامل با پایگاه داده دارد، اعمال کرد.

فهرست مطالب:

    داپر چیست؟

    مزایای داپر

    چالش های داپر

    شروع شدن

    نحوه اضافه کردن وابستگی های پروژه

    نحوه حذف کد دیگ بخار

    نحوه اجرای برنامه برای اولین بار

    نحوه ایجاد پایگاه داده SQLite و ایجاد جدول مشتریان

    CRUD - ایجاد، خواندن، به روز رسانی، حذف

    تزریق وابستگی - ثبت رابط ها

    نحوه اتصال مخزن به نقاط پایانی API Web

    نحوه تست API

    کلمات پایانی

داپر چیست؟

Dapper یک "micro ORM" است که دسترسی داده های سبک وزن و با کارایی بالا را با حداقل انتزاع فراهم می کند. به پرس و جوهای SQL متکی است (اینها پرس و جوهایی هستند که هنگام تعامل با پایگاه داده SQL استفاده می کنید) به عنوان مثال:
SELECT * FROM CUSTOMERS WHERE ID = 1 ، نگاشت نتایج به طور مستقیم به اشیا.

در اینجا نحوه توصیف Dapper's README این است که چه کاری انجام می دهد:

این [Dapper] یک API ساده و کارآمد برای فراخوانی SQL با پشتیبانی از دسترسی همزمان و ناهمزمان به داده‌ها فراهم می‌کند و امکان درخواست‌های بافر و غیر بافر را فراهم می‌کند. – مخزن Dapper github README

بیایید به یک مثال نگاه کنیم تا ببینیم چگونه کار می کند.

بگویید ما یک شی مانند این داریم:

 public class Customer { public int ID {get;set;} public string FirstName {get;set;} public string LastName {get;set} public string Email {get;set;} public DateTime DOB {get;set;} public Customer(){ } }

یک ORM (نگاشت شی - رابطه ای) مانند یک مترجم بین یک زبان برنامه نویسی و یک پایگاه داده است که به نرم افزار اجازه می دهد تا با استفاده از اشیاء برنامه نویسی آشنا به جای پرس و جوهای مستقیم SQL با پایگاه های داده تعامل داشته باشد و مدیریت و دستکاری داده ها را در برنامه ها آسان تر می کند.

از آنجایی که Dapper یک "Micro-ORM" است، در حد وسط بین SQL مستقیم و یک ORM کامل مانند Entity Framework (EF) عمل می کند. بسیاری از آپشن های اولیه را دارد، اما با تمام "نفخ" همراه نیست و آن را به یک ابزار پایگاه داده ایمن و سریع‌تر تبدیل می‌کند.

مزایای داپر

کارایی:

Dapper به دلیل عملکرد سبک و سریع خود شناخته شده است، به خصوص زمانی که با مجموعه داده های بزرگ یا عملیات خواندن سنگین سروکار دارید. در مقایسه با Entity Framework دارای ویژگی های بیشتر، سربار کمتری دارد.

کنترل بر روی پرس و جوهای SQL:

Dapper به شما این امکان را می دهد که پرس و جوهای SQL خود را به صراحت بنویسید و کنترل کنید. هنگامی که به کنترل دقیق بر روی SQL تولید شده نیاز دارید یا هنگام برخورد با پرس و جوهای پیچیده، می تواند مفید باشد.

نقطه ضعف این است که شما باید روش های مدیریت و ساختار خود را در پایگاه کد خود پیدا کنید.

انعطاف پذیری نقشه برداری:

Dapper انعطاف پذیری بیشتری را در نگاشت نتایج پایگاه داده به اشیا فراهم می کند. این قرارداد به اندازه Entity Framework تحمیل نمی کند و به شما امکان می دهد نتایج پرس و جو را به راحتی به ساختارهای شی سفارشی نگاشت کنید.

مثال: فرض کنید می خواهید شیء SQL را فوراً به یک ViewModel با اعمال منطق سفارشی به جای شیء DTO، نگاشت کنید.

 var result = connection.QueryAsync<User>("SELECT * FROM Users");
قبل: نگاشت مستقیم به مشتری DTO
 var result = connection.QueryAsync("SELECT FirstName, LastName FROM Users") .Select(row => new { FirstName = row.FirstName.ToUpper(), LastName = row.LastName.ToLower() });
بعد: ویژگی های نگاشت سفارشی Query View Model با منطق سفارشی

حداقل انتزاع:

Dapper یک میکرو ORM با حداقل انتزاع است. اگر ترجیح می‌دهید نزدیک‌تر به فلز کار کنید و می‌خواهید کنترل بیشتری روی SQL و کد دسترسی به داده‌ها داشته باشید، Dapper ممکن است مناسب‌تر باشد.

مناسب برای سناریوهای Read-Heavy:

در سناریوهایی که عملکرد خواندن بسیار مهم است، و شما می خواهید پرس و جوها را برای موارد استفاده خاص بهینه کنید، سادگی و کنترل مستقیم Dapper می تواند سودمند باشد.

چالش های داپر

نوشتن دستی پرس و جو SQL:

Dapper از شما می خواهد که پرس و جوهای SQL خام بنویسید. در حالی که این انعطاف‌پذیری را فراهم می‌کند، همچنین به این معنی است که باید ساخت و پارامترسازی پرس و جو را به صورت دستی انجام دهید، که به طور بالقوه شما را در معرض خطرات تزریق SQL قرار می‌دهد (در ادامه در مورد آن صحبت خواهیم کرد) تا با استفاده از Dapper از این کار جلوگیری کنید.

انتزاعات سطح بالا محدود:

Dapper یک API سطح پایین تری در مقایسه با ORM های کامل مانند Entity Framework ارائه می دهد. این بدان معنی است که شما باید کد بیشتری را برای عملیات معمول CRUD بنویسید و فاقد انتزاعات سطح بالا مانند ردیابی خودکار تغییرات و مهاجرت هستید.

بدون پشتیبانی داخلی LINQ

کسانی که با LINQ آشنا هستند می دانند که چقدر می تواند در هنگام پرس و جو از مجموعه داده ها و مخازن داده مفید باشد.

Dapper پشتیبانی داخلی LINQ را ارائه نمی دهد. در حالی که LINQ را می توان همراه با Dapper مورد استفاده قرار داد، اما سطح یکپارچگی و بیانی مشابه یک ORM مانند Entity Framework را ندارد.

کنوانسیون های محدود:

Dapper به اندازه برخی ORM های دیگر دارای قراردادها و پیش فرض ها نیست. این بدان معنی است که شما ممکن است نیاز به ارائه نگاشت ها و پیکربندی های واضح تری در سناریوهای خاص داشته باشید (اما همانطور که قبلاً گفته شد، این بسته به نیاز شما می تواند هم یک نزول و هم یک مزیت باشد).

ویژگی های کمتر برای مدل های داده پیچیده:

Dapper ممکن است برای برنامه‌هایی با مدل‌های داده پیچیده که شامل روابط پیچیده بین موجودیت‌ها هستند، انتخاب ایده‌آلی نباشد، زیرا مدیریت چنین روابطی ممکن است به تلاش دستی بیشتری نیاز داشته باشد.

بدون بارگذاری تنبل:

Dapper از بارگیری تنبل خارج از جعبه پشتیبانی نمی کند. اگر به بارگذاری تنبل برای موجودیت های مرتبط نیاز دارید، ممکن است لازم باشد خودتان آن را پیاده سازی کنید یا سایر ORM ها مانند Entity Framework را که این ویژگی را ارائه می دهند در نظر بگیرید.

شروع شدن

من قصد دارم در این آموزش به شما یاد بدهم که چگونه پروژه و تمام وابستگی های مورد نیاز را با استفاده از CLI به جای ویرایشگر کد / IDE راه اندازی کنید. من این کار را انجام می دهم زیرا:

    خواهید دید که انجام این نوع کارها در ترمینال چقدر آسان است

    با استفاده از CLI در مقابل تکیه بر منوها و ابزارهای زمینه، به ایجاد اعتماد به نفس شما کمک می کند

    این به شما نشان می دهد که استفاده از ترمینال چقدر سریعتر از استفاده از رابط کاربری گرافیکی (رابط کاربری گرافیکی) است.

پس ویرایشگر خود را به دایرکتوری ریشه پروژه های شخصی خود باز کنید.

اکنون ترمینال را باز کرده و به آن پوشه در ترمینال خود بروید. من تمایل دارم پوشه پروژه هایم را برای دسترسی آسان نزدیک به ریشه خود نگه دارم. به عنوان مثال من دارم:

ترمینال «cd git» <a href= را به پوشه git در سیستم عامل Mac root نشان می دهد" srcset="https://www.freecodecamp.org/news/content/images/size/w600/2024/01/image-123.png 600w, https://www.freecodecamp.org/news/content/images/2024/01/image-123.png 788w" width="788" height="206" loading="lazy">
تصویری که پنجره ترمینال را با دستور cd به پوشه git نشان می دهد

اگر قبلاً نمی دانستید، می توانید با استفاده از دستور cd (تغییر دایرکتوری) به پوشه ها بروید.

پس از ورود به پوشه پروژه های خود، دستور زیر را بنویسید:

dotnet new webapi -n FCC_Dapper

پس این چه می کند؟

یک پوشه جدید به نام FCC_Dapper در فهرست پروژه های شما ایجاد می کند

یک پروژه WebApi .Net 8 جدید به نام FCC_Dapper ایجاد می کند

با استفاده از دستور cd FCC_Dapper به پوشه پروژه بروید.

نکته: هنگام استفاده از ترمینال، به سادگی cd F تایپ کرده و Tab را فشار دهید. این به طور خودکار تا آنجا که می تواند مسیر فایل را تکمیل می کند (تا زمانی که چندین مورد منطبق را پیدا کند). اگر فقط 1 مورد منطبق پیدا شود، تکمیل خودکار مسیر کامل را به پایان می‌رساند و cd FCC_Dapper را برای شما باقی می‌گذارد. اگر چند مورد پیدا شد، دوباره Tab را فشار دهید و چندین گزینه را به شما نشان می دهد.

نحوه اضافه کردن وابستگی های پروژه

دستور زیر را اجرا کنید تا تمام وابستگی های مورد نیاز را اضافه کنید:

 dotnet add package Dapper && dotnet add package Microsoft.Data.Sqlite && dotnet add package Microsoft.Extensions.Configuration && dotnet add package Microsoft.Extensions.DependencyInjection.Abstractions
دستور CLI برای نصب Project Dependencies

نکته: با دستورات ترمینال، می‌توانید آنها را در یک دستور ترمینال بزرگ با استفاده از ; یا کاراکترهای && .

در اینجا یک مثال است:

 dotnet add package Dapper; dotnet add package Microsoft.Data.SQLite

یا:

 dotnet add package Dapper && dotnet add package Microsoft.Data.SQLite

کاراکتر && مانند یک چک بولی عمل می کند، به این صورت که فرمان دوم فقط در صورتی اجرا می شود که اولی خراب نشود/خطا کند.

اکنون باید فایل ها و پوشه های زیر را در ویرایشگر کد / IDE خود مشاهده کنید:

تصویری <a href= که ساختار پوشه پروژه را پس از اجرای دستورات اولیه نشان می دهد" srcset="https://www.freecodecamp.org/news/content/images/size/w600/2024/01/image-124.png 600w, https://www.freecodecamp.org/news/content/images/2024/01/image-124.png 760w" width="760" height="490" loading="lazy">
تصویری که ساختار پوشه پروژه را پس از اجرای دستورات اولیه نشان می دهد

نحوه حذف کد دیگ بخار

فایل Program.cs را باز کنید و تمام کدهای boilerplate را که از قبل پر شده است حذف کنید. می توانید با خیال راحت کد زیر را از Program.cs حذف کنید:

 var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; app.MapGet("/weatherforecast", () => { var forecast = Enumerable.Range(1, 5).Select(index => new WeatherForecast ( DateOnly.FromDateTime(DateTime.Now.AddDays(index)), Random.Shared.Next(-20, 55), summaries[Random.Shared.Next(summaries.Length)] )) .ToArray(); return forecast; }) .WithName("GetWeatherForecast") .WithOpenApi();

نحوه اجرای برنامه برای اولین بار

حالا اجرا کنید:

 dotnet dev-certs https --trust

این امر از شما خواسته می‌شود گواهی‌های توسعه را بپذیرید و به شما امکان می‌دهد وب api را با استفاده از پروتکل https اجرا کنید. پس اگر اکنون اجرا می کنید:

 dotnetrun --launch-profile https

شما باید موارد زیر را در کنسول خود مشاهده کنید:

 ... info: Microsoft.Hosting.Lifetime[14] Now listening on: https://localhost:{port} ...

در ابتدا صفحه 404 مانند زیر را مشاهده خواهید کرد:

تصویر: تصویر صفحه نمایش 404 یافت نشد
تصویر: تصویر صفحه نمایش 404 یافت نشد

با این حال، اگر /swagger به URL اضافه کنید، اکنون رابط کاربری Swagger را خواهید دید که به ما اطلاع می دهد که هیچ نقطه پایانی تعریف نشده است (که در این مرحله قابل انتظار است)

تصویر: اسکرین شات <a href= از Swagger UI که هیچ نقطه پایانی را نشان نمی‌دهد" srcset="https://www.freecodecamp.org/news/content/images/size/w600/2024/01/image-145.png 600w, https://www.freecodecamp.org/news/content/images/size/w1000/2024/01/image-145.png 1000w, https://www.freecodecamp.org/news/content/images/size/w1600/2024/01/image-145.png 1600w, https://www.freecodecamp.org/news/content/images/size/w2400/2024/01/image-145.png 2400w" sizes="(min-width: 1200px) 1200px" width="2000" height="1208" loading="lazy">
تصویر: اسکرین شات از Swagger UI که هیچ نقطه پایانی را نشان نمی‌دهد

Swagger مجموعه ای از ابزارها است که به توسعه دهندگان اجازه می دهد تا نقاط پایانی API خود را طراحی، مشاهده، آزمایش و مستند کنند. Swagger UI عنصر رابط کاربری مجموعه ابزار است که به شما امکان می دهد از طریق یک رابط وب با یکدیگر تعامل داشته باشید و نقاط پایانی خود را مشاهده کنید.

OpenAPI نام رسمی مشخصات است. توسعه مشخصات توسط ابتکار OpenAPI، که شامل 30 سازمان از مناطق مختلف دنیای فناوری - از جمله مایکروسافت، گوگل، IBM، و CapitalOne می‌شود، تشویق می‌شود. نرم‌افزار Smartbear، که شرکتی است که توسعه ابزارهای Swagger را رهبری می‌کند، همچنین یکی از اعضای OpenAPI Initiative است که به پیشروی تکامل مشخصات - Swagger، Smartbear کمک می‌کند.

نحوه ایجاد پایگاه داده SQLite و ایجاد جدول مشتریان

ایجاد فایل پایگاه داده SQLite.

دو راه برای انجام این کار وجود دارد:

    گزینه 1: در خط فرمان / ترمینال sqlite3 customers.db را اجرا کنید

    گزینه 2: در ویرایشگر کد / IDE خود یک فایل به نام customers.db ایجاد کنید

اگر گزینه 1 را انتخاب کنید، خروجی را در ترمینال خود مشاهده خواهید کرد، مانند زیر:

 SQLite version 3.32.2 2021-07-12 15:00:17 Enter ".help" for usage hints.

دستور .database را در خط فرمان / ترمینال خود اجرا کنید و نام و فایل های پایگاه داده پیوست شده را فهرست می کند.

اتصال Web API به پایگاه داده

با باز کردن فایل، یک رشته اتصال پیش‌فرض به فایل appsettings.json اضافه کنید. کد زیر را به بالای فایل، در اولین { . پس فایل appsettings.json شما اکنون باید به شکل زیر باشد:

 { "ConnectionStrings": { "DefaultConnection": "Data Source=customers.db" }, "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "AllowedHosts": "*" }

نحوه ایجاد جدول مشتری و در ابتدا دانه بندی داده ها

مرحله 1: یک پوشه پایگاه داده و یک فایل DatabaseUtilities.cs ایجاد کنید.

دو راه اصلی برای انجام این کار وجود دارد:

گزینه 1: یک پوشه به نام "Database" و در داخل پوشه یک فایل به نام DatabaseUtilities.cs ایجاد کنید.

گزینه 2: از CLI استفاده کنید

خط فرمان ویندوز:

 mkdir Database && cd Database && echo. > DatabaseUtilities.cs

ترمینال سیستم عامل مک:

 mkdir Database && cd Database && touch DatabaseUtilities.cs

مرحله 2: کد زیر را به فایل DatabaseUtilities.cs اضافه کنید:

 using Dapper; using Microsoft.Data.Sqlite; public static class DBUtilities { public static async Task<bool> InitializeDBAsync(this IWebApplication app) { var connectionString = app.Configuration.GetConnectionString("DefaultConnection"); var createSQL = @"CREATE TABLE IF NOT EXISTS Customer ( ID INTEGER PRIMARY KEY AUTOINCREMENT, FirstName TEXT, LastName TEXT, DOB DATE, Email TEXT );"; var insertSQL = @" INSERT INTO Customer (FirstName, LastName, DOB, Email) VALUES ('Tony', 'Stark', '1970-05-29', 'tony.stark@example.com'), ('Bruce', 'Wayne', '1972-11-11', 'bruce.wayne@example.com'), ('Peter', 'Parker', '1995-08-10', 'peter.parker@example.com'), ('Diana', 'Prince', '1985-04-02', 'diana.prince@example.com'), ('Clark', 'Kent', '1980-07-18', 'clark.kent@example.com'), ('Natasha', 'Romanoff', '1983-06-25', 'natasha.romanoff@example.com'), ('Wade', 'Wilson', '1977-02-19', 'wade.wilson@example.com'), ('Hal', 'Jordan', '1988-09-05', 'hal.jordan@example.com'), ('Steve', 'Rogers', '1920-07-04', 'steve.rogers@example.com'), ('Selina', 'Kyle', '1982-12-08', 'selina.kyle@example.com');"; using var connection = new SqliteConnection(connectionString); connection.Open(); using var transaction = connection.BeginTransaction(); try { await connection.ExecuteAsync(createSQL, transaction: transaction); // Check if the Customer table exists var tableExists = await connection.QueryFirstOrDefaultAsync<int>( "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='Customer';", transaction: transaction); if (tableExists > 0) { // Table exists and populated, no need to seed database again return true; } await connection.ExecuteAsync(insertSQL, transaction: transaction); // Commit the transaction if everything is successful transaction.Commit(); connection.Close(); return true; } catch (Exception ex) { Console.WriteLine(ex.Message); // An error occurred, rollback the transaction transaction.Rollback(); connection.Close(); return false; } } }

بیایید این کد را ذره ذره تجزیه کنیم:

ابتدا یک متد افزونه در کلاس WebApplication ایجاد می کند

سپس SQL مورد نیاز برای ایجاد یک پایگاه داده را ایجاد می کند

سپس یک اتصال به پایگاه داده SQLite با استفاده از نوع SqliteConnection با عبور یک رشته اتصال ایجاد می کنیم و به برنامه اطلاع می دهیم که از فایل customers.db به عنوان منبع داده استفاده کند.

در نهایت، دستورات SQL را به صورت ناهمزمان اجرا می‌کند، که در یک بلوک try/catch با استفاده از روش‌های توسعه قدرتمند Dapper روی شی connection پیچیده شده است.

تراکنش های SQL

این کد از BeginTransaction API در بسته SQLite استفاده می کند. سینتکس تراکنش روشی بسیار ایمن‌تر برای مدیریت دستورات SQL است که می‌خواهید چندین فرمان یا دستورالعمل‌های بزرگ را اجرا کنید.

 using var connection = new SqliteConnection(connectionString); using var transaction = connection.BeginTransaction();
کد برای شروع تراکنش SQL

دوباره، مانند قبل، از دستور using می کنیم و یک اتصال ایجاد می کنیم - اما این بار یک دستور اضافی (تودرتو) using دستور اضافه می کنیم و یک شی transaction ایجاد می کنیم.

در اینجا می‌توانید اطلاعات بیشتری درباره تراکنش‌های SQL بخوانید، اما در اصل می‌تواند از اجرای دستورات بعدی در صورت خطا/یا اجرا نشدن دستور قبلی جلوگیری کند. در سناریویی که فرمان دوم به فرمان قبلی که با موفقیت اجرا می شود، وابستگی دارد عالی است.

اجرای دستورات و rollbacks

حالا بیایید درباره Rollbacks صحبت کنیم. بازگشت‌ها فقط چیزی نیست که در سوپرمارکت پیدا کنید، بلکه یکی از آپشن های عالی در تراکنش‌های SQL است.

در SQL، به یک ROLLBACK مانند یک دکمه لغو فکر کنید. پس ، اگر در وسط انجام وظایف / دستورات در پایگاه داده هستید (مانند گفت ن، به‌روزرسانی یا حذف چیزها)، وای! مشکلی پیش می‌آید یا نظرتان تغییر می‌کند، می‌توانید ROLLBACK را بزنید. قبل از شروع هر یک از دستورات داخل TRANSACTION ، شما را به شروع حالت قبلی خود برمی گرداند و تمام تغییراتی را که در آن تراکنش ایجاد کرده اید پاک می کند.

فکر کنید که مانند Git است: شما همه تغییرات پایگاه داده خود را انجام داده اید و اکنون می خواهید آنها را به شعبه خود (در مورد ما یک پایگاه داده) متعهد کنید. یا، می‌توانید با نادیده گرفتن تغییرات خود (در مورد ما با فراخوانی .Rollback() "برگرداندن" آنها را انتخاب کنید.

 try { await connection.ExecuteAsync(createSQL, transaction: transaction); // Check if the Customer table exists var tableExists = await connection.QueryFirstOrDefaultAsync<int>( "SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name='Customer';", transaction: transaction); if (tableExists > 0) { // Table exists and populated, no need to seed database again return true; } await connection.ExecuteAsync(insertSQL, transaction: transaction); // Commit the transaction if everything is successful transaction.Commit(); connection.Close(); return true; } catch (Exception ex) { Console.WriteLine(ex.Message); // An error occurred, rollback the transaction transaction.Rollback(); connection.Close(); return false; } }

قرار دادن منطق فرمان در داخل یک بلوک try/catch به ما امکان می‌دهد تا زمان وقوع بازگشت تراکنش را کنترل کنیم. در حالی که SQLite به طور خودکار تراکنش‌ها را در صورت عدم اطلاع از Commit برمی‌گرداند و یک استثنا ایجاد کرده است، برخی از سیستم‌های پایگاه داده ممکن است در صورت بروز خطا، تراکنش را به‌طور خودکار برگردانند یا نکنند - این تضمین جهانی نیست.

من شخصاً ترجیح می‌دهم صراحتاً به تعهد و بازگرداندن تراکنش‌های خودم رسیدگی کنم، تا این کنترل را بر آنچه که اتفاق می‌افتد و نمی‌شود داشته باشم.

کد transaction.Commit() به پایگاه داده اطلاع می دهد تا تراکنش را انجام دهد - یعنی تمام تغییراتی که در پایگاه داده انجام شده است.

مرحله 3: روش InitializeDBAsync را به فایل Program.cs اضافه کنید

به فایل Program.cs خود برگردید و روش InitializeDBAsync را قبل از app.Run() فراخوانی کنید:

 // Initialise the Db await app.InitializeDBAsync(); app.Run();

برنامه را دوباره اجرا کنید...

یک بار دیگر در ترمینال خود در پوشه FCC_Dapper اجرا کنید:

 dotnetrun --launch-profile https

اکنون برنامه شما را می سازد و اجرا می کند، جدول customer را در فایل customers.db شما راه اندازی می کند و داده ها را تخمین می زند.

CRUD - ایجاد، خواندن، به روز رسانی، حذف

قبل از اینکه به نحوه نوشتن متدهای CRUD بپردازیم، اجازه دهید به اختصار چند روش مفید و متداول توسعه Dapper را تحلیل کنیم.

QueryAsync<T>() - یک پرس و جو را اجرا می کند و مجموعه نتایج را به یک فهرست با تایپ قوی از اشیاء نوع T نگاشت می کند.

QueryFirstOrDefaultAsync<T> – شبیه Query<T> است، اما اگر هیچ نتیجه ای یافت نشد، اولین نتیجه یا مقدار پیش فرض را برمی گرداند.

ExecuteAsync() - یک عبارت SQL غیر پرس و جو را اجرا می کند (به عنوان مثال، INSERT، UPDATE، DELETE) و تعداد ردیف های تحت تأثیر را برمی گرداند.

QueryMultiple() - یک پرس و جو را اجرا می کند که چندین مجموعه نتیجه را برمی گرداند و به شما امکان می دهد چندین مجموعه از نتایج را از یک پرس و جو بخوانید.

 using (var multi = connection.QueryMultiple("SELECT * FROM Table1; SELECT * FROM Table2")) { var result1 = multi.Read<MyClass1>().ToList(); var result2 = multi.Read<MyClass2>().ToList(); }
مثالی از استفاده از MultiQuery

QuerySingle<T>: - یک پرس و جو را اجرا می کند و دقیقاً یک نتیجه را انتظار دارد. اگر صفر یا بیش از یک نتیجه وجود داشته باشد، یک استثنا می اندازد.

چگونه یک مخزن برای روش های خام بسازیم

برای این نسخه ی نمایشی، من یک مخزن سریع با استفاده از الگوی مخزن تهیه کرده ام که می توانید در این مقاله درباره آن اطلاعات بیشتری کسب کنید.

گزینه 1: همه فایل ها و پوشه ها را به صورت دستی در IDE / Code Editor ایجاد کنید

ICustomerRepository.cs

IGenericRepository.cs

IUnitOfWork.cs

UnitOfWork.cs

CustomerRepsository.cs

گزینه 2 (توصیه می شود): فایل هایی را با استفاده از شروع CLI در پوشه FCC_Dapper خود در ترمینال ایجاد کنید.

خط فرمان ویندوز:

 cd Database && type nul > ICustomerRepository.cs && type nul > CustomerRepository.cs && type nul > IGenericRepository.cs && type nul > IUnitOfWork.cs && type nul > UnitOfWork.cs
دستور Command Prompt برای ایجاد فایل های مرتبط برای Dapper Repository

سیستم عامل مک:

 cd Database; touch IGenericRepository.cs ICustomerRepository.cs CustomerRepository.cs IUnitOfWork.cs UnitOfWork.cs
دستور ترمینال ایجاد پوشه پایگاه داده و 6 فایل و پوشه کلاس Csharp را نشان می دهد

امیدوارم در این مرحله شما ارزش و کارایی استفاده از CLI را ببینید.

پس از ایجاد این فایل ها، کد زیر را در هر یک از آنها قرار دهید:

 public interface IUnitOfWork { ICustomerRepository Customers { get; } }
IUnitOfWork.cs
 using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace FCC_Dapper; public interface IGenericRepository<T> where T : class { Task<T?> GetByIdAsync(int id); Task<IReadOnlyList<T>> GetAllAsync(); Task<int> AddAsync(T entity); Task<int> UpdateAsync(T entity); Task<int> DeleteAsync(int id); }
IGenericRepository.cs
 using FCC_Dapper; public interface ICustomerRepository : IGenericRepository<Customer> { }
ICustomerRepository.cs

این مورد بعدی مهمترین است، زیرا این کلاس با تمام منطق واقعی است:

 using Dapper; using FCC_Dapper; using Microsoft.Data.Sqlite; public class CustomerRepository : ICustomerRepository { public CustomerRepository(IConfiguration configuration) { this._configuration = configuration; } private readonly IConfiguration _configuration; public async Task<int> AddAsync(Customer customer) { var sql = "INSERT INTO Customer (firstName, lastName, email, dob) VALUES (@FirstName, @LastName, @Email, @DOB)"; using var connection = new SqliteConnection(_configuration.GetConnectionString("DefaultConnection")); return await connection.ExecuteAsync(sql, customer); } public async Task<int> DeleteAsync(int id) { var sql = "DELETE FROM Customer WHERE ID = @ID"; using var connection = new SqliteConnection(_configuration.GetConnectionString("DefaultConnection")); var result = await connection.ExecuteAsync(sql, new { ID = id }); return result; } public async Task<IReadOnlyList<Customer>> GetAllAsync() { var sql = "SELECT * FROM Customer"; using var connection = new SqliteConnection(_configuration.GetConnectionString("DefaultConnection")); var result = await connection.QueryAsync<Customer>(sql); return result.ToList(); } public async Task<Customer?> GetByIdAsync(int id) { var sql = "SELECT * FROM Customer WHERE ID = @ID"; using var connection = new SqliteConnection(_configuration.GetConnectionString("DefaultConnection")); var result = await connection.QuerySingleOrDefaultAsync<Customer>(sql, new { ID = id }); return result ?? null; } public async Task<int> UpdateAsync(Customer entity) { var sql = "UPDATE Customer SET FirstName = @FirstName, LastName = @LastName, DOB = @DOB, Email = @Email WHERE ID = @ID"; using var connection = new SqliteConnection(_configuration.GetConnectionString("DefaultConnection")); var result = await connection.ExecuteAsync(sql, entity); return result; } }

در این کلاس CustomerRepository ، ما چندین روش مختلف را راه‌اندازی می‌کنیم که می‌توان آن‌ها را در هر جایی از WebApi فراخوانی کرد.

با انتزاع منطق دستورات عمل بر روی یک منبع داده، اگر بخواهیم از Dapper به EF (Entity Framework)، یکی دیگر از معروف‌ترین .Net ORM، تغییر کنیم، بقیه برنامه همچنان کار می‌کند.

هر روش بسیار ساده است و اقدامات زیر را انجام می دهد:

رشته اتصال را بازیابی می کند

یک SQLiteConnection می سازد

یک رشته SQL می سازد

SQL را با استفاده از شی اتصال اجرا می کند.

چند تفاوت جزئی وجود دارد. ممکن است متوجه شده باشید که برخی از نحوها دارای نمادهای @ در جلوی نام متغیرها هستند. این مکان‌ها برای پرس و جوهای پارامتری شده هستند.

نگاهی کوتاه به SQL Injection

Dapper پارامترسازی داخلی کوئری ها را ارائه می دهد، به این معنی که می تواند از شما در برابر حملات SQL Injection محافظت کند.

همانطور که در مقاله پیوند داده شده در بالا توضیح داده شد، می توانید یک متغیر رشته ای را برای ساخت SQL خود تعریف کنید:

 var rawSqlString = $"SELECT * FROM CUSTOMER WHERE FirstName={searchString}"; //output: //SELECT * FROM CUSTOMER WHRE FIRSTNAME=Grant; DROP TABLE Customer
CSharp از چگونگی تزریق SQL در کد

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

اگر به سادگی مقدار را بدون هیچ گونه پاکسازی دریافت کنید، دستور SQL آنها را به رشته پرس و جوی SQL خود تزریق می کنید، و همانطور که می دانیم ; کاراکتر دستورات SQL را جدا می کند.

معنی آنچه در واقع اتفاق خواهد افتاد این است:

 SELECT * FROM Customer WHERE FirstName = Grant; // then it will run DROP TABLE Customer
دستور SQL که نتیجه SQL Injection را نشان می دهد

Dapper پرس و جوهای پارامتری شده را ترجیح می دهد، به این معنی که می توانید متغیرهای پارامتر را به رشته SQL خود منتقل کنید، و سپس با متغیر واقعی که از یک موجودیت یا شی ناشناس به آن اختصاص داده شده است، جایگزین می شوند.

بیایید با استفاده از یک پرس و جوی پارامتری به مثال بالا نگاه کنیم:

 var userInput = "GWeaths; Drop Table User"; string sql = "SELECT * FROM Users WHERE Username = @Username"; var user = connection.ExecuteAysnc<User>(sql, new { Username = userInput});
نسخه پارامتری شده تلاش برای تزریق SQL با استفاده از Dapper

با استفاده از یک شی ناشناس و انتساب ویژگی Username به مقدار userInput ، کوئری را پارامتری کرده ایم.

به این معنی که خروجی کوئری SQL به این صورت خواهد بود:

 SELECT * FROM Users WHERE Username = "GWeaths; Drop Table User";
مثال SQL پارامتری شده

تفاوت عمده در اینجا این است که معیارهای جستجوی username اکنون به جای یک دستور SQL مانند مقدار جستجوی ستون در نظر گرفته شده است. این سطح حفاظت بیشتری را ارائه می دهد. روش‌های زیادی برای محافظت در برابر تزریق SQL وجود دارد، اما اینها برای این آموزش نیستند.

تطبیق پارامتر

همانطور که ممکن است از کدی که استفاده می کنیم متوجه شده باشید، ما از یک شی ناشناس استفاده نمی کنیم - ما به طور مستقیم به نهاد customer خود منتقل می کنیم. سپس Dapper پارامترها و مکان‌نمای آنها (مقادیر @ ) را به نام ویژگی موجودیت نگاشت می‌کند.

 public async Task<int> AddAsync(Customer customer) { var sql = "INSERT INTO Customer (id, firstName, lastName, email, dob) VALUES (@ID, @FirstName, @LastName, @Email, @DOB)"; using var connection = new SqliteConnection(_configuration.GetConnectionString("DefaultConnection")); return await connection.ExecuteAsync(sql, customer); }
نمونه ای از پرس و جوی پارامتری شده با استفاده از شی مستقیم

تزریق وابستگی - ثبت رابط ها

یک فایل به نام ServiceRegistration.cs در پوشه اصلی پروژه ایجاد کنید و کد زیر را اضافه کنید:

 public static class ServiceRegistration { public static void AddInfrastructure(this IServiceCollection services) { services.AddScoped<ICustomerRepository, CustomerRepository>(); services.AddScoped<IUnitOfWork, UnitOfWork>(); } }
ServiceRegistration.cs برای ثبت کلاس های Repository با Dependency Injection

این کلاس یک متد افزونه به IServiceCollection به نام AddInfrastructure() اضافه می کند که به ما این امکان را می دهد تا به راحتی کلاس های Repository و UnitOfWork خود را برای Dependency Injection اضافه کنیم.

سپس می توانیم این را در فایل Program.cs خود فراخوانی کنیم:

 using Dapper; using FCC_Dapper; using Microsoft.Data.Sqlite; var builder = WebApplication.CreateBuilder(args); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); //Add the line here builder.Services.AddInfrastructure(); var app = builder.Build();

اتصال مخزن به نقاط پایانی Web API

ما از نقاط پایانی API زیر استفاده خواهیم کرد:

/get

/get/{id}

/create

/update

/delete

مرحله 1: با استفاده از حداقل API، نقاط پایانی را به Program.cs اضافه کنید.

پس فایل Program.cs شما اکنون باید به این شکل باشد:

 using Dapper; using FCC_Dapper; using Microsoft.Data.Sqlite; var builder = WebApplication.CreateBuilder(args); // Add services to the container. // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddInfrastructure(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); // initialize the database here await app.InitializeDBAsync(); app.Run(); app.MapGet("/get", async (IUnitOfWork unitOfWork) => { var customers = await unitOfWork.Customers.GetAllAsync(); return Results.Ok(customers); }); app.MapGet("/get/{id}", async (IUnitOfWork unitOfWork, int id) => { var data = await unitOfWork.Customers.GetByIdAsync(id); return data != null ? Results.Ok(data) : Results.Problem("Failed to locate customer"); }); app.MapPost("/create", async (IUnitOfWork unitOfWork) => { var count = await unitOfWork.Customers.AddAsync(new Customer { FirstName = "Stan", LastName = "Lee", DOB = new DateTime(1945, 1, 1), Email = "stan.lee@test.com" }); return count > 0 ? Results.Ok("Customer created successfully") : Results.Problem("Unable to create customer"); }); app.MapPut("/update", async (IUnitOfWork unitOfWork, Customer customer) => { var count = await unitOfWork.Customers.UpdateAsync(customer); return count > 0 ? Results.Ok("Customer updated successfully") : Results.Problem("Customer failed to update"); }); app.MapPost("/delete", async (IUnitOfWork unitOfWork, int id) => { var count = await unitOfWork.Customers.DeleteAsync(id); return count > 0 ? Results.Ok("Customer deleted successfully") : Results.Problem("Customer failed to delete"); });

کد بالا این است:

راه اندازی هر یک از نقاط پایانی با استفاده از حداقل API

وابستگی تزریق یک کلاس UnitOfWork به هر نقطه پایانی

فراخوانی متد مربوطه در CustomerRepository

برگرداندن یک پاسخ HTTP بر اساس معیارهای خاصی که از مخزن بازگردانده شده است.

اینجاست که می توانید قدرت واقعی انتزاع CustomerRepository را به دور از منطق تجاری برنامه از طریق UnitOfWork ببینید. فایل Program.cs مربوط به ابزار پایگاه داده ای است که ما از آن استفاده می کنیم، تنها چیزی که به آن اهمیت می دهد رسیدگی به تماس با کنترل کننده مربوطه و مدیریت پاسخ به مشتری است.

نحوه تست API

می توانید API را با اجرای دستور قبلی آزمایش کنید:

 dotnet run --launch-profile https

سپس وقتی مرورگر شما باز شد، به /swagger بروید و اکنون خواهید دید:

تصویر-144
تصویر: اسکرین شات از Swagger UI که تمام نقاط پایانی API را نشان می دهد

ادامه دهید، با API بازی کنید. سعی کنید به تمام نقاط پایانی در رابط کاربری Swagger ضربه بزنید.

برای مثال امتحان کنید:

نقطه پایان ایجاد را بزنید

نقطه پایانی /get بزنید و تحلیل کنید که مشتری «Stan Lee» تازه ایجاد شده شما در پایین فهرست شده است

نقطه پایانی /update را بزنید، شاید نام یا ایمیل او را به روز کنید

نقطه پایانی /delete در شناسه مشتری تازه ایجاد شده Stan Lee و سپس /getById با همان شناسه ضربه بزنید و ببینید چه اتفاقی می افتد.

کلمات پایانی

در اینجا شما آن را دارید - یک Api وب دات نت کاملاً کارآمد، با پشتیبانی Dapper و یک فایل پایگاه داده محلی SQLite، با استفاده از یک پیاده سازی اساسی از الگوی مخزن.

در این آموزش یاد گرفتید:

چگونه Dapper را به پروژه اضافه کنیم

مزایا و معایب Dapper در مقایسه با رقبای خود

روش ها و برنامه های گفت نی پایه Dapper

آشنایی با تراکنش های SQL و پیشگیری از تزریق SQL

پرس و جوهای پارامتری شده

امیدوارم این دوره برای شما مفید بوده باشد، و مثل همیشه هر سوالی دارید، لطفاً با من تماس بگیرید یا من را در Twitter / X دنبال کنید.

خبرکاو

ارسال نظر

دیدگاه‌ها بسته شده‌اند.


تبليغات ايهنا تبليغات ايهنا

تمامی حقوق مادی و معنوی این سایت متعلق به خبرکاو است و استفاده از مطالب با ذکر منبع بلامانع است