Tags no longer saved outside of database
This commit is contained in:
parent
7cf4c63d79
commit
3982537d46
@ -10,7 +10,8 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Dinah.EntityFrameworkCore" Version="4.1.1.1" />
|
<PackageReference Include="Dinah.Core" Version="4.4.1.1" />
|
||||||
|
<PackageReference Include="Dinah.EntityFrameworkCore" Version="5.0.0.1" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.7">
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="6.0.7">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
@ -29,10 +30,6 @@
|
|||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
<DebugType>embedded</DebugType>
|
<DebugType>embedded</DebugType>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\LibationFileManager\LibationFileManager.csproj" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="migrate.json">
|
<None Update="migrate.json">
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Dinah.Core;
|
using Dinah.Core;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
|
|
||||||
namespace DataLayer
|
namespace DataLayer
|
||||||
{
|
{
|
||||||
|
|||||||
@ -30,10 +30,6 @@ namespace DataLayer
|
|||||||
{
|
{
|
||||||
ArgumentValidator.EnsureNotNull(book, nameof(book));
|
ArgumentValidator.EnsureNotNull(book, nameof(book));
|
||||||
Book = book;
|
Book = book;
|
||||||
|
|
||||||
// import previously saved tags
|
|
||||||
ArgumentValidator.EnsureNotNullOrWhiteSpace(book.AudibleProductId, nameof(book.AudibleProductId));
|
|
||||||
Tags = LibationFileManager.TagsPersistence.GetTags(book.AudibleProductId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Tags
|
#region Tags
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
using DataLayer.Configurations;
|
using DataLayer.Configurations;
|
||||||
using Dinah.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
namespace DataLayer
|
namespace DataLayer
|
||||||
{
|
{
|
||||||
public class LibationContext : InterceptableDbContext
|
public class LibationContext : DbContext
|
||||||
{
|
{
|
||||||
// IMPORTANT: USING DbSet<>
|
// IMPORTANT: USING DbSet<>
|
||||||
// ========================
|
// ========================
|
||||||
@ -35,14 +34,6 @@ namespace DataLayer
|
|||||||
// see DesignTimeDbContextFactoryBase for info about ctors and connection strings/OnConfiguring()
|
// see DesignTimeDbContextFactoryBase for info about ctors and connection strings/OnConfiguring()
|
||||||
internal LibationContext(DbContextOptions options) : base(options) { }
|
internal LibationContext(DbContextOptions options) : base(options) { }
|
||||||
|
|
||||||
// called on each instantiation
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
|
||||||
{
|
|
||||||
AddInterceptor(new TagPersistenceInterceptor());
|
|
||||||
|
|
||||||
base.OnConfiguring(optionsBuilder);
|
|
||||||
}
|
|
||||||
|
|
||||||
// typically only called once per execution; NOT once per instantiation
|
// typically only called once per execution; NOT once per instantiation
|
||||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,30 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Dinah.Core.Collections.Generic;
|
|
||||||
using Dinah.EntityFrameworkCore;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
|
|
||||||
namespace DataLayer
|
|
||||||
{
|
|
||||||
internal class TagPersistenceInterceptor : IDbInterceptor
|
|
||||||
{
|
|
||||||
public void Executed(DbContext context) { }
|
|
||||||
|
|
||||||
public void Executing(DbContext context)
|
|
||||||
{
|
|
||||||
var tagsCollection
|
|
||||||
= context
|
|
||||||
.ChangeTracker
|
|
||||||
.Entries()
|
|
||||||
.Where(e => e.State.In(EntityState.Modified, EntityState.Added))
|
|
||||||
.Select(e => e.Entity as UserDefinedItem)
|
|
||||||
.Where(udi => udi is not null)
|
|
||||||
// do NOT filter out entires with blank tags. blank is the valid way to show the absence of tags
|
|
||||||
.Select(t => (t.Book.AudibleProductId, t.Tags))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
LibationFileManager.TagsPersistence.Save(tagsCollection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,70 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.IO;
|
|
||||||
using System.Linq;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
using Polly;
|
|
||||||
using Polly.Retry;
|
|
||||||
|
|
||||||
namespace LibationFileManager
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Tags must also be stored in db for search performance. Stored in json file to survive a db reset.
|
|
||||||
/// json is only read when a product is first loaded into the db
|
|
||||||
/// json is only written to when tags are edited
|
|
||||||
/// json access is infrequent and one-off
|
|
||||||
/// </summary>
|
|
||||||
public static class TagsPersistence
|
|
||||||
{
|
|
||||||
private static string TagsFile => Path.Combine(Configuration.Instance.LibationFiles, "BookTags.json");
|
|
||||||
|
|
||||||
private static object locker { get; } = new object();
|
|
||||||
|
|
||||||
// if failed, retry only 1 time after a wait of 100 ms
|
|
||||||
// 1st save attempt sometimes fails with
|
|
||||||
// The requested operation cannot be performed on a file with a user-mapped section open.
|
|
||||||
private static RetryPolicy policy { get; }
|
|
||||||
= Policy.Handle<Exception>()
|
|
||||||
.WaitAndRetry(new[] { TimeSpan.FromMilliseconds(100) });
|
|
||||||
|
|
||||||
public static void Save(IEnumerable<(string productId, string tags)> tagsCollection)
|
|
||||||
{
|
|
||||||
ensureCache();
|
|
||||||
|
|
||||||
if (!tagsCollection.Any())
|
|
||||||
return;
|
|
||||||
|
|
||||||
// on initial reload, there's a huge benefit to adding to cache individually then updating the file only once
|
|
||||||
foreach ((string productId, string tags) in tagsCollection)
|
|
||||||
cache[productId] = tags;
|
|
||||||
|
|
||||||
lock (locker)
|
|
||||||
policy.Execute(() => File.WriteAllText(TagsFile, JsonConvert.SerializeObject(cache, Formatting.Indented)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Dictionary<string, string> cache;
|
|
||||||
|
|
||||||
public static string GetTags(string productId)
|
|
||||||
{
|
|
||||||
ensureCache();
|
|
||||||
|
|
||||||
cache.TryGetValue(productId, out string value);
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ensureCache()
|
|
||||||
{
|
|
||||||
if (cache is not null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
lock (locker)
|
|
||||||
{
|
|
||||||
if (File.Exists(TagsFile))
|
|
||||||
cache = JsonConvert.DeserializeObject<Dictionary<string, string>>(File.ReadAllText(TagsFile));
|
|
||||||
|
|
||||||
// if file doesn't exist. or if file is corrupt and deserialize returns null
|
|
||||||
cache ??= new Dictionary<string, string>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\DataLayer\DataLayer.csproj" />
|
<ProjectReference Include="..\DataLayer\DataLayer.csproj" />
|
||||||
|
<ProjectReference Include="..\LibationFileManager\LibationFileManager.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user