0Pricing
C# Academy · บทเรียน

SQL ดิบ โพรซีเยอร์ที่จัดเก็บ และการแทรกค่า

เรียกใช้ SQL ดิบอย่างปลอดภัยด้วย FromSqlRaw, FromSqlInterpolated และโพรซีเยอร์ที่จัดเก็บ พร้อมหลีกเลี่ยงการแทรกคำสั่ง

SQL ดิบ โพรซีเยอร์ที่จัดเก็บ และการแทรกค่า เป็นบทเรียน C# Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน C# Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน

เมื่อใดควรใช้ SQL ดิบใน EF Core

คิวรีส่วนใหญ่ทำงานได้ดีกับ LINQ แต่บางสถานการณ์จำเป็นต้องใช้ SQL ดิบ เช่น การวิเคราะห์ที่ซับซ้อน ฟังก์ชันเฉพาะของผู้ให้บริการ โพรซีเยอร์ที่จัดเก็บไว้ หรือคิวรีที่เน้นประสิทธิภาพและ LINQ แปลได้ไม่ดี

EF Core มีวิธีที่ปลอดภัยสำหรับการใช้ SQL ดิบโดยไม่ลดทอนความปลอดภัย

FromSqlRaw: คิวรีที่ใช้พารามิเตอร์อย่างปลอดภัย

FromSqlRaw จะเรียกใช้ SQL ดิบและแมปผลลัพธ์กลับเป็นเอนทิตี ควรใช้ตัวแทน {0} เสมอ — ห้ามต่อสตริง — เพื่อป้องกันการฉีด SQL

// SAFE: parameterized
var products = await _db.Products
    .FromSqlRaw("SELECT * FROM Products WHERE Price > {0}", 100m)
    .ToListAsync();

// DANGEROUS: never do this
// FromSqlRaw("SELECT * FROM Products WHERE Price > " + price)

FromSqlInterpolated: ความปลอดภัยของสตริงแทรกค่า

FromSqlInterpolated รับสตริงแทรกค่าของ C# โดย EF Core จะแปลงค่าที่แทรกไว้เป็นพารามิเตอร์ SQL โดยอัตโนมัติ จึงปลอดภัยและอ่านเข้าใจง่าย

decimal minPrice = 100m;
string category = "Electronics";

var products = await _db.Products
    .FromSqlInterpolated(
        $"SELECT * FROM Products WHERE Price > {minPrice} AND Category = {category}")
    .OrderBy(p => p.Name)
    .ToListAsync();
// Values become @p0, @p1 parameters automatically

การประกอบคิวรี LINQ หลัง FromSql

คุณสามารถเรียกตัวดำเนินการ LINQ ต่อจาก FromSql ได้ EF Core จะครอบ SQL ดิบของคุณไว้เป็นคิวรีย่อย แล้วนำ LINQ ไปใช้ต่อด้านบน ซึ่งมีประโยชน์สำหรับการเพิ่มการกรองหรือการแบ่งหน้า

decimal min = 50m;
var page = await _db.Products
    .FromSqlInterpolated($"SELECT * FROM Products WHERE Price > {min}")
    .Where(p => p.IsActive)        // additional LINQ filter
    .OrderBy(p => p.Price)
    .Skip(0).Take(20)              // pagination
    .ToListAsync();
// Generates: SELECT ... FROM (raw SQL) AS p WHERE ...

การเรียกใช้โพรซีเยอร์ที่จัดเก็บไว้

เรียกโพรซีเยอร์ที่จัดเก็บไว้ซึ่งส่งคืนแถวของเอนทิตีผ่าน FromSqlRaw สำหรับโพรซีเยอร์ที่ไม่ส่งคืนแถว เช่น INSERT/UPDATE ให้ใช้ ExecuteSqlRawAsync

// Stored proc returning Products
var products = await _db.Products
    .FromSqlRaw("EXEC GetProductsByCategory {0}", "Books")
    .ToListAsync();

// Stored proc with no return rows
await _db.Database.ExecuteSqlRawAsync(
    "EXEC ArchiveOldOrders {0}",
    DateTime.UtcNow.AddYears(-1));

ExecuteSqlInterpolated สำหรับ DML

ExecuteSqlInterpolated เรียกใช้คำสั่ง DML (INSERT, UPDATE, DELETE) พร้อมความปลอดภัยของสตริงแทรกค่า โดยจะส่งคืนจำนวนแถวที่ได้รับผลกระทบ

int days = 30;
int affected = await _db.Database.ExecuteSqlInterpolatedAsync(
    $"UPDATE Orders SET Status = 'Expired' WHERE PlacedAt < {DateTime.UtcNow.AddDays(-days)}");

Console.WriteLine($"{affected} orders expired");

การคิวรีชนิดที่ไม่ใช่เอนทิตี

ใช้ SqlQuery<T> (.NET 7+) เพื่อคิวรีชนิดสเกลาร์หรือออบเจ็กต์ถ่ายโอนข้อมูลแบบใดก็ได้ที่ไม่ใช่เอนทิตีของ EF Core โดยไม่จำเป็นต้องมี DbSet

// Query a DTO directly — no entity needed
var stats = await _db.Database
    .SqlQuery<ProductStats>(
        $"SELECT Category, COUNT(*) AS Count, AVG(Price) AS AvgPrice FROM Products GROUP BY Category")
    .ToListAsync();

public record ProductStats(string Category, int Count, decimal AvgPrice);

ชนิดเอนทิตีที่ไม่มีคีย์สำหรับมุมมอง

แมปมุมมองฐานข้อมูลหรือผลลัพธ์จากคิวรีดิบไปยัง ชนิดเอนทิตีที่ไม่มีคีย์ โดยใช้ HasNoKey() และ ToView() จากนั้นคิวรีได้เหมือนกับ DbSet โดยไม่ต้องมีคีย์หลัก

public class ProductSalesSummary
{
    public string Category { get; set; } = "";
    public int TotalSold { get; set; }
    public decimal Revenue { get; set; }
}

// In OnModelCreating:
modelBuilder.Entity<ProductSalesSummary>()
    .HasNoKey()
    .ToView("vw_ProductSalesSummary");

// Query:
var summary = await _db.Set<ProductSalesSummary>().ToListAsync();

หลีกเลี่ยงข้อผิดพลาดจากการฉีด SQL

กฎสำคัญที่สุดคือ ห้ามต่ออินพุตจากผู้ใช้เข้ากับสตริง SQL โดยตรง ควรใช้คิวรีที่มีพารามิเตอร์เสมอผ่านตัวแทนของ FromSqlRaw, FromSqlInterpolated หรือ LINQ ของ EF Core

// SAFE
var name = userInput;
var result = await _db.Products
    .FromSqlInterpolated($"SELECT * FROM Products WHERE Name = {name}")
    .ToListAsync();

// INJECTION RISK (never do this)
// FromSqlRaw("SELECT * FROM Products WHERE Name = '" + name + "'");

พารามิเตอร์ผลลัพธ์พร้อมทางเลือกสำรอง ADO.NET

EF Core ไม่รองรับพารามิเตอร์ผลลัพธ์ของโพรซีเยอร์ที่จัดเก็บไว้โดยตรง สำหรับกรณีเหล่านี้ให้เปลี่ยนไปใช้ ADO.NET ผ่าน _db.Database.GetDbConnection()

await _db.Database.OpenConnectionAsync();
var conn = _db.Database.GetDbConnection();
using var cmd = conn.CreateCommand();
cmd.CommandText = "EXEC GetOrderCount @CustomerId, @Count OUTPUT";
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new SqlParameter("@CustomerId", 5));
var outParam = new SqlParameter("@Count", SqlDbType.Int) { Direction = ParameterDirection.Output };
cmd.Parameters.Add(outParam);
await cmd.ExecuteNonQueryAsync();
int count = (int)outParam.Value;

ในสถานการณ์จริง: ทางเลือกสำรองสำหรับการค้นหาข้อความเต็ม

LINQ ไม่สามารถแสดงเงื่อนไขการค้นหาข้อความเต็มที่เฉพาะกับฐานข้อมูลได้ ให้ใช้ FromSqlInterpolated เพื่อใช้ความสามารถ FREETEXT หรือ tsvector ดั้งเดิม

string query = "keyboard mechanical";
var results = await _db.Products
    .FromSqlInterpolated(
        $"SELECT * FROM Products WHERE FREETEXT(Name, {query})")
    .AsNoTracking()
    .Take(20)
    .ToListAsync();

ตรวจสอบความเข้าใจ

เหตุใด FromSqlInterpolated จึงปลอดภัยกว่าการต่อสตริงสำหรับคิวรีที่ใช้พารามิเตอร์

สรุป: SQL ดิบใน EF Core

ประเด็นสำคัญ:

  • FromSqlRaw: ใช้ตัวแทน {0} และห้ามต่ออินพุตจากผู้ใช้เข้ากับสตริง
  • FromSqlInterpolated: สตริงแทรกค่าที่ปลอดภัย → พารามิเตอร์ ADO.NET
  • เรียกตัวดำเนินการ LINQ ต่อจาก FromSql เพื่อเพิ่มการกรอง การเรียงลำดับ และการแบ่งหน้า
  • ExecuteSqlInterpolatedAsync: การเรียกใช้ DML อย่างปลอดภัย
  • SqlQuery<T>: คิวรีออบเจ็กต์ถ่ายโอนข้อมูลที่ไม่ใช่เอนทิตี (.NET 7+)
  • ใช้ ADO.NET โดยตรงสำหรับพารามิเตอร์ผลลัพธ์หรือโพรซีเยอร์ที่จัดเก็บไว้ซับซ้อน

คำถามที่พบบ่อย

บทเรียน “SQL ดิบ โพรซีเยอร์ที่จัดเก็บ และการแทรกค่า” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “SQL ดิบ โพรซีเยอร์ที่จัดเก็บ และการแทรกค่า” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส C# Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส C# Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “SQL ดิบ โพรซีเยอร์ที่จัดเก็บ และการแทรกค่า”

เรียกใช้ SQL ดิบอย่างปลอดภัยด้วย FromSqlRaw, FromSqlInterpolated และโพรซีเยอร์ที่จัดเก็บ พร้อมหลีกเลี่ยงการแทรกคำสั่ง คุณปฏิบัติ C# Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน C# Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน C# Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน

บทเรียน “SQL ดิบ โพรซีเยอร์ที่จัดเก็บ และการแทรกค่า” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน C# Academy นี้ได้ไหม

ได้ บทเรียน C# Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. การโหลดแบบทันที แบบขี้เกียจ และแบบชัดแจ้ง
  2. SQL ดิบ โพรซีเยอร์ที่จัดเก็บ และการแทรกค่า
  3. คำสั่งสืบค้นที่คอมไพล์แล้วและประสิทธิภาพ
  4. ตัวกรองคำสั่งสืบค้นส่วนกลางและเอนทิตีที่เป็นเจ้าของ
← กลับไปที่ C# Academy