Вызов скалярной функции в операторе LINQ без EDMX - PullRequest
0 голосов
/ 29 мая 2018

Я пытаюсь вызвать функцию SQL SCALAR в EF.Я пробовал различные примеры, но продолжаю получать:

Указанный метод x для типа y не может быть преобразован в выражение хранилища LINQ to Entities.

Я использую ...

  • EF 6.1.3
  • EntityFramework.Functions 1.4.1

ВОПРОС:Заметьте, я звоню в SELECT ...

public IQueryable<MeterDataItem> Query()
{
    var query = from meter in UnitOfWork.Meter
                join meterType in UnitOfWork.MeterType on meter.MeterTypeId equals meterType.Id into meterTypeLEFTJOIN
                    from meterType in meterTypeLEFTJOIN.DefaultIfEmpty()
                join company in UnitOfWork.Company on meter.CompanyId equals company.Id
                join meterPosition in UnitOfWork.EFMMeterPosition on meter.EFMMeterPositionId equals meterPosition.Id into meterPositionLEFTJOIN
                    from meterPosition in meterPositionLEFTJOIN.DefaultIfEmpty()
                join flowType in UnitOfWork.FlowType on meter.FlowTypeId equals flowType.Id into flowTypeLEFTJOIN
                    from flowType in flowTypeLEFTJOIN.DefaultIfEmpty()
                join fluidType in UnitOfWork.FluidType on meter.FluidTypeId equals fluidType.Id into fluidTypeLEFTJOIN
                    from fluidType in fluidTypeLEFTJOIN.DefaultIfEmpty()
                join runStatus in UnitOfWork.RunStatus on meter.RunStatusId equals runStatus.Id into runStatusLEFTJOIN
                    from runStatus in runStatusLEFTJOIN.DefaultIfEmpty()
                join pipeline in UnitOfWork.Pipeline on meter.PipelineId equals pipeline.Id into pipelineLEFTJOIN
                    from pipeline in pipelineLEFTJOIN.DefaultIfEmpty()

                // Device portion
                join device in UnitOfWork.Device on meter.DeviceId equals device.Id into deviceLEFTJOIN
                    from device in deviceLEFTJOIN.DefaultIfEmpty()
                join rtuDevice in UnitOfWork.RTUDevice on device.Id equals rtuDevice.DeviceId into rtuDeviceLEFTJOIN
                    from rtuDevice in rtuDeviceLEFTJOIN.DefaultIfEmpty()

                // Contact portion
                join measureTech in UnitOfWork.User on rtuDevice.MeasurementTechnicianId equals measureTech.Id into measureTechLEFTJOIN
                    from measureTech in measureTechLEFTJOIN.DefaultIfEmpty()
                join commTech in UnitOfWork.User on rtuDevice.CommunicationTechnicianId equals commTech.Id into commTechLEFTJOIN
                    from commTech in commTechLEFTJOIN.DefaultIfEmpty()

                // Connection portion
                join deviceCircuit in UnitOfWork.DeviceCircuit on device.Id equals deviceCircuit.DeviceId into deviceCircuitLEFTJOIN
                    from deviceCircuit in deviceCircuitLEFTJOIN.DefaultIfEmpty()
                join circuit in UnitOfWork.Circuit on deviceCircuit.CircuitId equals circuit.Id
                join circuitConnection in UnitOfWork.CircuitConnection on circuit.Id equals circuitConnection.CircuitId
                join connection in UnitOfWork.Connection on circuitConnection.ConnectionId equals connection.Id

                where
                    deviceCircuit.IsPrimary == true

                select new MeterDataItem()
                {
                    MeterId = meter.Id,
                    MeterNumber = meter.MeterNumber,
                    MeterName = meter.MeterName,
                    MeterTypeId = meterType.Id,
                    MeterTypeName = meterType.MeterTypeName,
                    MeterPositionCategory = meterPosition.EFMMeterPositionCategory,
                    FlowTypeName = flowType.FlowTypeName,
                    FluidTypeCategory = fluidType.FluidTypeCategory,
                    RunStatusCategory = runStatus.RunStatusCategory,
                    PipelineName = pipeline.PipelineName,
                    CompanyName = company.CompanyName,
                    ConnectionValue = GetConnection(circuitConnection.Id, connection.ConnectionTypeName),
                    DeviceId = device.Id,
                    DeviceName = device.DeviceName,
                    MeasurementTechnicianId = measureTech.Id,
                    MeasurementTechnicianFirstName = measureTech.FirstName,
                    MeasurementTechnicianLastName = measureTech.LastName,
                    CommunicationTechnicianId = commTech.Id,
                    CommunicationTechnicianFirstName = commTech.FirstName,
                    CommunicationTechnicianLastName = commTech.LastName,
                    MeterObjectStateName = null,    //<-- Default Value
                    FavoriteId = 0                  //<-- Default Value
                };

    return query.OrderBy(x => x.MeterNumber);
}

