Skip to content

Commit faa1173

Browse files
hhvrcLucHeart
andauthored
Decrease IEnumerable usage (#161)
* Decrease IEnumerable usage This will reduce the risk of multiple enumeration crashes and probably improve performance * Remove the rest unnecessary * Implement back direct enumerable returns * Change controller return types to iasyncenumerable where applicable * Remove async on endpoint * Revert and adjust some changes to array * Friendship ended with array, we now like IReadOnlyList * More * Remove async keyword on endpoints returning IAsyncEnumerable --------- Co-authored-by: LucHeart <luc@luc.cat>
1 parent 128c0d8 commit faa1173

40 files changed

+252
-211
lines changed

API/Controller/Admin/GetOnlineDevices.cs

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,23 +41,25 @@ public async Task<IActionResult> GetOnlineDevices()
4141
}
4242
}).ToArrayAsync();
4343

44-
return RespondSuccessLegacy(allOnlineDevices.Select(x =>
45-
{
46-
var dbItem = dbLookup.First(y => y.Id == x.Id);
47-
return new AdminOnlineDeviceResponse
44+
return RespondSuccessLegacy(
45+
allOnlineDevices
46+
.Select(x =>
4847
{
49-
Id = x.Id,
50-
FirmwareVersion = x.FirmwareVersion,
51-
Gateway = x.Gateway,
52-
Owner = dbItem.Owner,
53-
Name = dbItem.Name,
54-
ConnectedAt = x.ConnectedAt,
55-
UserAgent = x.UserAgent,
56-
BootedAt = x.BootedAt,
57-
LatencyMs = x.LatencyMs,
58-
Rssi = x.Rssi,
59-
};
60-
})
48+
var dbItem = dbLookup.First(y => y.Id == x.Id);
49+
return new AdminOnlineDeviceResponse
50+
{
51+
Id = x.Id,
52+
FirmwareVersion = x.FirmwareVersion,
53+
Gateway = x.Gateway,
54+
Owner = dbItem.Owner,
55+
Name = dbItem.Name,
56+
ConnectedAt = x.ConnectedAt,
57+
UserAgent = x.UserAgent,
58+
BootedAt = x.BootedAt,
59+
LatencyMs = x.LatencyMs,
60+
Rssi = x.Rssi,
61+
};
62+
})
6163
);
6264
}
6365

API/Controller/Devices/DevicesController.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,19 @@ public sealed partial class DevicesController
2323
/// </summary>
2424
/// <response code="200">All devices for the current user</response>
2525
[HttpGet]
26-
[ProducesResponseType<BaseResponse<Models.Response.ResponseDevice[]>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
26+
[ProducesResponseType<BaseResponse<IAsyncEnumerable<Models.Response.ResponseDevice>>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
2727
[MapToApiVersion("1")]
28-
public async Task<IActionResult> ListDevices()
28+
public IActionResult ListDevices()
2929
{
30-
var devices = await _db.Devices.Where(x => x.Owner == CurrentUser.Id)
30+
var devices = _db.Devices
31+
.Where(x => x.Owner == CurrentUser.Id)
3132
.Select(x => new Models.Response.ResponseDevice
3233
{
3334
Id = x.Id,
3435
Name = x.Name,
3536
CreatedOn = x.CreatedOn
36-
}).ToArrayAsync();
37+
})
38+
.AsAsyncEnumerable();
3739

3840
return RespondSuccessLegacy(devices);
3941
}

