Add initial ai battle saving
This commit is contained in:
parent
1d89ce2009
commit
2de8d8ab86
14
SharedProject/Utils/ValueHelpers.cs
Normal file
14
SharedProject/Utils/ValueHelpers.cs
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
namespace SharedProject.Utils;
|
||||||
|
|
||||||
|
public static class ValueHelpers
|
||||||
|
{
|
||||||
|
public static T Min<T>(T a, T b) where T : IComparable
|
||||||
|
{
|
||||||
|
return Comparer<T>.Default.Compare(a, b) <= 0 ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T Max<T>(T a, T b) where T : IComparable
|
||||||
|
{
|
||||||
|
return Comparer<T>.Default.Compare(a, b) >= 0 ? a : b;
|
||||||
|
}
|
||||||
|
}
|
@ -16,13 +16,16 @@ public class BaidController : BaseController<BaidController>
|
|||||||
|
|
||||||
private readonly IDanScoreDatumService danScoreDatumService;
|
private readonly IDanScoreDatumService danScoreDatumService;
|
||||||
|
|
||||||
|
private readonly IAiDatumService aiDatumService;
|
||||||
|
|
||||||
public BaidController(IUserDatumService userDatumService, ICardService cardService,
|
public BaidController(IUserDatumService userDatumService, ICardService cardService,
|
||||||
ISongBestDatumService songBestDatumService, IDanScoreDatumService danScoreDatumService)
|
ISongBestDatumService songBestDatumService, IDanScoreDatumService danScoreDatumService, IAiDatumService aiDatumService)
|
||||||
{
|
{
|
||||||
this.userDatumService = userDatumService;
|
this.userDatumService = userDatumService;
|
||||||
this.cardService = cardService;
|
this.cardService = cardService;
|
||||||
this.songBestDatumService = songBestDatumService;
|
this.songBestDatumService = songBestDatumService;
|
||||||
this.danScoreDatumService = danScoreDatumService;
|
this.danScoreDatumService = danScoreDatumService;
|
||||||
|
this.aiDatumService = aiDatumService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -112,6 +115,9 @@ public class BaidController : BaseController<BaidController>
|
|||||||
var genericInfoFlgLength = genericInfoFlg.Any()? genericInfoFlg.Max() + 1 : 0;
|
var genericInfoFlgLength = genericInfoFlg.Any()? genericInfoFlg.Max() + 1 : 0;
|
||||||
var genericInfoFlgArray = FlagCalculator.GetBitArrayFromIds(genericInfoFlg, (int)genericInfoFlgLength, Logger);
|
var genericInfoFlgArray = FlagCalculator.GetBitArrayFromIds(genericInfoFlg, (int)genericInfoFlgLength, Logger);
|
||||||
|
|
||||||
|
var aiScores = await aiDatumService.GetAllAiScoreById(baid);
|
||||||
|
var totalWin = aiScores.Count(datum => datum.IsWin);
|
||||||
|
|
||||||
response = new BAIDResponse
|
response = new BAIDResponse
|
||||||
{
|
{
|
||||||
Result = 1,
|
Result = 1,
|
||||||
@ -159,7 +165,7 @@ public class BaidController : BaseController<BaidController>
|
|||||||
LastPlayMode = userData.LastPlayMode,
|
LastPlayMode = userData.LastPlayMode,
|
||||||
IsDispSouuchiOn = true,
|
IsDispSouuchiOn = true,
|
||||||
AiRank = 0,
|
AiRank = 0,
|
||||||
AiTotalWin = 0,
|
AiTotalWin = (uint)totalWin,
|
||||||
Accesstoken = "123456",
|
Accesstoken = "123456",
|
||||||
ContentInfo = GZipBytesUtil.GetGZipBytes(new byte[10])
|
ContentInfo = GZipBytesUtil.GetGZipBytes(new byte[10])
|
||||||
};
|
};
|
||||||
|
@ -1,19 +1,30 @@
|
|||||||
namespace TaikoLocalServer.Controllers.Game;
|
using TaikoLocalServer.Services.Interfaces;
|
||||||
|
|
||||||
|
namespace TaikoLocalServer.Controllers.Game;
|
||||||
|
|
||||||
[Route("/v12r03/chassis/getaidata.php")]
|
[Route("/v12r03/chassis/getaidata.php")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class GetAiDataController : BaseController<GetAiDataController>
|
public class GetAiDataController : BaseController<GetAiDataController>
|
||||||
{
|
{
|
||||||
|
private readonly IAiDatumService aiDatumService;
|
||||||
|
|
||||||
|
public GetAiDataController(IAiDatumService aiDatumService)
|
||||||
|
{
|
||||||
|
this.aiDatumService = aiDatumService;
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Produces("application/protobuf")]
|
[Produces("application/protobuf")]
|
||||||
public IActionResult GetAiData([FromBody] GetAiDataRequest request)
|
public async Task<IActionResult> GetAiData([FromBody] GetAiDataRequest request)
|
||||||
{
|
{
|
||||||
Logger.LogInformation("GetAiData request : {Request}", request.Stringify());
|
Logger.LogInformation("GetAiData request : {Request}", request.Stringify());
|
||||||
|
|
||||||
|
var aiScoreData = await aiDatumService.GetAllAiScoreById(request.Baid);
|
||||||
|
var totalWin = aiScoreData.Count(datum => datum.IsWin);
|
||||||
var response = new GetAiDataResponse
|
var response = new GetAiDataResponse
|
||||||
{
|
{
|
||||||
Result = 1,
|
Result = 1,
|
||||||
TotalWinnings = 0
|
TotalWinnings = (uint)totalWin
|
||||||
};
|
};
|
||||||
|
|
||||||
return Ok(response);
|
return Ok(response);
|
||||||
|
@ -1,12 +1,22 @@
|
|||||||
namespace TaikoLocalServer.Controllers.Game;
|
using TaikoLocalServer.Services.Interfaces;
|
||||||
|
using Throw;
|
||||||
|
|
||||||
|
namespace TaikoLocalServer.Controllers.Game;
|
||||||
|
|
||||||
[Route("/v12r03/chassis/getaiscore.php")]
|
[Route("/v12r03/chassis/getaiscore.php")]
|
||||||
[ApiController]
|
[ApiController]
|
||||||
public class GetAiScoreController : BaseController<GetAiScoreController>
|
public class GetAiScoreController : BaseController<GetAiScoreController>
|
||||||
{
|
{
|
||||||
|
private readonly IAiDatumService aiDatumService;
|
||||||
|
|
||||||
|
public GetAiScoreController(IAiDatumService aiDatumService)
|
||||||
|
{
|
||||||
|
this.aiDatumService = aiDatumService;
|
||||||
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
[Produces("application/protobuf")]
|
[Produces("application/protobuf")]
|
||||||
public IActionResult GetAiScore([FromBody] GetAiScoreRequest request)
|
public async Task<IActionResult> GetAiScore([FromBody] GetAiScoreRequest request)
|
||||||
{
|
{
|
||||||
Logger.LogInformation("GetAiScore request : {Request}", request.Stringify());
|
Logger.LogInformation("GetAiScore request : {Request}", request.Stringify());
|
||||||
|
|
||||||
@ -15,6 +25,30 @@ public class GetAiScoreController : BaseController<GetAiScoreController>
|
|||||||
Result = 1
|
Result = 1
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var difficulty = (Difficulty)request.Level;
|
||||||
|
difficulty.Throw().IfOutOfRange();
|
||||||
|
|
||||||
|
var aiData = await aiDatumService.GetSongAiScore(request.Baid, request.SongNo, difficulty);
|
||||||
|
if (aiData is null)
|
||||||
|
{
|
||||||
|
return Ok(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var index = 0; index < aiData.AiSectionScoreData.Count; index++)
|
||||||
|
{
|
||||||
|
var sectionScoreDatum = aiData.AiSectionScoreData[index];
|
||||||
|
response.AryBestSectionDatas.Add(new GetAiScoreResponse.AiBestSectionData
|
||||||
|
{
|
||||||
|
Crown = (uint)sectionScoreDatum.Crown,
|
||||||
|
GoodCnt = sectionScoreDatum.GoodCount,
|
||||||
|
OkCnt = sectionScoreDatum.OkCount,
|
||||||
|
NgCnt = sectionScoreDatum.MissCount,
|
||||||
|
PoundCnt = sectionScoreDatum.DrumrollCount,
|
||||||
|
Score = sectionScoreDatum.Score,
|
||||||
|
SectionNo = (uint)index
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// There's either 3 or 5 total sections
|
// There's either 3 or 5 total sections
|
||||||
// SectionNo doesn't seem to actually affect which section is being assigned to, only the List order matters
|
// SectionNo doesn't seem to actually affect which section is being assigned to, only the List order matters
|
||||||
/*response.AryBestSectionDatas.Add(new GetAiScoreResponse.AiBestSectionData()
|
/*response.AryBestSectionDatas.Add(new GetAiScoreResponse.AiBestSectionData()
|
||||||
|
@ -20,13 +20,16 @@ public class PlayResultController : BaseController<PlayResultController>
|
|||||||
|
|
||||||
private readonly IDanScoreDatumService danScoreDatumService;
|
private readonly IDanScoreDatumService danScoreDatumService;
|
||||||
|
|
||||||
|
private readonly IAiDatumService aiDatumService;
|
||||||
|
|
||||||
public PlayResultController(IUserDatumService userDatumService, ISongPlayDatumService songPlayDatumService,
|
public PlayResultController(IUserDatumService userDatumService, ISongPlayDatumService songPlayDatumService,
|
||||||
ISongBestDatumService songBestDatumService, IDanScoreDatumService danScoreDatumService)
|
ISongBestDatumService songBestDatumService, IDanScoreDatumService danScoreDatumService, IAiDatumService aiDatumService)
|
||||||
{
|
{
|
||||||
this.userDatumService = userDatumService;
|
this.userDatumService = userDatumService;
|
||||||
this.songPlayDatumService = songPlayDatumService;
|
this.songPlayDatumService = songPlayDatumService;
|
||||||
this.songBestDatumService = songBestDatumService;
|
this.songBestDatumService = songBestDatumService;
|
||||||
this.danScoreDatumService = danScoreDatumService;
|
this.danScoreDatumService = danScoreDatumService;
|
||||||
|
this.aiDatumService = aiDatumService;
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
@ -77,9 +80,7 @@ public class PlayResultController : BaseController<PlayResultController>
|
|||||||
|
|
||||||
if (playMode == PlayMode.AiBattle)
|
if (playMode == PlayMode.AiBattle)
|
||||||
{
|
{
|
||||||
// await UpdateAiBattleData(request, stageData);
|
await UpdateAiBattleData(request, stageData);
|
||||||
// Update AI win count here somewhere, or in UpdatePlayData?
|
|
||||||
// I have no clue how to update input median or variance
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await UpdateBestData(request, stageData, bestData);
|
await UpdateBestData(request, stageData, bestData);
|
||||||
@ -163,7 +164,7 @@ public class PlayResultController : BaseController<PlayResultController>
|
|||||||
ComboCount = stageData.ComboCnt,
|
ComboCount = stageData.ComboCnt,
|
||||||
HitCount = stageData.HitCnt,
|
HitCount = stageData.HitCnt,
|
||||||
DrumrollCount = stageData.PoundCnt,
|
DrumrollCount = stageData.PoundCnt,
|
||||||
Crown = PlayResultToCrown(stageData),
|
Crown = PlayResultToCrown(stageData.PlayResult, stageData.OkCnt),
|
||||||
Score = stageData.PlayScore,
|
Score = stageData.PlayScore,
|
||||||
ScoreRate = stageData.ScoreRate,
|
ScoreRate = stageData.ScoreRate,
|
||||||
ScoreRank = (ScoreRank)stageData.ScoreRank,
|
ScoreRank = (ScoreRank)stageData.ScoreRank,
|
||||||
@ -294,7 +295,7 @@ public class PlayResultController : BaseController<PlayResultController>
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Determine whether it is dondaful crown as this is not reflected by play result
|
// Determine whether it is dondaful crown as this is not reflected by play result
|
||||||
var crown = PlayResultToCrown(stageData);
|
var crown = PlayResultToCrown(stageData.PlayResult, stageData.OkCnt);
|
||||||
|
|
||||||
bestDatum.UpdateBestData(crown, stageData.ScoreRank, stageData.PlayScore, stageData.ScoreRate);
|
bestDatum.UpdateBestData(crown, stageData.ScoreRank, stageData.PlayScore, stageData.ScoreRate);
|
||||||
|
|
||||||
@ -302,23 +303,67 @@ public class PlayResultController : BaseController<PlayResultController>
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO: AI battle
|
// TODO: AI battle
|
||||||
/*private async Task UpdateAiBattleData(PlayResultRequest request, StageData stageData)
|
private async Task UpdateAiBattleData(PlayResultRequest request, StageData stageData)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < stageData.ArySectionDatas.Count; i++)
|
var difficulty = (Difficulty)stageData.Level;
|
||||||
|
difficulty.Throw().IfOutOfRange();
|
||||||
|
var existing = await aiDatumService.GetSongAiScore(request.BaidConf,
|
||||||
|
stageData.SongNo, difficulty);
|
||||||
|
|
||||||
|
if (existing is null)
|
||||||
{
|
{
|
||||||
// Only update crown if it's a higher crown than the previous best crown
|
var aiScoreDatum = new AiScoreDatum
|
||||||
|
{
|
||||||
|
Baid = request.BaidConf,
|
||||||
|
SongId = stageData.SongNo,
|
||||||
|
Difficulty = difficulty,
|
||||||
|
IsWin = stageData.IsWin
|
||||||
|
};
|
||||||
|
for (var index = 0; index < stageData.ArySectionDatas.Count; index++)
|
||||||
|
{
|
||||||
|
AddNewAiSectionScore(request, stageData, index, difficulty, aiScoreDatum);
|
||||||
|
}
|
||||||
|
|
||||||
// Maybe have a "SectionNo" variable for which section number it is on the DB
|
await aiDatumService.InsertSongAiScore(aiScoreDatum);
|
||||||
// compare DB.SectionNo == i
|
return;
|
||||||
// if any aspect of the section is higher than the previous best, update it
|
|
||||||
// Similar to Dan best play updates
|
|
||||||
}
|
}
|
||||||
}*/
|
|
||||||
|
|
||||||
private static CrownType PlayResultToCrown(StageData stageData)
|
for (var index = 0; index < stageData.ArySectionDatas.Count; index++)
|
||||||
|
{
|
||||||
|
var sectionData = stageData.ArySectionDatas[index];
|
||||||
|
if (index < existing.AiSectionScoreData.Count)
|
||||||
|
{
|
||||||
|
existing.AiSectionScoreData[index].UpdateBest(sectionData);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AddNewAiSectionScore(request,stageData,index,difficulty,existing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await aiDatumService.UpdateSongAiScore(existing);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddNewAiSectionScore(PlayResultRequest request, StageData stageData, int index, Difficulty difficulty,
|
||||||
|
AiScoreDatum aiScoreDatum)
|
||||||
{
|
{
|
||||||
var crown = (CrownType)stageData.PlayResult;
|
var sectionData = stageData.ArySectionDatas[index];
|
||||||
if (crown == CrownType.Gold && stageData.OkCnt == 0)
|
var aiSectionScoreDatum = new AiSectionScoreDatum
|
||||||
|
{
|
||||||
|
Baid = request.BaidConf,
|
||||||
|
SongId = stageData.SongNo,
|
||||||
|
Difficulty = difficulty,
|
||||||
|
SectionIndex = index
|
||||||
|
};
|
||||||
|
aiSectionScoreDatum.UpdateBest(sectionData);
|
||||||
|
aiScoreDatum.AiSectionScoreData.Add(aiSectionScoreDatum);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static CrownType PlayResultToCrown(uint playResult, uint okCount)
|
||||||
|
{
|
||||||
|
var crown = (CrownType)playResult;
|
||||||
|
if (crown == CrownType.Gold && okCount == 0)
|
||||||
{
|
{
|
||||||
crown = CrownType.Dondaful;
|
crown = CrownType.Dondaful;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
namespace TaikoLocalServer.Entities;
|
using SharedProject.Utils;
|
||||||
|
|
||||||
|
namespace TaikoLocalServer.Entities;
|
||||||
|
|
||||||
public class AiSectionScoreDatum
|
public class AiSectionScoreDatum
|
||||||
{
|
{
|
||||||
@ -25,4 +27,21 @@ public class AiSectionScoreDatum
|
|||||||
public uint DrumrollCount { get; set; }
|
public uint DrumrollCount { get; set; }
|
||||||
|
|
||||||
public AiScoreDatum Parent { get; set; } = null!;
|
public AiScoreDatum Parent { get; set; } = null!;
|
||||||
|
|
||||||
|
public void UpdateBest(PlayResultDataRequest.StageData.AiStageSectionData sectionData)
|
||||||
|
{
|
||||||
|
var crown = (CrownType)sectionData.Crown;
|
||||||
|
if (crown == CrownType.Gold && sectionData.OkCnt == 0)
|
||||||
|
{
|
||||||
|
crown = CrownType.Dondaful;
|
||||||
|
}
|
||||||
|
|
||||||
|
IsWin = sectionData.IsWin ? sectionData.IsWin : IsWin;
|
||||||
|
Crown = ValueHelpers.Max(crown, Crown);
|
||||||
|
Score = ValueHelpers.Max(sectionData.Score, Score);
|
||||||
|
GoodCount = ValueHelpers.Max(sectionData.GoodCnt, GoodCount);
|
||||||
|
OkCount = ValueHelpers.Min(sectionData.OkCnt, OkCount);
|
||||||
|
MissCount = ValueHelpers.Min(sectionData.NgCnt, MissCount);
|
||||||
|
DrumrollCount = ValueHelpers.Max(sectionData.PoundCnt, DrumrollCount);
|
||||||
|
}
|
||||||
}
|
}
|
50
TaikoLocalServer/Services/AiDatumService.cs
Normal file
50
TaikoLocalServer/Services/AiDatumService.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using TaikoLocalServer.Services.Interfaces;
|
||||||
|
using Throw;
|
||||||
|
|
||||||
|
namespace TaikoLocalServer.Services;
|
||||||
|
|
||||||
|
public class AiDatumService : IAiDatumService
|
||||||
|
{
|
||||||
|
private readonly TaikoDbContext context;
|
||||||
|
|
||||||
|
public AiDatumService(TaikoDbContext context)
|
||||||
|
{
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<List<AiScoreDatum>> GetAllAiScoreById(uint baid)
|
||||||
|
{
|
||||||
|
return await context.AiScoreData.Where(datum => datum.Baid == baid)
|
||||||
|
.Include(datum => datum.AiSectionScoreData)
|
||||||
|
.ToListAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<AiScoreDatum?> GetSongAiScore(uint baid, uint songId, Difficulty difficulty)
|
||||||
|
{
|
||||||
|
return await context.AiScoreData.Where(datum => datum.Baid == baid &&
|
||||||
|
datum.SongId == songId &&
|
||||||
|
datum.Difficulty == difficulty)
|
||||||
|
.Include(datum => datum.AiSectionScoreData)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task UpdateSongAiScore(AiScoreDatum datum)
|
||||||
|
{
|
||||||
|
var existing = await context.AiScoreData.FindAsync(datum.Baid, datum.SongId, datum.Difficulty);
|
||||||
|
existing.ThrowIfNull("Cannot update a non-existing ai score!");
|
||||||
|
|
||||||
|
context.AiScoreData.Update(datum);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task InsertSongAiScore(AiScoreDatum datum)
|
||||||
|
{
|
||||||
|
var existing = await context.AiScoreData.FindAsync(datum.Baid, datum.SongId, datum.Difficulty);
|
||||||
|
if (existing is not null)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Ai score already exists!", nameof(datum));
|
||||||
|
}
|
||||||
|
context.AiScoreData.Add(datum);
|
||||||
|
await context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
@ -30,7 +30,7 @@ public class DanScoreDatumService : IDanScoreDatumService
|
|||||||
var existing = await context.DanScoreData.FindAsync(datum.Baid, datum.DanId);
|
var existing = await context.DanScoreData.FindAsync(datum.Baid, datum.DanId);
|
||||||
if (existing is null)
|
if (existing is null)
|
||||||
{
|
{
|
||||||
await context.DanScoreData.AddAsync(datum);
|
context.DanScoreData.Add(datum);
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ public static class ServiceExtensions
|
|||||||
services.AddScoped<ISongPlayDatumService, SongPlayDatumService>();
|
services.AddScoped<ISongPlayDatumService, SongPlayDatumService>();
|
||||||
services.AddScoped<ISongBestDatumService, SongBestDatumService>();
|
services.AddScoped<ISongBestDatumService, SongBestDatumService>();
|
||||||
services.AddScoped<IDanScoreDatumService, DanScoreDatumService>();
|
services.AddScoped<IDanScoreDatumService, DanScoreDatumService>();
|
||||||
|
services.AddScoped<IAiDatumService, AiDatumService>();
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
12
TaikoLocalServer/Services/Interfaces/IAiDatumService.cs
Normal file
12
TaikoLocalServer/Services/Interfaces/IAiDatumService.cs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
namespace TaikoLocalServer.Services.Interfaces;
|
||||||
|
|
||||||
|
public interface IAiDatumService
|
||||||
|
{
|
||||||
|
public Task<List<AiScoreDatum>> GetAllAiScoreById(uint baid);
|
||||||
|
|
||||||
|
public Task<AiScoreDatum?> GetSongAiScore(uint baid, uint songId, Difficulty difficulty);
|
||||||
|
|
||||||
|
public Task UpdateSongAiScore(AiScoreDatum datum);
|
||||||
|
|
||||||
|
public Task InsertSongAiScore(AiScoreDatum datum);
|
||||||
|
}
|
@ -32,7 +32,7 @@ public class SongBestDatumService : ISongBestDatumService
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await context.SongBestData.AddAsync(datum);
|
context.SongBestData.Add(datum);
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ class SongPlayDatumService : ISongPlayDatumService
|
|||||||
|
|
||||||
public async Task AddSongPlayDatum(SongPlayDatum datum)
|
public async Task AddSongPlayDatum(SongPlayDatum datum)
|
||||||
{
|
{
|
||||||
await context.SongPlayData.AddAsync(datum);
|
context.SongPlayData.Add(datum);
|
||||||
await context.SaveChangesAsync();
|
await context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user