Reputation: 825
I have an ASP.Net Core Web API project in which I access an SQL Server database. This is one of my API endpoints that works that returns a single result object:
// GET: api/Players/5
[HttpGet("{id}")]
public async Task<ActionResult<Player>> GetPlayer(int id)
{
var player = await _context.Players.FindAsync(id);
if (player == null)
{
return NotFound();
}
return player;
}
This one returns a list of all players using a stored procedure:
// GET: api/Players
[HttpGet("proc")]
public async Task<ActionResult<IEnumerable<Player>>> GetPlayersProc()
{
return await _context.Players.FromSqlRaw("exec GetAllPlayers").ToListAsync();
}
How could I rewrite the first one with a stored procedure?
EDIT:
Based on the first answer I am now trying this:
// GET: api/Players/5
[HttpGet("{id}")]
public async Task<ActionResult<Player>> GetPlayerInfo(int id)
{
var player = await _context.Players.FindAsync(id);
if (player == null)
{
return NotFound();
}
//return player;
var ID = new SqlParameter("@ID", id);
return (await _context.Players.FromSqlRaw("exec GetOnePlayerInfo @ID",
parameters: new[] { ID }).ToListAsync()).FirstOrDefault();
}
But when I try this I get this error:
System.ArgumentException: An item with the same key has already been added. Key: PlayerID
at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
at System.Linq.Enumerable.ToDictionary[TSource,TKey,TElement](IEnumerable`1 source, Func`2 keySelector, Func`2 elementSelector, IEqualityComparer`1 comparer)
at Microsoft.EntityFrameworkCore.Query.Internal.FromSqlQueryingEnumerable`1.BuildIndexMap(IReadOnlyList`1 columnNames, DbDataReader dataReader)
at Microsoft.EntityFrameworkCore.Query.Internal.FromSqlQueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(DbContext _, Boolean result, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Query.Internal.FromSqlQueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
at CyberpunkAPI.Controllers.PlayersController.GetPlayerInfo(Int32 id) in C:\Users\e096752\Documents\Cole's Git Repos\CyberpunkAPI\CyberpunkAPI\Controllers\PlayersController.cs:line 74
at lambda_method52(Closure , Object )
at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Logged|12_1(ControllerActionInvoker invoker)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
at Swashbuckle.AspNetCore.SwaggerUI.SwaggerUIMiddleware.Invoke(HttpContext httpContext)
at Swashbuckle.AspNetCore.Swagger.SwaggerMiddleware.Invoke(HttpContext httpContext, ISwaggerProvider swaggerProvider)
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)
HEADERS
=======
Accept: text/plain
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close
Host: localhost:44326
Referer: https://localhost:44326/swagger/index.html
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36
sec-ch-ua: "Chromium";v="92", " Not A;Brand";v="99", "Google Chrome";v="92"
sec-ch-ua-mobile: ?0
sec-fetch-site: same-origin
sec-fetch-mode: cors
sec-fetch-dest: empty
This is the stored proc GetOnePlayerInfo
USE [cyberpunk]
GO
/****** Object: StoredProcedure [dbo].[GetOnePlayerInfo] Script Date: 8/19/2021 11:35:32 AM ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER Procedure [dbo].[GetOnePlayerInfo] @ID int
AS
SELECT Player.*, Stats.*, Special_Abilities.*
FROM Player
LEFT OUTER JOIN Stats ON Player.ID=Stats.PlayerID
LEFT OUTER JOIN Special_Abilities ON Player.ID=Special_Abilities.PlayerID
WHERE ID = @ID;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
#nullable disable
namespace CyberpunkAPI.Models
{
public partial class Player
{
public int Id { get; set; }
[Required]
public string Handle { get; set; }
[Required]
public string Role { get; set; }
public string Avatar { get; set; }
public virtual SpecialAbilities SpecialAbilities { get; set; }
public virtual Stats Stats { get; set; }
}
}
Upvotes: 1
Views: 2486
Reputation: 43860
You will have to create a special class for your sp result, it should include ALL data properties that are selected in your sp
[NotMapped]
public class SpPlayerResult
{
public int PlayerId { get; set; }
public string Handle { get; set; }
.......
public int SpecialAbilitiiesId { get; set; }
public int SpecialAbilitiiesName{ get; set; }
..... and so on
public int StatsId { get; set; }
public int StatsName { get; set; }
..... and so on
}
All properties in this class should be the same name and type as it is in you stored procedure
SELECT Player.Id as PlayerId, Stats.Id as StatsId,
Special_Abilities.Id as SpecialAbilitiesId, ....and so on the same name as it in the SpResultClass
add SpPlayerResult to db context and run this code
public virtual DbSet<SpPlayerResult> SpPlayerResults { get; set; }
[HttpGet("{id}")]
public async Task<ActionResult<Player>> GetPlayerInfo(int id)
{
if(id==0) return null;
var ID = new SqlParameter("@ID", id);
var players = await _context.SpPlayerResults.FromSqlRaw("exec GetOnePlayerInfo @ID",
parameters: new[] { ID }).ToListAsync();
if (players!=null && players.Count>0) return players.FirstOrDefault();
return null;
}
after this you will have to map result to a Player object.
So as you can see it needs some code. I am using stored procedures only if I neeed a very complicated select query that can take hundreds line of code. For CRUD much easier to use just EF
var player = await _context.Players
.Include(i=> i.SpecialAbilities)
.Include(i=> i.Stats)
.FirstOrDefaultAsync(i=> i.PlayerID==id);
Upvotes: 1