API/Controller/Devices/ShockersController.cs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,26 @@ public sealed partial class DevicesController
1919
/// <response code="200">All shockers for the device</response>
2020
/// <response code="404">Device does not exists or you do not have access to it.</response>
2121
[HttpGet("{deviceId}/shockers")]
22-
[ProducesResponseType<BaseResponse<ShockerResponse[]>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
22+
[ProducesResponseType<BaseResponse<IAsyncEnumerable<ShockerResponse>>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
2323
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // DeviceNotFound
2424
[MapToApiVersion("1")]
2525
public async Task<IActionResult> GetShockers([FromRoute] Guid deviceId)
2626
{
2727
var deviceExists = await _db.Devices.AnyAsync(x => x.Owner == CurrentUser.Id && x.Id == deviceId);
2828
if (!deviceExists) return Problem(DeviceError.DeviceNotFound);
2929

30-
var shockers = await _db.Shockers.Where(x => x.Device == deviceId).Select(x => new ShockerResponse
31-
{
32-
Id = x.Id,
33-
Name = x.Name,
34-
RfId = x.RfId,
35-
CreatedOn = x.CreatedOn,
36-
Model = x.Model,
37-
IsPaused = x.Paused
38-
}).ToArrayAsync();
30+
var shockers = _db.Shockers
31+
.Where(x => x.Device == deviceId)
32+
.Select(x => new ShockerResponse
33+
{
34+
Id = x.Id,
35+
Name = x.Name,
36+
RfId = x.RfId,
37+
CreatedOn = x.CreatedOn,
38+
Model = x.Model,
39+
IsPaused = x.Paused
40+
})
41+
.AsAsyncEnumerable();
3942

4043
return RespondSuccessLegacy(shockers);
4144
}

API/Controller/Shares/Links/ListShareLinks.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,13 @@ public sealed partial class ShareLinksController
1414
/// </summary>
1515
/// <response code="200">All share links for the current user</response>
1616
[HttpGet]
17-
[ProducesResponseType<BaseResponse<ShareLinkResponse[]>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
18-
public async Task<IActionResult> List()
17+
[ProducesResponseType<BaseResponse<IAsyncEnumerable<ShareLinkResponse>>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
18+
public IActionResult List()
1919
{
20-
var ownShareLinks = await _db.ShockerSharesLinks.Where(x => x.OwnerId == CurrentUser.Id)
21-
.Select(x => ShareLinkResponse.GetFromEf(x)).ToArrayAsync();
20+
var ownShareLinks = _db.ShockerSharesLinks
21+
.Where(x => x.OwnerId == CurrentUser.Id)
22+
.Select(x => ShareLinkResponse.GetFromEf(x))
23+
.AsAsyncEnumerable();
2224

2325
return RespondSuccessLegacy(ownShareLinks);
2426
}

API/Controller/Shares/V2GetShares.cs

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,21 @@ namespace OpenShock.API.Controller.Shares;
1515
public sealed partial class SharesController
1616
{
1717
[HttpGet]
18-
[ProducesResponseType<IEnumerable<GenericIni>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
18+
[ProducesResponseType<IAsyncEnumerable<GenericIni>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
1919
[ApiVersion("2")]
20-
public async Task<GenericIni[]> GetSharesByUsers()
20+
public IAsyncEnumerable<GenericIni> GetSharesByUsers()
2121
{
22-
var sharedToUsers = await _db.ShockerShares.Where(x => x.Shocker.DeviceNavigation.Owner == CurrentUser.Id)
22+
return _db.ShockerShares
23+
.Where(x => x.Shocker.DeviceNavigation.Owner == CurrentUser.Id)
2324
.Select(x => new GenericIni
2425
{
2526
Id = x.SharedWithNavigation.Id,
2627
Image = x.SharedWithNavigation.GetImageUrl(),
2728
Name = x.SharedWithNavigation.Name
28-
}).OrderBy(x => x.Name).Distinct().ToArrayAsync();
29-
return sharedToUsers;
29+
})
30+
.OrderBy(x => x.Name)
31+
.Distinct()
32+
.AsAsyncEnumerable();
3033
}
3134

3235
[HttpGet("{userId:guid}")]
@@ -35,7 +38,8 @@ public async Task<GenericIni[]> GetSharesByUsers()
3538
[ApiVersion("2")]
3639
public async Task<IActionResult> GetSharesToUser(Guid userId)
3740
{
38-
var sharedWithUser = await _db.ShockerShares.Where(x => x.Shocker.DeviceNavigation.Owner == CurrentUser.Id && x.SharedWith == userId)
41+
var sharedWithUser = await _db.ShockerShares
42+
.Where(x => x.Shocker.DeviceNavigation.Owner == CurrentUser.Id && x.SharedWith == userId)
3943
.Select(x => new UserShareInfo
4044
{
4145
Id = x.Shocker.Id,
@@ -54,7 +58,8 @@ public async Task<IActionResult> GetSharesToUser(Guid userId)
5458
Intensity = x.LimitIntensity
5559
},
5660
Paused = x.Paused
57-
}).ToArrayAsync();
61+
})
62+
.ToArrayAsync();
5863

5964
if(sharedWithUser.Length == 0)
6065
{

API/Controller/Shares/V2Requests.cs

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,44 +16,45 @@ namespace OpenShock.API.Controller.Shares;
1616
public sealed partial class SharesController
1717
{
1818
[HttpGet("requests/outstanding")]
19-
[ProducesResponseType<ShareRequestBaseItem[]>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
19+
[ProducesResponseType<IAsyncEnumerable<ShareRequestBaseItem>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
2020
[ApiVersion("2")]
21-
public async Task<ShareRequestBaseItem[]> GetOutstandingRequestsList()
21+
public IAsyncEnumerable<ShareRequestBaseItem> GetOutstandingRequestsList()
2222
{
23-
var outstandingShares = await _db.ShareRequests.Where(x => x.Owner == CurrentUser.Id)
23+
return _db.ShareRequests
24+
.Where(x => x.Owner == CurrentUser.Id)
2425
.Select(x => new ShareRequestBaseItem()
25-
{
26-
Id = x.Id,
27-
CreatedOn = x.CreatedOn,
28-
Owner = new GenericIni
2926
{
30-
Id = x.OwnerNavigation.Id,
31-
Name = x.OwnerNavigation.Name,
32-
Image = x.OwnerNavigation.GetImageUrl()
33-
},
34-
SharedWith = x.UserNavigation == null
35-
? null
36-
: new GenericIni
27+
Id = x.Id,
28+
CreatedOn = x.CreatedOn,
29+
Owner = new GenericIni
3730
{
38-
Id = x.UserNavigation.Id,
39-
Name = x.UserNavigation.Name,
40-
Image = x.UserNavigation.GetImageUrl()
31+
Id = x.OwnerNavigation.Id,
32+
Name = x.OwnerNavigation.Name,
33+
Image = x.OwnerNavigation.GetImageUrl()
4134
},
42-
Counts = new ShareRequestBaseItem.ShareRequestCounts
43-
{
44-
Shockers = x.ShareRequestsShockers.Count
45-
}
46-
}).ToArrayAsync();
47-
48-
return outstandingShares;
35+
SharedWith = x.UserNavigation == null
36+
? null
37+
: new GenericIni
38+
{
39+
Id = x.UserNavigation.Id,
40+
Name = x.UserNavigation.Name,
41+
Image = x.UserNavigation.GetImageUrl()
42+
},
43+
Counts = new ShareRequestBaseItem.ShareRequestCounts
44+
{
45+
Shockers = x.ShareRequestsShockers.Count
46+
}
47+
})
48+
.AsAsyncEnumerable();
4949
}
5050

5151
[HttpGet("requests/incoming")]
52-
[ProducesResponseType<ShareRequestBaseItem[]>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
52+
[ProducesResponseType<IAsyncEnumerable<ShareRequestBaseItem>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
5353
[ApiVersion("2")]
54-
public async Task<ShareRequestBaseItem[]> GetIncomingRequestsList()
54+
public IAsyncEnumerable<ShareRequestBaseItem> GetIncomingRequestsList()
5555
{
56-
var outstandingShares = await _db.ShareRequests.Where(x => x.User == CurrentUser.Id)
56+
return _db.ShareRequests
57+
.Where(x => x.User == CurrentUser.Id)
5758
.Select(x => new ShareRequestBaseItem
5859
{
5960
Id = x.Id,
@@ -76,9 +77,8 @@ public async Task<ShareRequestBaseItem[]> GetIncomingRequestsList()
7677
{
7778
Shockers = x.ShareRequestsShockers.Count
7879
}
79-
}).ToArrayAsync();
80-
81-
return outstandingShares;
80+
})
81+
.AsAsyncEnumerable();
8282
}
8383

8484
[HttpGet("requests/{id:guid}")]
@@ -88,7 +88,7 @@ public async Task<ShareRequestBaseItem[]> GetIncomingRequestsList()
8888
public async Task<IActionResult> GetRequest(Guid id)
8989
{
9090
var outstandingShare = await _db.ShareRequests.Where(x => x.Id == id && (x.Owner == CurrentUser.Id || x.User == CurrentUser.Id))
91-
.Select(x => new ShareRequestBaseDetails()
91+
.Select(x => new ShareRequestBaseDetails
9292
{
9393
Id = x.Id,
9494
CreatedOn = x.CreatedOn,

API/Controller/Shockers/ControlLogController.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public sealed partial class ShockerController
2424
/// <response code="200">The logs</response>
2525
/// <response code="404">Shocker does not exist</response>
2626
[HttpGet("{shockerId}/logs")]
27-
[ProducesResponseType<BaseResponse<IEnumerable<LogEntry>>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
27+
[ProducesResponseType<BaseResponse<IAsyncEnumerable<LogEntry>>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
2828
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status404NotFound, MediaTypeNames.Application.ProblemJson)] // ShockerNotFound
2929
[MapToApiVersion("1")]
3030
public async Task<IActionResult> GetShockerLogs([FromRoute] Guid shockerId, [FromQuery] uint offset = 0,
@@ -33,8 +33,12 @@ [FromQuery] [Range(1, 500)] uint limit = 100)
3333
var exists = await _db.Shockers.AnyAsync(x => x.DeviceNavigation.Owner == CurrentUser.Id && x.Id == shockerId);
3434
if (!exists) return Problem(ShockerError.ShockerNotFound);
3535

36-
var logs = await _db.ShockerControlLogs.Where(x => x.ShockerId == shockerId)
37-
.OrderByDescending(x => x.CreatedOn).Skip((int)offset).Take((int)limit).Select(x => new LogEntry
36+
var logs = _db.ShockerControlLogs
37+
.Where(x => x.ShockerId == shockerId)
38+
.OrderByDescending(x => x.CreatedOn)
39+
.Skip((int)offset)
40+
.Take((int)limit)
41+
.Select(x => new LogEntry
3842
{
3943
Id = x.Id,
4044
Duration = x.Duration,
@@ -56,7 +60,8 @@ [FromQuery] [Range(1, 500)] uint limit = 100)
5660
Image = x.ControlledByNavigation.GetImageUrl(),
5761
CustomName = x.CustomName
5862
}
59-
}).ToArrayAsync();
63+
})
64+
.AsAsyncEnumerable();
6065

6166
return RespondSuccessLegacy(logs);
6267
}

API/Controller/Shockers/ControlShockerController.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System.Net;
2-
using System.Net.Mime;
1+
using System.Net.Mime;
32
using Asp.Versioning;
43
using Microsoft.AspNetCore.Mvc;
54
using Microsoft.AspNetCore.SignalR;
@@ -11,7 +10,6 @@
1110
using OpenShock.Common.Models;
1211
using OpenShock.Common.Problems;
1312
using OpenShock.Common.Services.RedisPubSub;
14-
using OpenShock.Common.Utils;
1513

1614
namespace OpenShock.API.Controller.Shockers;
1715

@@ -65,7 +63,7 @@ public async Task<IActionResult> SendControl(
6563
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status412PreconditionFailed, MediaTypeNames.Application.ProblemJson)] // Shocker is paused
6664
[ProducesResponseType<OpenShockProblem>(StatusCodes.Status403Forbidden, MediaTypeNames.Application.ProblemJson)] // You don't have permission to control this shocker
6765
public Task<IActionResult> SendControl_DEPRECATED(
68-
[FromBody] IEnumerable<Common.Models.WebSocket.User.Control> body,
66+
[FromBody] IReadOnlyList<Common.Models.WebSocket.User.Control> body,
6967
[FromServices] IHubContext<UserHub, IUserHub> userHub,
7068
[FromServices] IRedisPubService redisPubService)
7169
{

API/Controller/Shockers/OwnShockerController.cs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,26 +15,31 @@ public sealed partial class ShockerController
1515
/// </summary>
1616
/// <response code="200">The shockers were successfully retrieved.</response>
1717
[HttpGet("own")]
18-
[ProducesResponseType<BaseResponse<IEnumerable<ResponseDeviceWithShockers>>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
18+
[ProducesResponseType<BaseResponse<IAsyncEnumerable<ResponseDeviceWithShockers>>>(StatusCodes.Status200OK, MediaTypeNames.Application.Json)]
1919
[MapToApiVersion("1")]
20-
public async Task<IActionResult> ListShockers()
20+
public IActionResult ListShockers()
2121
{
22-
var shockers = await _db.Devices.Where(x => x.Owner == CurrentUser.Id).OrderBy(x => x.CreatedOn).Select(
23-
x => new ResponseDeviceWithShockers
22+
var shockers = _db.Devices
23+
.Where(x => x.Owner == CurrentUser.Id)
24+
.OrderBy(x => x.CreatedOn).Select(x => new ResponseDeviceWithShockers
2425
{
2526
Id = x.Id,
2627
Name = x.Name,
2728
CreatedOn = x.CreatedOn,
28-
Shockers = x.Shockers.OrderBy(y => y.CreatedOn).Select(y => new ShockerResponse
29-
{
30-
Id = y.Id,
31-
Name = y.Name,
32-
RfId = y.RfId,
33-
CreatedOn = y.CreatedOn,
34-
Model = y.Model,
35-
IsPaused = y.Paused
36-
})
37-
}).ToArrayAsync();
29+
Shockers = x.Shockers
30+
.OrderBy(y => y.CreatedOn)
31+
.Select(y => new ShockerResponse
32+
{
33+
Id = y.Id,
34+
Name = y.Name,
35+
RfId = y.RfId,
36+
CreatedOn = y.CreatedOn,
37+
Model = y.Model,
38+
IsPaused = y.Paused
39+
})
40+
.ToArray()
41+
})
42+
.AsAsyncEnumerable();
3843

3944
return RespondSuccessLegacy(shockers);
4045
}

0 commit comments

Comments
 (0)