簡體   English   中英

代碼第一個實體框架6.1自定義聚合函數

[英]Code first Entity Framework 6.1 Custom Aggregate Function

我在SQL Server上有一個自定義CLR聚合函數來計算百分位數。 是否可以通過Entity Framework調用我的自定義聚合函數? 如何配置映射以允許此操作?

我嘗試使用類似於Entity Framework 6 Code First Custom Functions中描述的codefirstfunctions,但是函數似乎只允許使用scaler參數,其中我的函數是一個聚合函數,因此需要獲取一個項目列表(類似如何Sum,Averagg和Count工作)。

Aggregate函數具有以下簽名,包含我們想要的中值和百分位值(50是中位數,25下四分位數,75上四分位數)

CREATE AGGREGATE [dbo].[Percentile]
(@value [float], @tile [smallint])
RETURNS[float]
EXTERNAL NAME [SqlFuncs].[Percentile]
GO

我已經嘗試添加DbFunctionAttribute,但不完全確定如何首先使用代碼將其連接到實體框架存儲模型。

[DbFunction("SqlServer", "Percentile")]

public static double? Percentile(IEnumerable<int?> arg, int tile)
{
    throw new NotSupportedException("Direct calls are not supported.");
}

我正在尋找的是能夠寫出類似的東西

paymentsTable
    .GroupBy(x=>x.CustomerId)
    .Select(new{
            Median = MyDbContext.Percentile(x.Select(g=>g.Amount), 50)
    });

哪個會映射到SQL之類的

SELECT [dbo].[Percentile](Amount, 50) as Median
FROM Payments
GROUP BY CustomerId

正如@srutzky在評論中提到的那樣,EF似乎不喜歡使用多個參數綁定聚合函數。 因此,您必須將百分位數函數更改為中值函數或您感興趣的任何固定百分位數(您將需要更新SqlClr函數以使參數匹配)

public class MySqlFunctions
{
    [DbFunction("dbo", "Median")]
    public static float? Median(IEnumerable<float?> arg)
    {
        throw new NotSupportedException("Direct calls are not supported.");
    }
}

下一步是讓EF知道數據庫有一個名為median的函數我們可以在DbContext中執行此操作。 創建一個新的約定來訪問dbModel然后我們在dbModel中添加該函數。 您必須確保參數和參數類型完全匹配SQL和C#函數。

public class EmContext : DbContext
{    
    ...

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        //Register a convention so we can load our function
        modelBuilder.Conventions.Add(new AddMedianFunction());

        ...

    }

    public class AddMedianFunction : IConvention, IStoreModelConvention<EntityContainer>
    {
        public void Apply(EntityContainer item, DbModel dbModel)
        {
            //these parameter types need to match both the database method and the C# method for EF to link
            var edmFloatType = PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.Single);

            //CollectionType constructor is internal making it impossible to get a collection type. 
            //We resort to reflection instantiation.
            var edmFloatListType = CreateInstance<CollectionType>(edmFloatType);

            var medianfunction = EdmFunction.Create("Median", "dbo", DataSpace.SSpace, new EdmFunctionPayload
            {
                ParameterTypeSemantics = ParameterTypeSemantics.AllowImplicitConversion,
                IsComposable = true,
                IsAggregate = true,
                Schema = "dbo",
                ReturnParameters = new[]
                {
                    FunctionParameter.Create("ReturnType", edmFloatType, ParameterMode.ReturnValue)
                },
                Parameters = new[]
                {
                    FunctionParameter.Create("input", edmFloatListType, ParameterMode.In),
                }
            }, null);

            dbModel.StoreModel.AddItem(medianfunction);
            dbModel.Compile();       
        }

        public static T CreateInstance<T>(params object[] args)
        {
            var type = typeof(T);
            var instance = type.Assembly.CreateInstance(
                type.FullName, false,
                BindingFlags.Instance | BindingFlags.NonPublic,
                null, args, null, null);
            return (T)instance;
        }
    }
}

有了這一切,您應該能夠按預期調用您的功能

paymentsTable
    .GroupBy(x=>x.CustomerId)
    .Select(new{
            Median = MySqlFunctions.Median(x.Select(g=>g.Amount))
    });

注意:我已經假設你已經加載了我在這里沒有涉及的SqlClr函數

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM