简体   繁体   中英

How to map set of results from stored procedure in postresql where I return multiple cursors using Entity Framework Core

I was working on stored procedure to get some statistics from database, but I do not want to do it in code side because there is lots of data to process and there are some tables I would need to call after execution, well lots of stuff that will use resources and can be made cheaply by database query. So I came up with stored procedure that looks like:

CREATE OR REPLACE FUNCTION PUBLIC.get_statistics_info(severityref refcursor, eventsref refcursor) 
    RETURNS SETOF refcursor 
    LANGUAGE 'plpgsql'
AS $BODY$ 
                                
BEGIN
    OPEN severityref FOR 
        SELECT 
            si."VarId", si."Date", si."SystemId", si."SystemName", 
            SUM(ss."InfoCounter") AS info, SUM(ss."WarningCounter") AS warn, SUM(ss."NonFatalCounter") AS nfatal, SUM(ss."FatalCounter") AS fatal,
            SUM(ssm."InfoCounter") AS minfo, SUM(ssm."WarningCounter") AS mwarn, SUM(ssm."NonFatalCounter") AS nfatal, SUM(ssm."FatalCounter") AS mfatal
        FROM "StatisticsInfo" AS si
        left JOIN "StatisticsSeverity" AS ss ON si."Id" = ss."StatisticsInfoId"
        left JOIN "StatisticsSeverityMaintenance" AS ssm ON si."Id" = ssm."StatisticsInfoId"
        GROUP BY si."VarId", si."Date", si."SystemId", si."SystemName"; 
    RETURN NEXT severityref;                                                                            

    OPEN eventsref for 
        SELECT 
            si."VarId", si."SystemId" ,
            se."EventId" AS eventid, se."Counter" AS eventcounter, sed."Symbol" AS eventsymbol,
            sem."EventId" AS mseventid, sem."Counter" AS mseventcounter, msed."Symbol" AS meventsymbol
        FROM "StatisticsInfo" AS si
        left JOIN "StatisticsEvent" AS se ON si."Id" = se."StatisticsInfoId"
        LEFT JOIN "EventDefinitions" AS sed ON se."EventId" = sed."Decimal"
        left JOIN "StatisticsEventMaintenance" AS sem ON si."Id" = sem."StatisticsInfoId"
        LEFT JOIN "EventDefinitions" AS msed ON sem."EventId" = msed."Decimal";  
    RETURN NEXT eventsref;   
END
$BODY$;

It sums up statistics using 2 cursors, so I should get 2 sets of data as a result. But now I have a problem how to recevie it. I am using entity framework core for my db operations, so I made something like this.

Service Call:

    public async ValueTask<IEnumerable<object>> GetCombinedStatisticsInfo()
    {
        try
        {
            using var unitOfWork = UnitOfWorkFactory.CreateGeneralContext();

            //I MADE THIS MAPPING AS TEST TO CHECK WHAT WILL BE RETURNED
            var map = new Func<DbDataReader, object>(reader => new
            {
                first = reader[0]
            });

            const string procedureCall = "BEGIN; SELECT get_statistics_info('severity', 'events'); FETCH ALL IN \"severity\"; FETCH ALL IN \"events\"; COMMIT;";
            var result = await unitOfWork.GeneralRepository.ExecuteSqQuery(procedureCall, map);
            unitOfWork.Dispose();
            return result;
        }
        catch (Exception ex)
        {
            logger.Error(ex);
            throw;
        }
    }

Repository:

    public async ValueTask<IEnumerable<TResponse>> ExecuteSqlProcedure<TResponse>(string procedureCall, Func<DbDataReader, TResponse> map)
    {
        var transaction = await this.Context.Database.BeginTransactionAsync();
        var result = await ExecuteSql(procedureCall, map, CommandType.StoredProcedure);
        await transaction.CommitAsync();
        return result;
    }

    private async ValueTask<IEnumerable<TResponse>> ExecuteSql<TResponse>(string query, Func<DbDataReader, TResponse> map, CommandType commandType)
    {
        await using var command = this.Context.Database.GetDbConnection().CreateCommand();
        command.CommandText = query;
        command.CommandType = commandType;

        await this.Context.Database.OpenConnectionAsync();
        await using var result = await command.ExecuteReaderAsync();
        var entities = new List<TResponse>();

        while (await result.ReadAsync())
        {
            entities.Add(map(result));
        }

        return entities;
    }

Well basically it executes procedure but as a result I am getting cursor names like: {first: "severity"; first: "events"}, so I checked execution on my database and it works i get 2 sets of data using that transaction with fetch. So my question is what am I doing wrong here?

So I figured this one out I need to add each fetch in a transaction after executing stored procedure separately and map results, something like:

    public async ValueTask<Dictionary<string, List<object>>> ExecuteGetStatisticsProcedure()
    {
        var transaction = await this.Context.Database.BeginTransactionAsync();
        var entities = new Dictionary<string, List<object>>
        {
            {SeverityKey, new List<object>() },
            {EventsKey, new List<object>() }
        };

        try
        {
            async Task GetSeverity(DbCommand severityCommand)
            {
                const string fetchSeverityCursor = "FETCH ALL IN \"severity\";";
                severityCommand.CommandText = fetchSeverityCursor;
                await using var result = await severityCommand.ExecuteReaderAsync();
                while (await result.ReadAsync())
                {
                    entities[SeverityKey].Add(
                        new
                        {
                            //Map results
                        });
                }
            }

            async Task GetEvents(DbCommand eventsCommand)
            {
                const string fetchEventCursor = "FETCH ALL IN \"events\";";
                eventsCommand.CommandText = fetchEventCursor;

                await eventsCommand.ExecuteReaderAsync();
                await using var eventsResult = await eventsCommand.ExecuteReaderAsync();
                while (await eventsResult.ReadAsync())
                {
                    entities[EventsKey].Add(
                        new
                        {
                            //Map results
                        });
                }
            }

            await using var command = this.Context.Database.GetDbConnection().CreateCommand();
            command.CommandType = CommandType.Text;
            await this.Context.Database.OpenConnectionAsync();

            const string procedureCallProcedure = "SELECT * From get_statistics_info(\'severity\', \'events\');";
            await this.Context.Database.ExecuteSqlRawAsync(procedureCallProcedure);

            await GetSeverity(command);
            await GetEvents(command);

            await transaction.CommitAsync();
        }
        catch (Exception ex)
        {
            Logger.Error(ex);
            await transaction.RollbackAsync();
        }

        return entities;
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM