우리가 직접 만든 SQL을 호출하게 하는 기능이다. 연산을 DB쪽에서 하도록 떠넘기고 싶을 때 사용한다. 또 EF Core 쪽에서 비효율적인 쿼리를 짤 수 있을 때 우리가 직접 정의할 수 있다.
SQL 쿼리를 만든 후 DB에 등록하고 등록한 함수를 호출하도록 하자
static 함수를 만들고 EF Core에 등록
public class ItemReview
{
public int ItemReviewId { get; set; }
public int Score { get; set; }
}
[Table("Item")]
public class Item
{
...
public ICollection<ItemReview> Reviews { get; set; }
}
[DbFunction()] // UDF로 인식
public static double? GetAverageReviewScore(int itemId)
{
throw new NotImplementedException("C#에서 사용금지!");
}
강의에서는 클래스를 따로 만들지 않고, 그냥 Program 클래스 안에 static 함수를 정의하였다. C# 코드에서 호출되면 바로 Exception을 날리도록 정의했고, [DbFunction()]을 삽입하여 UDF로 인식되게 하였다. Fluent API로도 UDF로 인식하게 할 수는 있으나 어노테이션 방식이 더 깔끔하다.
builder.HasDbFunction(() => Program.GetAverageReviewScore(0)); // 아무값이나 인자로
cmd 문자열에 함수 몸체를 다음과 같이 SQL 구문을 정의한다.
// Database Setup
string cmd = @"CREATE FUNCTION GetAverageReviewScore (@itemId INT) RETURNS FLOAT
AS
BEGIN
DECLARE @result AS FLOAT
SELECT @result = AVG(CAST([Score] AS FLOAT))
FROM ItemReview AS r
WHERE @itemId = r.ItemId
RETURN @result
END
";
db.Database.ExecuteSqlRaw(cmd);
...
items[0].Reviews = new List<ItemReview>()
{
new ItemReview() { Score = 5 },
new ItemReview() { Score = 3 },
new ItemReview() { Score = 1 },
};
...
public static void CalcAverage()
{
using (AppDbContext db = new AppDbContext())
{
foreach (var avg in db.Items.Select(i => Program.GetAverageReviewScore(i.ItemId)))
{
if(avg == null)
Console.WriteLine("리뷰 없음");
else
Console.WriteLine($"Avg : {avg.Value}");
}
}
}