Merge branch 'rmcrackan:master' into master

This commit is contained in:
Mbucari 2022-07-31 21:59:46 -06:00 committed by GitHub
commit 073787173d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 4 additions and 120 deletions

View File

@ -10,7 +10,8 @@
</PropertyGroup>
<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">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@ -29,10 +30,6 @@
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DebugType>embedded</DebugType>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\LibationFileManager\LibationFileManager.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="migrate.json">

View File

@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Linq;
using Dinah.Core;
using Microsoft.EntityFrameworkCore;
namespace DataLayer
{

View File

@ -30,10 +30,6 @@ namespace DataLayer
{
ArgumentValidator.EnsureNotNull(book, nameof(book));
Book = book;
// import previously saved tags
ArgumentValidator.EnsureNotNullOrWhiteSpace(book.AudibleProductId, nameof(book.AudibleProductId));
Tags = LibationFileManager.TagsPersistence.GetTags(book.AudibleProductId);
}
#region Tags

View File

@ -1,10 +1,9 @@
using DataLayer.Configurations;
using Dinah.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
namespace DataLayer
{
public class LibationContext : InterceptableDbContext
public class LibationContext : DbContext
{
// IMPORTANT: USING DbSet<>
// ========================
@ -35,14 +34,6 @@ namespace DataLayer
// see DesignTimeDbContextFactoryBase for info about ctors and connection strings/OnConfiguring()
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
protected override void OnModelCreating(ModelBuilder modelBuilder)
{

View File

@ -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);
}
}
}

View File

@ -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>();
}
}
}
}

View File

@ -14,6 +14,7 @@
<ItemGroup>
<ProjectReference Include="..\DataLayer\DataLayer.csproj" />
<ProjectReference Include="..\LibationFileManager\LibationFileManager.csproj" />
</ItemGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">