Функция CSHARP: Я предполагаю, что это не может найти функцию по какой-то причине ...

[Function(FunctionType.ComposableScalarValuedFunction, nameof(svfn_GetMeterConnection), Schema = "dbo")]
[return: Parameter(DbType = "VARCHAR(100)")]
public string svfn_GetMeterConnection([Parameter(DbType = "INT")]int circuitConnectionId, [Parameter(DbType = "VARCHAR(50)")]string connectionTypeName)
{
    ObjectParameter circuitConnectionIdParameter = new ObjectParameter("@CircuitConnectionId", circuitConnectionId);
    ObjectParameter connectionTypeNameParameter = new ObjectParameter("@ConnectionTypeName", connectionTypeName);

    return UnitOfWork.DbContext.ObjectContext().ExecuteFunction<string>(nameof(this.svfn_GetMeterConnection), circuitConnectionIdParameter, connectionTypeNameParameter).SingleOrDefault();
}

SQL-ФУНКЦИЯ: Все ссылки на мои имена и имена параметров выглядят правильно ...

ALTER FUNCTION [dbo].[svfn_GetMeterConnection]
(
    @CircuitConnectionId INT,
    @ConnectionTypeName VARCHAR(50)
)
RETURNS VARCHAR(100)
AS
BEGIN

    DECLARE @Value VARCHAR(100) = NULL;

    -- DIAL-UP
    IF(@ConnectionTypeName = 'Dial-Up')
    BEGIN
        SELECT
            @Value = circuitConnectionConfiguration.ConnectionPropertyValue
        FROM [dbo].[CircuitConnectionConfiguration] circuitConnectionConfiguration
        JOIN [dbo].[ConnectionConfiguration] connectionConfiguration ON connectionConfiguration.Id = circuitConnectionConfiguration.ConnectionConfigurationId
        JOIN [dbo].[ConnectionProperty] connProperty ON connProperty.Id = connectionConfiguration.ConnectionPropertyId
        WHERE
            circuitConnectionConfiguration.CircuitConnectionId = @CircuitConnectionId
            AND connProperty.ConnectionPropertyName = 'Dial-Up Number'
    END

    -- INTERNET PROTOCOL (IP)
    IF(@ConnectionTypeName = 'Internet Protocol (IP)')
    BEGIN
        SELECT @Value = 
            ((SELECT circuitConnectionConfiguration.ConnectionPropertyValue
                FROM [dbo].[CircuitConnectionConfiguration] circuitConnectionConfiguration
                JOIN [dbo].[ConnectionConfiguration] connectionConfiguration ON connectionConfiguration.Id = circuitConnectionConfiguration.ConnectionConfigurationId
                JOIN [dbo].[ConnectionProperty] connProperty ON connProperty.Id = connectionConfiguration.ConnectionPropertyId
            WHERE
                circuitConnectionConfiguration.CircuitConnectionId = @CircuitConnectionId
                AND connProperty.ConnectionPropertyName = 'IP Address') 
            + ':' +
            (SELECT circuitConnectionConfiguration.ConnectionPropertyValue
                FROM [dbo].[CircuitConnectionConfiguration] circuitConnectionConfiguration
                JOIN [dbo].[ConnectionConfiguration] connectionConfiguration ON connectionConfiguration.Id = circuitConnectionConfiguration.ConnectionConfigurationId
                JOIN [dbo].[ConnectionProperty] connProperty ON connProperty.Id = connectionConfiguration.ConnectionPropertyId
            WHERE
                circuitConnectionConfiguration.CircuitConnectionId = @CircuitConnectionId
                AND connProperty.ConnectionPropertyName = 'Port'))
    END

    -- Return the result of the function
    RETURN @Value
END

ОБНОВЛЕНИЕ - ОТВЕТ: Вот некоторые из необходимых мне изменений:

  • Изменение типа функции на FunctionType.ComposableScalarValuedFunction
  • Изменение строк параметра DbType в нижний регистр
  • Не включайтеразмер в ваших объявлениях DbType: замените VACHAR (50) на varchar
  • Перемещение вызовов в конкретный DbContext
  • Регистрация FunctionConvention в OnModelCreating
конкретного DbContext для конкретного DbContext БЕТОННЫЙ ДБКОНТЕКСТ: Включая пространства имен ...
using EntityFramework.Functions;
using StructureMap;
using System.Configuration;
using System.Data.Entity;
using System.Data.Entity.Core.Objects;
using System.Linq;

public class MeasurementContractsDbContext : BaseDbContext
{
    #region <Constructors>

    [DefaultConstructor]
    public MeasurementContractsDbContext() : base(Settings.ConnectionString.Database.MeasurementContractsDb)
    {
        Database.SetInitializer<MeasurementContractsDbContext>(null);
        Database.CommandTimeout = int.Parse(ConfigurationManager.AppSettings[Settings.Command.TimeoutInterval]);
        Configuration.ProxyCreationEnabled = false;
    }

    #endregion

    #region <Methods>

    [Function(FunctionType.ComposableScalarValuedFunction, nameof(svfn_GetMeterConnection), Schema = "dbo")]
    [return: Parameter(DbType = "varchar")]
    public string svfn_GetMeterConnection(int circuitConnectionId, string connectionTypeName)
    {
        ObjectParameter circuitConnectionIdParameter = new ObjectParameter("CircuitConnectionId", circuitConnectionId);
        ObjectParameter connectionTypeNameParameter = new ObjectParameter("ConnectionTypeName", connectionTypeName);

        return this.ObjectContext().ExecuteFunction<string>(nameof(this.svfn_GetMeterConnection), circuitConnectionIdParameter, connectionTypeNameParameter).SingleOrDefault();
    }

    [Function(FunctionType.ComposableScalarValuedFunction, nameof(svfn_GetCurrentObjectStateName), Schema = "dbo")]
    [return: Parameter(DbType = "varchar")]
    public string svfn_GetCurrentObjectStateName(int contextId, string contextFullName)
    {
        ObjectParameter contextIdParameter = new ObjectParameter("contextId", contextId);
        ObjectParameter contextFullNameParameter = new ObjectParameter("contextFullName", contextFullName);

        return this.ObjectContext().ExecuteFunction<string>(nameof(this.svfn_GetCurrentObjectStateName), contextIdParameter, contextFullNameParameter).SingleOrDefault();
    }

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

        // ADD Functions
        modelBuilder.Conventions.Add(new FunctionConvention<MeasurementContractsDbContext>());

        // ...
    }

    #endregion
}

Образец использования:

/// <exception cref="ArgumentNullException">Non-Existent 'Query' value throws this exception</exception>
public IQueryable<MeterDetailDataItem> AuthorizationToFlowMeterDocumentFilter(IQueryable<MeterDetailDataItem> query)
{
    if (query == null)
        throw new ArgumentNullException("Query");

    string contextFullName = typeof(AuthorizationToFlowMeterDocument).FullName;

    // Get the ATF (if it exists)
    var filteredQuery = (from dataitem in query //<-- QUERY
                        join document in UnitOfWork.Document on dataitem.RequestToFlowMeterDocumentId equals document.ParentId

                         // TODO: Figure out if you can move svfn_GetCurrentObjectStateName into an Algorythm class that can be injected
                         // SQL Function
                         let objectStateName = ((MeasurementContractsDbContext)UnitOfWork.DbContext).svfn_GetCurrentObjectStateName(document.Id, contextFullName)

                        select new MeterDetailDataItem()
                        {
                            MeterId = dataitem.MeterId,
                            RequestToFlowMeterDocumentId = dataitem.RequestToFlowMeterDocumentId,
                            RequestToFlowMeterDocumentObjectStateName = dataitem.RequestToFlowMeterDocumentObjectStateName,
                            AuthorizationToFlowMeterDocumentId = document.Id,
                            AuthorizationToFlowMeterDocumentObjectStateName = objectStateName,
                            FirstDeliveryNoticeDocumentId = dataitem.FirstDeliveryNoticeDocumentId,
                            FirstDeliveryNoticeDocumentObjectStateName = dataitem.FirstDeliveryNoticeDocumentObjectStateName,
                            FavoriteId = dataitem.FavoriteId
                        });

    return filteredQuery.OrderBy(x => x.MeterId);
}

1 Ответ

0 голосов
/ 30 мая 2018

Похоже, вы используете EntityFramework.Functions пакет.Тогда взгляните на тему Функция .Вы используете Скалярная функция, не подлежащая компоновке , которая в соответствии с автором пакета

может вызываться напрямую, как и другие методы выше

но

Однако, поскольку он указан как несложный, он не может быть преобразован Entity Framework в запросах LINQ to Entities

и

Это дизайн Entity Framework

В то время как вам нужна Скалярная функция, компонуемая , которая

работает вLINQ to Entities запрашивает, но не может быть вызван напрямую

Вскоре, поскольку вы используете его в запросе LINQ to Entities, используйте FunctionType.ComposableScalarValuedFunction внутри Function аннотации.А поскольку он не вызывается напрямую, ему не нужно тело метода, поэтому вы можете просто вызвать исключение:

[Function(FunctionType.ComposableScalarValuedFunction, nameof(svfn_GetMeterConnection), Schema = "dbo")]
public string svfn_GetMeterConnection(int circuitConnectionId, string connectionTypeName)
{
    throw new NotSupportedException();
}

И не забудьте зарегистрировать функции, как показано в ссылке, в противном случае они победят 'эффект не будет, и вы продолжите получать NotSupportedException:

modelBuilder.Conventions.Add(new FunctionConvention<TheClassContainingTheFunction>());
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...