feat(Assets): update asset services and background workers
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
using FinlyticAssets.Database;
|
||||
using FinlyticAssets.Entities;
|
||||
using FinlyticAssets.Models.DataToObject.TradeRepublic;
|
||||
using FinlyticCore.Entities.Assets;
|
||||
using FinlyticCore.Models.TradeRepublic;
|
||||
using FinlyticCore.Services.TradeRepublic;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace FinlyticAssets.Services;
|
||||
@@ -11,63 +12,15 @@ namespace FinlyticAssets.Services;
|
||||
/// </summary>
|
||||
public interface IAssetsDbService
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all active assets from the database that have been updated within the last 14 days, including their associated tags.
|
||||
/// Assets older than 14 days are filtered out as they are considered de-listed or inactive.
|
||||
/// </summary>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains a list of all active <see cref="AssetEntity"/> instances.</returns>
|
||||
public Task<List<AssetEntity>> GetAllValidAssetsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Adds a new asset or updates an existing one based on the composite key of ISIN and InstrumentType.
|
||||
/// </summary>
|
||||
/// <param name="dtoAsset">The incoming asset data transfer object from the API.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains <c>true</c> if the asset was updated or created; otherwise, <c>false</c>.</returns>
|
||||
public Task<bool> AddOrUpdateAssetAsync(TradeRepublicAsset dtoAsset);
|
||||
|
||||
/// <summary>
|
||||
/// Batches processing for a collection of asset items, returning the total amount of modified or newly added entries.
|
||||
/// </summary>
|
||||
/// <param name="dtoAssets">The collection of incoming asset objects to process.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains the count of changed or added entries.</returns>
|
||||
public Task<int> AddOrUpdateAssetsAsync(IEnumerable<TradeRepublicAsset> dtoAssets);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all assets matching a specific International Securities Identification Number (ISIN).
|
||||
/// Can return multiple entities (e.g., both the stock and the derivative tracking asset for the same ISIN).
|
||||
/// </summary>
|
||||
/// <param name="isin">The ISIN value to look up.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains a list of matching <see cref="AssetEntity"/> instances.</returns>
|
||||
public Task<List<AssetEntity>> GetAssetsByIsinAsync(string isin);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves all assets matching a specific ISIN only if they have been updated within the last 14 days.
|
||||
/// Assets older than 14 days are considered de-listed or inactive.
|
||||
/// </summary>
|
||||
/// <param name="isin">The ISIN value to look up.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains a list of matching active <see cref="AssetEntity"/> instances.</returns>
|
||||
public Task<List<AssetEntity>> GetValidAssetsByIsinAsync(string isin);
|
||||
|
||||
/// <summary>
|
||||
/// Scans the database for active assets matching a combined, comma-separated search query.
|
||||
/// The search applies an AND-logic approach where every extracted keyword must be found within an asset's ISIN, name, or tags.
|
||||
/// Evaluates local records first and utilizes a targeted JIT-fallback to the external API for any search term formatted as a valid, completely unknown ISIN.
|
||||
/// </summary>
|
||||
/// <param name="searchQuery">A comma-separated string containing the keywords, ISINs, or tags to search for (e.g., "Siemens, Medic" or "IE00B4L5Y983, ETF").</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains a list of all affected active <see cref="AssetEntity"/> instances.</returns>
|
||||
public Task<List<AssetEntity>> FindAffectedActiveAssetsAsync(string searchQuery);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes all asset records associated with a specific ISIN from the database.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Use with caution. For standard maintenance and handling de-listed instruments,
|
||||
/// rely on the 14-day recency filter provided by <see cref="GetValidAssetsByIsinAsync"/> instead of hard deletion.
|
||||
/// </remarks>
|
||||
/// <param name="isin">The ISIN value of the assets to remove.</param>
|
||||
/// <returns>A task that represents the asynchronous operation. The task result contains <c>true</c> if any records were successfully deleted; otherwise, <c>false</c>.</returns>
|
||||
public Task UpdateAssetImageIdAsync(string isin, string imageId);
|
||||
public Task<bool> DeleteAssetAsync(string isin);
|
||||
public Task<List<AssetEntity>> GetDiscoveryAssetsAsync(int limit = 15);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -76,33 +29,77 @@ public class AssetsDbService : IAssetsDbService
|
||||
private readonly AssetsDbContext _context;
|
||||
private readonly ITradeRepublicService _tradeRepublicService;
|
||||
private readonly ILogger<AssetsDbService> _logger;
|
||||
private readonly Random _random = new();
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="AssetsDbService"/> class.
|
||||
/// </summary>
|
||||
public AssetsDbService(AssetsDbContext context, ILogger<AssetsDbService> logger,
|
||||
ITradeRepublicService tradeRepublicService)
|
||||
public AssetsDbService(AssetsDbContext context, ILogger<AssetsDbService> logger, ITradeRepublicService tradeRepublicService)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
_tradeRepublicService = tradeRepublicService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<List<AssetEntity>> GetAllValidAssetsAsync()
|
||||
/// <summary>Inherits documentation from interface.</summary>
|
||||
public async Task<List<AssetEntity>> GetDiscoveryAssetsAsync(int limit = 15)
|
||||
{
|
||||
var cutoff = DateTime.UtcNow.AddDays(-14);
|
||||
|
||||
var existingEntity = await _context.TradeRepublicAssets
|
||||
var cutoff = DateTime.UtcNow.AddDays(-90);
|
||||
|
||||
var validAssets = await _context.TradeRepublicAssets
|
||||
.AsNoTracking()
|
||||
.Include(a => a.Tags)
|
||||
.Where(a => a.UpdateAt >= cutoff)
|
||||
.Where(a => a.LastUpdatedAt >= cutoff && !string.IsNullOrEmpty(a.Name))
|
||||
.ToListAsync();
|
||||
|
||||
return existingEntity;
|
||||
if (validAssets.Count == 0) return [];
|
||||
|
||||
var scored = validAssets
|
||||
.Select(a => new
|
||||
{
|
||||
Asset = a,
|
||||
Score = (a.Tags?.Count ?? 0) * 10
|
||||
+ (a.HasCfd ? 5 : 0)
|
||||
+ (string.IsNullOrEmpty(a.ImageId) ? 0 : 15)
|
||||
+ (a.Name.Length > 3 ? 5 : 0)
|
||||
})
|
||||
.OrderByDescending(x => x.Score)
|
||||
.ThenByDescending(x => x.Asset.LastUpdatedAt)
|
||||
.ToList();
|
||||
|
||||
var result = new List<AssetEntity>();
|
||||
var grouped = scored.GroupBy(x => x.Asset.Type).ToList();
|
||||
|
||||
int index = 0;
|
||||
while (result.Count < limit && grouped.Any(g => g.Any()))
|
||||
{
|
||||
bool addedAny = false;
|
||||
foreach (var group in grouped)
|
||||
{
|
||||
var item = group.Skip(index).FirstOrDefault();
|
||||
if (item != null)
|
||||
{
|
||||
result.Add(item.Asset);
|
||||
addedAny = true;
|
||||
if (result.Count >= limit) break;
|
||||
}
|
||||
}
|
||||
index++;
|
||||
if (!addedAny) break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
/// <summary>Inherits documentation from interface.</summary>
|
||||
public async Task<List<AssetEntity>> GetAllValidAssetsAsync()
|
||||
{
|
||||
var cutoff = DateTime.UtcNow.AddDays(-90);
|
||||
|
||||
return await _context.TradeRepublicAssets
|
||||
.AsNoTracking()
|
||||
.Include(a => a.Tags)
|
||||
.Where(a => a.LastUpdatedAt >= cutoff)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>Inherits documentation from interface.</summary>
|
||||
public async Task<bool> AddOrUpdateAssetAsync(TradeRepublicAsset dtoAsset)
|
||||
{
|
||||
var existingEntity = await _context.TradeRepublicAssets
|
||||
@@ -110,7 +107,20 @@ public class AssetsDbService : IAssetsDbService
|
||||
.FirstOrDefaultAsync(a => a.Isin == dtoAsset.Isin);
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var nextUpdateScheduledAt = await CalculateNextUpdateDateAsync(now);
|
||||
|
||||
var mappedTags = new List<TagEntity>();
|
||||
foreach (var tagDto in dtoAsset.Tags ?? Array.Empty<TradeRepublicTag>())
|
||||
{
|
||||
var existingTag = await _context.TradeRepublicTags.FirstOrDefaultAsync(t => t.Id == tagDto.Id);
|
||||
if (existingTag == null)
|
||||
{
|
||||
_logger.LogTrace("Creating missing asset tag object in storage cache context. ID: {TagId}", tagDto.Id);
|
||||
existingTag = new TagEntity { Id = tagDto.Id, Name = tagDto.Name, Type = tagDto.Type };
|
||||
await _context.TradeRepublicTags.AddAsync(existingTag);
|
||||
}
|
||||
|
||||
mappedTags.Add(existingTag);
|
||||
}
|
||||
|
||||
if (existingEntity == null)
|
||||
{
|
||||
@@ -118,53 +128,125 @@ public class AssetsDbService : IAssetsDbService
|
||||
|
||||
var newEntity = MapDtoToEntity(dtoAsset);
|
||||
newEntity.LastUpdatedAt = now;
|
||||
newEntity.UpdateAt = nextUpdateScheduledAt;
|
||||
|
||||
newEntity.Tags = await MapTagsAsync(dtoAsset.Tags);
|
||||
newEntity.Tags = mappedTags;
|
||||
|
||||
await _context.TradeRepublicAssets.AddAsync(newEntity);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
_logger.LogDebug("Asset with ISIN {Isin} exists. Merging properties and updating database record.",
|
||||
dtoAsset.Isin);
|
||||
_logger.LogDebug("Asset with ISIN {Isin} exists. Merging properties and updating database record.", dtoAsset.Isin);
|
||||
|
||||
existingEntity.Name = dtoAsset.Name;
|
||||
existingEntity.Type = dtoAsset.Type;
|
||||
existingEntity.InstrumentCategory = dtoAsset.InstrumentCategory;
|
||||
existingEntity.HasCfd = dtoAsset.HasCfd;
|
||||
existingEntity.ImageId = dtoAsset.ImageId;
|
||||
|
||||
existingEntity.LastUpdatedAt = now;
|
||||
existingEntity.UpdateAt = nextUpdateScheduledAt;
|
||||
|
||||
UpdateSubtypeProperties(existingEntity, dtoAsset);
|
||||
existingEntity.Tags = await MapTagsAsync(dtoAsset.Tags);
|
||||
existingEntity.Tags = mappedTags;
|
||||
|
||||
_context.TradeRepublicAssets.Update(existingEntity);
|
||||
await _context.SaveChangesAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Inherits documentation from interface.</summary>
|
||||
public async Task<int> AddOrUpdateAssetsAsync(IEnumerable<TradeRepublicAsset> dtoAssets)
|
||||
{
|
||||
var assetsList = dtoAssets.ToList();
|
||||
if (assetsList.Count == 0) return 0;
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
int changedCount = 0;
|
||||
|
||||
foreach (var dto in dtoAssets)
|
||||
var isins = assetsList.Select(a => a.Isin).Distinct().ToList();
|
||||
var existingAssets = await _context.TradeRepublicAssets
|
||||
.Include(a => a.Tags)
|
||||
.Where(a => isins.Contains(a.Isin))
|
||||
.ToDictionaryAsync(a => a.Isin);
|
||||
|
||||
var tagIds = assetsList
|
||||
.SelectMany(a => a.Tags ?? Array.Empty<TradeRepublicTag>())
|
||||
.Select(t => t.Id)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
var tagCache = await _context.TradeRepublicTags
|
||||
.Where(t => tagIds.Contains(t.Id))
|
||||
.ToDictionaryAsync(t => t.Id);
|
||||
|
||||
foreach (var dto in assetsList)
|
||||
{
|
||||
var isChanged = await AddOrUpdateAssetAsync(dto);
|
||||
var isChanged = false;
|
||||
var existingEntity = existingAssets.GetValueOrDefault(dto.Isin);
|
||||
|
||||
var mappedTags = new List<TagEntity>();
|
||||
foreach (var tagDto in dto.Tags ?? Array.Empty<TradeRepublicTag>())
|
||||
{
|
||||
if (!tagCache.TryGetValue(tagDto.Id, out var tagEntity))
|
||||
{
|
||||
_logger.LogTrace("Creating missing asset tag object in storage cache context. ID: {TagId}", tagDto.Id);
|
||||
tagEntity = new TagEntity { Id = tagDto.Id, Name = tagDto.Name, Type = tagDto.Type };
|
||||
await _context.TradeRepublicTags.AddAsync(tagEntity);
|
||||
tagCache.Add(tagDto.Id, tagEntity);
|
||||
}
|
||||
mappedTags.Add(tagEntity);
|
||||
}
|
||||
|
||||
if (existingEntity == null)
|
||||
{
|
||||
_logger.LogDebug("Asset with ISIN {Isin} not found. Mapping and creating a new entity.", dto.Isin);
|
||||
|
||||
var newEntity = MapDtoToEntity(dto);
|
||||
newEntity.LastUpdatedAt = now;
|
||||
newEntity.Tags = mappedTags;
|
||||
|
||||
await _context.TradeRepublicAssets.AddAsync(newEntity);
|
||||
isChanged = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogDebug("Asset with ISIN {Isin} exists. Merging properties and updating database record.", dto.Isin);
|
||||
|
||||
if (existingEntity.Name != dto.Name ||
|
||||
existingEntity.Type != dto.Type ||
|
||||
existingEntity.InstrumentCategory != dto.InstrumentCategory ||
|
||||
existingEntity.HasCfd != dto.HasCfd ||
|
||||
existingEntity.ImageId != dto.ImageId ||
|
||||
!existingEntity.Tags.SequenceEqual(mappedTags))
|
||||
{
|
||||
existingEntity.Name = dto.Name;
|
||||
existingEntity.Type = dto.Type;
|
||||
existingEntity.InstrumentCategory = dto.InstrumentCategory;
|
||||
existingEntity.HasCfd = dto.HasCfd;
|
||||
existingEntity.ImageId = dto.ImageId;
|
||||
existingEntity.LastUpdatedAt = now;
|
||||
|
||||
UpdateSubtypeProperties(existingEntity, dto);
|
||||
existingEntity.Tags = mappedTags;
|
||||
|
||||
_context.TradeRepublicAssets.Update(existingEntity);
|
||||
isChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isChanged)
|
||||
{
|
||||
changedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
if (changedCount > 0)
|
||||
{
|
||||
await _context.SaveChangesAsync();
|
||||
}
|
||||
|
||||
return changedCount;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Inherits documentation from interface.</summary>
|
||||
public async Task<List<AssetEntity>> GetAssetsByIsinAsync(string isin)
|
||||
{
|
||||
var localAssets = await _context.TradeRepublicAssets
|
||||
@@ -172,26 +254,30 @@ public class AssetsDbService : IAssetsDbService
|
||||
.Where(a => a.Isin == isin)
|
||||
.ToListAsync();
|
||||
|
||||
if (localAssets.Any())
|
||||
if (localAssets.Count > 0)
|
||||
{
|
||||
return localAssets;
|
||||
}
|
||||
|
||||
// JIT-Fetch via API
|
||||
var trAssetDto = await _tradeRepublicService.GetAsset(isin);
|
||||
if (trAssetDto != null)
|
||||
if (trAssetDto?.Results != null && trAssetDto.Results.Count > 0)
|
||||
{
|
||||
foreach (var asset in trAssetDto.Results)
|
||||
{
|
||||
_ = await AddOrUpdateAssetAsync(asset);
|
||||
await AddOrUpdateAssetAsync(asset);
|
||||
}
|
||||
|
||||
return await GetAssetsByIsinAsync(isin);
|
||||
return await _context.TradeRepublicAssets
|
||||
.Include(a => a.Tags)
|
||||
.Where(a => a.Isin == isin)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Inherits documentation from interface.</summary>
|
||||
public async Task<List<AssetEntity>> GetValidAssetsByIsinAsync(string isin)
|
||||
{
|
||||
var cutoff = DateTime.UtcNow.AddDays(-14);
|
||||
@@ -199,23 +285,20 @@ public class AssetsDbService : IAssetsDbService
|
||||
return await _context.TradeRepublicAssets
|
||||
.AsNoTracking()
|
||||
.Include(a => a.Tags)
|
||||
.Where(a => a.Isin == isin && a.UpdateAt >= cutoff)
|
||||
.Where(a => a.Isin == isin && a.LastUpdatedAt >= cutoff)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Inherits documentation from interface.</summary>
|
||||
public async Task<List<AssetEntity>> FindAffectedActiveAssetsAsync(string searchQuery)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(searchQuery))
|
||||
{
|
||||
return [];
|
||||
}
|
||||
if (string.IsNullOrWhiteSpace(searchQuery)) return [];
|
||||
|
||||
var cutoff = DateTime.UtcNow.AddDays(-14);
|
||||
|
||||
var searchTerms = searchQuery
|
||||
var searchTerms = searchQuery
|
||||
.Split(',')
|
||||
.Select(t => t.Trim().ToLower())
|
||||
.Select(t => t.Trim())
|
||||
.Where(t => !string.IsNullOrEmpty(t))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
@@ -224,17 +307,17 @@ public class AssetsDbService : IAssetsDbService
|
||||
|
||||
var query = _context.TradeRepublicAssets
|
||||
.AsNoTracking()
|
||||
.Where(a => a.UpdateAt >= cutoff)
|
||||
.Where(a => a.LastUpdatedAt >= cutoff)
|
||||
.Include(a => a.Tags)
|
||||
.AsQueryable();
|
||||
|
||||
|
||||
foreach (var term in searchTerms)
|
||||
{
|
||||
var lowerTerm = term.ToLower();
|
||||
query = query.Where(a =>
|
||||
a.Isin.ToLower().Contains(term) ||
|
||||
a.Name.ToLower().Contains(term) ||
|
||||
a.Tags.Any(tag => tag.Name.ToLower().Contains(term)));
|
||||
a.Isin.ToLower().Contains(lowerTerm) ||
|
||||
a.Name.ToLower().Contains(lowerTerm) ||
|
||||
a.Tags.Any(tag => tag.Name.ToLower().Contains(lowerTerm)));
|
||||
}
|
||||
|
||||
var localAssets = await query.ToListAsync();
|
||||
@@ -244,12 +327,12 @@ public class AssetsDbService : IAssetsDbService
|
||||
.Select(t => t.ToUpper())
|
||||
.ToList();
|
||||
|
||||
if (possibleIsins.Any())
|
||||
if (possibleIsins.Count > 0)
|
||||
{
|
||||
var foundIsins = localAssets.Select(a => a.Isin).ToHashSet();
|
||||
var missingIsins = possibleIsins.Where(isin => !foundIsins.Contains(isin)).ToList();
|
||||
|
||||
if (missingIsins.Any())
|
||||
if (missingIsins.Count > 0)
|
||||
{
|
||||
var fetchedNewAsset = false;
|
||||
foreach (var missingIsin in missingIsins)
|
||||
@@ -276,57 +359,42 @@ public class AssetsDbService : IAssetsDbService
|
||||
return localAssets;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>Inherits documentation from interface.</summary>
|
||||
public async Task UpdateAssetImageIdAsync(string isin, string imageId)
|
||||
{
|
||||
var existingAssets = await _context.TradeRepublicAssets
|
||||
.Where(a => a.Isin == isin)
|
||||
.ToListAsync();
|
||||
|
||||
if (existingAssets.Count > 0)
|
||||
{
|
||||
foreach (var asset in existingAssets)
|
||||
{
|
||||
asset.ImageId = imageId;
|
||||
}
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInformation("[{Channel}] Updated ImageId for ISIN {Isin} in database to '{ImageId}'", "AssetsChannel", isin, imageId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Inherits documentation from interface.</summary>
|
||||
public async Task<bool> DeleteAssetAsync(string isin)
|
||||
{
|
||||
var asset = await _context.TradeRepublicAssets.FirstOrDefaultAsync(a => a.Isin == isin);
|
||||
if (asset == null)
|
||||
{
|
||||
_logger.LogWarning("Delete execution cancelled. Asset with ISIN {Isin} does not exist.", isin);
|
||||
_logger.LogWarning("[{Channel}] Delete execution cancelled. Asset with ISIN {Isin} does not exist.", "AssetsChannel", isin);
|
||||
return false;
|
||||
}
|
||||
|
||||
_context.TradeRepublicAssets.Remove(asset);
|
||||
await _context.SaveChangesAsync();
|
||||
_logger.LogInformation("Asset with ISIN {Isin} has been successfully deleted.", isin);
|
||||
_logger.LogInformation("[{Channel}] Asset with ISIN {Isin} has been successfully deleted.", "AssetsChannel", isin);
|
||||
return true;
|
||||
}
|
||||
|
||||
#region Helper & Mapping Methods
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the next synchronization/update date for an asset using configured parameters and a random offset.
|
||||
/// </summary>
|
||||
/// <param name="baseDate">The baseline date to add the offsets to.</param>
|
||||
/// <returns>A task representing the asynchronous operation, returning a DateTime representing the next scheduled update time (UTC).</returns>
|
||||
private async Task<DateTime> CalculateNextUpdateDateAsync(DateTime baseDate)
|
||||
{
|
||||
var settings = await _context.Set<Settings>().FirstOrDefaultAsync() ?? new Settings();
|
||||
|
||||
int randomDays = _random.Next(settings.MinRandomUpdateDay, settings.MaxRandomUpdateDay + 1);
|
||||
var targetDate = baseDate.AddDays(randomDays);
|
||||
|
||||
int randomHour = _random.Next(settings.UpdateDayTimeStart, settings.UpdateDayTimeStop);
|
||||
int randomMinute = _random.Next(0, 60);
|
||||
int randomSecond = _random.Next(0, 60);
|
||||
|
||||
return new DateTime(
|
||||
targetDate.Year,
|
||||
targetDate.Month,
|
||||
targetDate.Day,
|
||||
randomHour,
|
||||
randomMinute,
|
||||
randomSecond,
|
||||
DateTimeKind.Utc
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a raw Trade Republic asset data transfer object (DTO) to its matching database entity subtype.
|
||||
/// </summary>
|
||||
/// <param name="dto">The source Trade Republic asset data transfer object.</param>
|
||||
/// <returns>A newly created subtype instance of <see cref="AssetEntity"/> mapped with the DTO properties.</returns>
|
||||
/// <exception cref="NotSupportedException">Thrown when the DTO type is unrecognized or unsupported.</exception>
|
||||
private AssetEntity MapDtoToEntity(TradeRepublicAsset dto)
|
||||
{
|
||||
AssetEntity entity = dto switch
|
||||
@@ -356,12 +424,6 @@ public class AssetsDbService : IAssetsDbService
|
||||
return PopulateBaseProperties(entity, dto);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates common base properties of a database asset entity using a Trade Republic DTO.
|
||||
/// </summary>
|
||||
/// <param name="entity">The target database entity.</param>
|
||||
/// <param name="dto">The source Trade Republic DTO.</param>
|
||||
/// <returns>The updated database asset entity.</returns>
|
||||
private AssetEntity PopulateBaseProperties(AssetEntity entity, TradeRepublicAsset dto)
|
||||
{
|
||||
entity.Name = dto.Name;
|
||||
@@ -372,11 +434,6 @@ public class AssetsDbService : IAssetsDbService
|
||||
return entity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Merges/updates the subtype-specific properties from a Trade Republic DTO into an existing database entity.
|
||||
/// </summary>
|
||||
/// <param name="entity">The existing database entity to update.</param>
|
||||
/// <param name="dto">The source Trade Republic DTO.</param>
|
||||
private void UpdateSubtypeProperties(AssetEntity entity, TradeRepublicAsset dto)
|
||||
{
|
||||
switch (entity)
|
||||
@@ -409,29 +466,5 @@ public class AssetsDbService : IAssetsDbService
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Maps a list of Trade Republic tags to database entity instances, registering new tags in the database context if they do not yet exist.
|
||||
/// </summary>
|
||||
/// <param name="dtos">The read-only collection of Trade Republic tags.</param>
|
||||
/// <returns>A task representing the asynchronous operation, returning the list of mapped database tag entities.</returns>
|
||||
private async Task<List<TagEntity>> MapTagsAsync(IReadOnlyList<TradeRepublicTag> dtos)
|
||||
{
|
||||
var tags = new List<TagEntity>();
|
||||
foreach (var tagDto in dtos)
|
||||
{
|
||||
var existingTag = await _context.TradeRepublicTags.FirstOrDefaultAsync(t => t.Id == tagDto.Id);
|
||||
if (existingTag == null)
|
||||
{
|
||||
_logger.LogTrace("Creating missing asset tag object in storage cache context. ID: {TagId}", tagDto.Id);
|
||||
existingTag = new TagEntity { Id = tagDto.Id, Name = tagDto.Name, Type = tagDto.Type };
|
||||
await _context.TradeRepublicTags.AddAsync(existingTag);
|
||||
}
|
||||
|
||||
tags.Add(existingTag);
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user