Kullanıcı tanımlı işlev eşleme

EF Core sorgularda kullanıcı tanımlı SQL işlevlerinin kullanılmasına izin verir. Bunu yapmak için, işlevlerin model yapılandırması sırasında bir CLR yöntemiyle eşlenmesi gerekir. lınq sorgusu SQL çevrilirken, kullanıcı tanımlı işlev, eşlendiği CLR işlevi yerine çağrılır.

bir yöntemi SQL işlevle eşleme

Kullanıcı tanımlı işlev eşlemesinin nasıl çalıştığını görmek için aşağıdaki varlıkları tanımlayalim:

public class Blog
{
    public int BlogId { get; set; }
    public string Url { get; set; }
    public int? Rating { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public int Rating { get; set; }
    public int BlogId { get; set; }

    public Blog Blog { get; set; }
    public List<Comment> Comments { get; set; }
}

public class Comment
{
    public int CommentId { get; set; }
    public string Text { get; set; }
    public int Likes { get; set; }
    public int PostId { get; set; }

    public Post Post { get; set; }
}

Ve aşağıdaki model yapılandırması:

modelBuilder.Entity<Blog>()
    .HasMany(b => b.Posts)
    .WithOne(p => p.Blog);

modelBuilder.Entity<Post>()
    .HasMany(p => p.Comments)
    .WithOne(c => c.Post);

Blog birçok gönderiye sahip olabilir ve her gönderiye birçok yorum bulunabilir.

Ardından, CommentedPostCountForBlog bloga göre belirli bir blog için en az bir yorumu olan gönderi sayısını döndüren kullanıcı tanımlı işlevini oluşturun Id :

CREATE FUNCTION dbo.CommentedPostCountForBlog(@id int)
RETURNS int
AS
BEGIN
    RETURN (SELECT COUNT(*)
        FROM [Posts] AS [p]
        WHERE ([p].[BlogId] = @id) AND ((
            SELECT COUNT(*)
            FROM [Comments] AS [c]
            WHERE [p].[PostId] = [c].[PostId]) > 0));
END

Bu işlevi EF Core kullanmak için, Kullanıcı tanımlı işlevle eşleştiğimiz aşağıdaki CLR metodunu tanımlıyoruz:

public int ActivePostCountForBlog(int blogId)
    => throw new NotSupportedException();

CLR yönteminin gövdesi önemli değildir. Yöntemi, EF Core bağımsız değişkenlerini çeviremediği müddetçe, istemci tarafı çağırılmaz. Bağımsız değişkenler çevrilebilmesi için EF Core yalnızca Yöntem imzası hakkında.

Not

Örnekte, metodu üzerinde tanımlanmıştır DbContext , ancak diğer sınıfların içinde statik bir yöntem olarak da tanımlanabilir.

Bu işlev tanımı artık model yapılandırmasındaki Kullanıcı tanımlı işlevle ilişkilendirilebilir:

modelBuilder.HasDbFunction(typeof(BloggingContext).GetMethod(nameof(ActivePostCountForBlog), new[] { typeof(int) }))
    .HasName("CommentedPostCountForBlog");

Varsayılan olarak, EF Core CLR işlevini aynı ada sahip kullanıcı tanımlı bir işleve eşlemeye çalışır. Adlar farklıysa, HasName eşlemek istediğimiz Kullanıcı tanımlı işlev için doğru adı sağlamak üzere kullanabiliriz.

Şimdi aşağıdaki sorgu yürütülüyor:

var query1 = from b in context.Blogs
             where context.ActivePostCountForBlog(b.BlogId) > 1
             select b;

Bu SQL oluşturacaktır:

SELECT [b].[BlogId], [b].[Rating], [b].[Url]
FROM [Blogs] AS [b]
WHERE [dbo].[CommentedPostCountForBlog]([b].[BlogId]) > 1

Bir yöntemi özel bir SQL eşleme

EF Core Ayrıca, belirli bir SQL dönüştürülen Kullanıcı tanımlı işlevlere izin verir. SQL ifadesi HasTranslation kullanıcı tanımlı işlev yapılandırması sırasında yöntemi kullanılarak sağlanır.

Aşağıdaki örnekte, iki tamsayı arasındaki yüzde farkını hesaplayan bir işlev oluşturacağız.

CLR yöntemi aşağıdaki gibidir:

public double PercentageDifference(double first, int second)
    => throw new NotSupportedException();

İşlev tanımı aşağıdaki gibidir:

// 100 * ABS(first - second) / ((first + second) / 2)
modelBuilder.HasDbFunction(
        typeof(BloggingContext).GetMethod(nameof(PercentageDifference), new[] { typeof(double), typeof(int) }))
    .HasTranslation(
        args =>
            new SqlBinaryExpression(
                ExpressionType.Multiply,
                new SqlConstantExpression(
                    Expression.Constant(100),
                    new IntTypeMapping("int", DbType.Int32)),
                new SqlBinaryExpression(
                    ExpressionType.Divide,
                    new SqlFunctionExpression(
                        "ABS",
                        new SqlExpression[]
                        {
                            new SqlBinaryExpression(
                                ExpressionType.Subtract,
                                args.First(),
                                args.Skip(1).First(),
                                args.First().Type,
                                args.First().TypeMapping)
                        },
                        nullable: true,
                        argumentsPropagateNullability: new[] { true, true },
                        type: args.First().Type,
                        typeMapping: args.First().TypeMapping),
                    new SqlBinaryExpression(
                        ExpressionType.Divide,
                        new SqlBinaryExpression(
                            ExpressionType.Add,
                            args.First(),
                            args.Skip(1).First(),
                            args.First().Type,
                            args.First().TypeMapping),
                        new SqlConstantExpression(
                            Expression.Constant(2),
                            new IntTypeMapping("int", DbType.Int32)),
                        args.First().Type,
                        args.First().TypeMapping),
                    args.First().Type,
                    args.First().TypeMapping),
                args.First().Type,
                args.First().TypeMapping));

İşlevi tanımladıktan sonra sorguda kullanılabilir. EF Core, veritabanı işlevini çağırmak yerine, yöntem gövdesini hastranslation tarafından oluşturulan SQL ifade ağacına göre doğrudan SQL dönüştürür. Aşağıdaki LINQ sorgusu:

var query2 = from p in context.Posts
             select context.PercentageDifference(p.BlogId, 3);

Aşağıdaki SQL üretir:

SELECT 100 * (ABS(CAST([p].[BlogId] AS float) - 3) / ((CAST([p].[BlogId] AS float) + 3) / 2))
FROM [Posts] AS [p]

Bağımsız değişkenlerine göre Kullanıcı tanımlı işlev için null olabilme yapılandırma

Kullanıcı tanımlı işlev yalnızca null bir veya daha fazla bağımsız değişkeni olduğunda dönebiliyorsanız null , efcore bunu belirtmek için bir yol sağlar, bu da daha fazla performans elde SQL. PropagatesNullability()İlgili işlev parametreleri model yapılandırmasına bir çağrı eklenerek yapılabilir.

Bunu göstermek için Kullanıcı işlevini tanımlayın ConcatStrings :

CREATE FUNCTION [dbo].[ConcatStrings] (@prm1 nvarchar(max), @prm2 nvarchar(max))
RETURNS nvarchar(max)
AS
BEGIN
    RETURN @prm1 + @prm2;
END

ve onunla eşlenen iki CLR yöntemi:

public string ConcatStrings(string prm1, string prm2)
    => throw new InvalidOperationException();

public string ConcatStringsOptimized(string prm1, string prm2)
    => throw new InvalidOperationException();

Model yapılandırması (iç OnModelCreating yöntemi) aşağıdaki gibidir:

modelBuilder
    .HasDbFunction(typeof(BloggingContext).GetMethod(nameof(ConcatStrings), new[] { typeof(string), typeof(string) }))
    .HasName("ConcatStrings");

modelBuilder.HasDbFunction(
    typeof(BloggingContext).GetMethod(nameof(ConcatStringsOptimized), new[] { typeof(string), typeof(string) }),
    b =>
    {
        b.HasName("ConcatStrings");
        b.HasParameter("prm1").PropagatesNullability();
        b.HasParameter("prm2").PropagatesNullability();
    });

İlk işlev standart şekilde yapılandırılır. İkinci işlev, null parametre etrafında işlevin nasıl davrandığı hakkında daha fazla bilgi sağlayan, null olabilme yayma iyileştirmesinden yararlanmak üzere yapılandırılmıştır.

Aşağıdaki sorguları verirken:

var query3 = context.Blogs.Where(e => context.ConcatStrings(e.Url, e.Rating.ToString()) != "https://mytravelblog.com/4");
var query4 = context.Blogs.Where(
    e => context.ConcatStringsOptimized(e.Url, e.Rating.ToString()) != "https://mytravelblog.com/4");

Şu SQL alırız:

SELECT [b].[BlogId], [b].[Rating], [b].[Url]
FROM [Blogs] AS [b]
WHERE ([dbo].[ConcatStrings]([b].[Url], CONVERT(VARCHAR(11), [b].[Rating])) <> N'Lorem ipsum...') OR [dbo].[ConcatStrings]([b].[Url], CONVERT(VARCHAR(11), [b].[Rating])) IS NULL

SELECT [b].[BlogId], [b].[Rating], [b].[Url]
FROM [Blogs] AS [b]
WHERE ([dbo].[ConcatStrings]([b].[Url], CONVERT(VARCHAR(11), [b].[Rating])) <> N'Lorem ipsum...') OR ([b].[Url] IS NULL OR [b].[Rating] IS NULL)

İkinci sorgunun, null değer alabilme durumunu test etmek için işlevin kendisini yeniden değerlendirmesi gerekmez.

Not

Bu iyileştirme yalnızca, işlev yalnızca parametreler olduğunda dönebiliyorsanız kullanılmalıdır nullnull .

Sorgulanabilir işlevi tablo değerli bir işlevle eşleme

EF Core Ayrıca, bir varlık türü döndüren kullanıcı tanımlı bir CLR yöntemi kullanılarak tablo değerli bir işleve eşlemeyi destekler IQueryable ve EF Core TVFs 'yi parametrelerle eşleştirmesine izin verir. işlem, kullanıcı tanımlı skalar işlevi bir SQL işlevi ile eşleştirmeye benzer: veritabanında bir tvf, lınq sorgularında kullanılan bir CLR işlevi ve ikisi arasında bir eşleme olmalıdır.

Örnek olarak, belirli bir "Like" eşiğini karşılayan en az bir yoruma sahip olan tüm gönderileri döndüren tablo değerli bir işlev kullanacağız:

CREATE FUNCTION dbo.PostsWithPopularComments(@likeThreshold int)
RETURNS TABLE
AS
RETURN
(
    SELECT [p].[PostId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
    FROM [Posts] AS [p]
    WHERE (
        SELECT COUNT(*)
        FROM [Comments] AS [c]
        WHERE ([p].[PostId] = [c].[PostId]) AND ([c].[Likes] >= @likeThreshold)) > 0
)

CLR yöntemi imzası aşağıdaki gibidir:

public IQueryable<Post> PostsWithPopularComments(int likeThreshold)
    => FromExpression(() => PostsWithPopularComments(likeThreshold));

İpucu

FromExpressionClr işlevi gövdesinde yapılan çağrı, işlevin normal bir DbSet yerine kullanılmasına izin verir.

Eşleme aşağıda verilmiştir:

modelBuilder.Entity<Post>().ToTable("Posts");
modelBuilder.HasDbFunction(typeof(BloggingContext).GetMethod(nameof(PostsWithPopularComments), new[] { typeof(int) }));

Dikkat

23408 sorunu düzeltilene kadar, varlık türlerinden birine eşleme, dbset için bir tabloyla varsayılan eşlemeyi geçersiz kılar. Gerekirse-Örneğin, varlık bir anahtarsız eşleme değilse, yöntemi kullanılarak açıkça belirtilmesi gerekir ToTable .

Not

Queryable işlevi tablo değerli bir işleve eşlenmelidir ve kullanamaz HasTranslation .

İşlev eşlendiğinde aşağıdaki sorgu:

var likeThreshold = 3;
var query5 = from p in context.PostsWithPopularComments(likeThreshold)
             orderby p.Rating
             select p;

Üretmez

SELECT [p].[PostId], [p].[BlogId], [p].[Content], [p].[Rating], [p].[Title]
FROM [dbo].[PostsWithPopularComments](@likeThreshold) AS [p]
ORDER BY [p].[Rating]