Вызов пользовательской функции SQL в запросе LINQ

Мне сложно с этим справиться. Я пытаюсь выполнить поиск по радиусу, используя следующий помощник фильтра на IQueryable. Существует ряд других фильтров, которые применяются до применения RadiusSearch. Порядок не имеет особого значения, поскольку цель состоит в том, чтобы отложить запрос до операции ToList().

public static IQueryable<ApiSearchCommunity> RadiusSearch(this IQueryable<ApiSearchCommunity> communities)
{
    var centerLatitude = 30.421278;
    var centerLongitude = -97.426261;
    var radius = 25;

    return communities.Select(c => new ApiSearchCommunity()
    {
        CommunityId = c.CommunityId,
        City = c.City,
        //Distance = c.GetArcDistance(centerLatitude, centerLongitude, c.Latitude, c.Longitude, radius)
    });
}

Можно ли каким-то образом написать помощника, такого как GetArcDistance, который, в свою очередь, вызывает UDF на SQL? Запрос, который я пытаюсь создать, следующий

SELECT 
    comms.community_id, 
    comms.city, 
    comms.distance 
FROM (
    SELECT 
        c.community_id, 
        c.city, 
        dbo.udf_ArcDistance(
            30.421278,-97.426261, 
            c.community_latitude,
            c.community_longitude
        ) AS distance 
    FROM communities c) AS comms 
WHERE comms.distance <= 25 
ORDER BY comms.distance

Ответ 1

Хорошо, думаю, я понимаю вопрос - суть в том, что вы хотите иметь возможность вызывать SQL UDF как часть вашего запроса Linq to Entities.

Это если вы сначала используете базу данных или модель:

В этой статье объясняется, как это сделать: http://msdn.microsoft.com/en-us/library/dd456847(VS.100).aspx

Чтобы подвести итог, сначала вам нужно отредактировать файл edmx в редакторе xml, в разделе edmx: StorageModels → Schema вам нужно указать сопоставление с вашим sql udf, например

<Function Name="SampleFunction" ReturnType="int" Schema="dbo">
    <Parameter Name="Param" Mode="In" Type="int" />
</Function>

Затем вам нужно создать статическую функцию где-нибудь с атрибутом EdmFunction, что-то вроде этого:

public static class ModelDefinedFunctions
{
    [EdmFunction("TestDBModel.Store", "SampleFunction")]
    public static int SampleFunction(int param)
    {
      throw new NotSupportedException("Direct calls are not supported.");
    }
}

Этот метод будет отображаться в UDF во время запроса по структуре сущности. Первый атрибут атрибута - пространство имен хранилища - вы можете найти это в своем XML файле edmx в элементе Schema (посмотрите на пространство имен). Второй аргумент - имя udf.

Затем вы можете вызвать его примерно так:

var result = from s in context.UDFTests
            select new
            {
                TestVal = ModelDefinedFunctions.SampleFunction(22)
            };

Надеюсь, что это поможет.

Ответ 2

если вы используете подход Code-First, тогда вы не можете вызывать UDF как хотите (с EF6) - вот доказательство и еще один. Вы ограничены только вызовом UDF в качестве части вашего SQL-запроса:

bool result = FooContext.CreateQuery<bool>(
    "SELECT VALUE FooModel.Store.UserDefinedFunction(@someParameter) FROM {1}",
    new ObjectParameter("someParameter", someParameter)
).First();

который является уродливым IMO и подверженным ошибкам.

Кроме того - на этой странице MSDN говорится:

Процесс вызова пользовательской функции требует трех основных шагов:

  • Определите функцию в вашей концептуальной модели или объявите функцию в своей модели хранения.

что по существу означает, что вам нужно использовать подход Model-First для вызова UDF.