Optimize tag persistence

This commit is contained in:
Robert McRackan 2019-11-27 15:36:34 -05:00
parent 5c81441f83
commit 0683e5f55b
5 changed files with 53 additions and 48 deletions

View File

@ -123,14 +123,8 @@ namespace DataLayer
ArgumentValidator.EnsureEnumerableNotNullOrEmpty(newContributors, nameof(newContributors)); ArgumentValidator.EnsureEnumerableNotNullOrEmpty(newContributors, nameof(newContributors));
// the edge cases of doing local-loaded vs remote-only got weird. just load it // the edge cases of doing local-loaded vs remote-only got weird. just load it
if (_contributorsLink == null) if (_contributorsLink is null)
{ getEntry(context).Collection(s => s.ContributorsLink).Load();
ArgumentValidator.EnsureNotNull(context, nameof(context));
if (!context.Entry(this).IsKeySet)
throw new InvalidOperationException("Could not add contributors");
context.Entry(this).Collection(s => s.ContributorsLink).Load();
}
var roleContributions = getContributions(role); var roleContributions = getContributions(role);
var isIdentical = roleContributions.Select(c => c.Contributor).SequenceEqual(newContributors); var isIdentical = roleContributions.Select(c => c.Contributor).SequenceEqual(newContributors);
@ -140,6 +134,7 @@ namespace DataLayer
_contributorsLink.RemoveWhere(bc => bc.Role == role); _contributorsLink.RemoveWhere(bc => bc.Role == role);
addNewContributors(newContributors, role); addNewContributors(newContributors, role);
} }
private void addNewContributors(IEnumerable<Contributor> newContributors, Role role) private void addNewContributors(IEnumerable<Contributor> newContributors, Role role)
{ {
byte order = 0; byte order = 0;
@ -155,6 +150,18 @@ namespace DataLayer
.ToList(); .ToList();
#endregion #endregion
private Microsoft.EntityFrameworkCore.ChangeTracking.EntityEntry<Book> getEntry(DbContext context)
{
ArgumentValidator.EnsureNotNull(context, nameof(context));
var entry = context.Entry(this);
if (!entry.IsKeySet)
throw new InvalidOperationException("Could not load a valid Book from database");
return entry;
}
#region series #region series
private HashSet<SeriesBook> _seriesLink; private HashSet<SeriesBook> _seriesLink;
public IEnumerable<SeriesBook> SeriesLink => _seriesLink?.ToList(); public IEnumerable<SeriesBook> SeriesLink => _seriesLink?.ToList();
@ -186,14 +193,8 @@ namespace DataLayer
// our add() is conditional upon what's already included in the collection. // our add() is conditional upon what's already included in the collection.
// therefore if not loaded, a trip is required. might as well just load it // therefore if not loaded, a trip is required. might as well just load it
if (_seriesLink == null) if (_seriesLink is null)
{ getEntry(context).Collection(s => s.SeriesLink).Load();
ArgumentValidator.EnsureNotNull(context, nameof(context));
if (!context.Entry(this).IsKeySet)
throw new InvalidOperationException("Could not add series");
context.Entry(this).Collection(s => s.SeriesLink).Load();
}
var singleSeriesBook = _seriesLink.SingleOrDefault(sb => sb.Series == series); var singleSeriesBook = _seriesLink.SingleOrDefault(sb => sb.Series == series);
if (singleSeriesBook == null) if (singleSeriesBook == null)
@ -211,8 +212,7 @@ namespace DataLayer
public void AddSupplementDownloadUrl(string url) public void AddSupplementDownloadUrl(string url)
{ {
// supplements are owned by Book, so no need to Load(): // supplements are owned by Book, so no need to Load():
// OwnsMany: "Can only ever appear on navigation properties of other entity types. // Are automatically loaded, and can only be tracked by a DbContext alongside their owner.
// Are automatically loaded, and can only be tracked by a DbContext alongside their owner."
ArgumentValidator.EnsureNotNullOrWhiteSpace(url, nameof(url)); ArgumentValidator.EnsureNotNullOrWhiteSpace(url, nameof(url));
@ -233,17 +233,10 @@ namespace DataLayer
public void UpdateCategory(Category category, DbContext context = null) public void UpdateCategory(Category category, DbContext context = null)
{ {
// since category is never null, nullity means it hasn't been loaded. non null means we're correctly loaded. just overwrite // since category is never null, nullity means it hasn't been loaded
if (Category != null) if (Category is null)
{ getEntry(context).Reference(s => s.Category).Load();
Category = category;
return;
}
if (context == null)
throw new Exception("need context");
context.Entry(this).Reference(s => s.Category).Load();
Category = category; Category = category;
} }

View File

@ -26,13 +26,13 @@ namespace DataLayer
private static void persistTags(List<EntityEntry> modifiedEntities) private static void persistTags(List<EntityEntry> modifiedEntities)
{ {
var tagSets = modifiedEntities var tagsCollection = modifiedEntities
.Select(e => e.Entity as UserDefinedItem) .Select(e => e.Entity as UserDefinedItem)
// filter by null but NOT by blank. blank is the valid way to show the absence of tags // filter by null but NOT by blank. blank is the valid way to show the absence of tags
.Where(a => a != null) .Where(a => a != null)
.Select(t => (t.Book.AudibleProductId, t.Tags))
.ToList(); .ToList();
foreach (var t in tagSets) FileManager.TagsPersistence.Save(tagsCollection);
FileManager.TagsPersistence.Save(t.Book.AudibleProductId, t.Tags);
} }
} }
} }

View File

@ -7,15 +7,22 @@ nuget
Microsoft.EntityFrameworkCore.Tools (needed for using Package Manager Console) Microsoft.EntityFrameworkCore.Tools (needed for using Package Manager Console)
Microsoft.EntityFrameworkCore.Sqlite Microsoft.EntityFrameworkCore.Sqlite
MIGRATIONS require standard, not core MIGRATIONS
using standard instead of core. edit 3 things in csproj require core, not standard
1of3: pluralize xml TargetFramework tag to TargetFrameworks this can be a problem b/c standard and framework can only reference standard, not core
2of2: TargetFrameworks from: netstandard2.1 TO USE MIGRATIONS (core and/or standard)
to: netcoreapp3.0;netstandard2.1 add to csproj
3of3: add
<PropertyGroup> <PropertyGroup>
<GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles> <GenerateRuntimeConfigurationFiles>true</GenerateRuntimeConfigurationFiles>
</PropertyGroup> </PropertyGroup>
TO USE MIGRATIONS AS *BOTH* CORE AND STANDARD
edit csproj
pluralize this xml tag
from: TargetFramework
to: TargetFrameworks
inside of TargetFrameworks
from: netstandard2.1
to: netcoreapp3.0;netstandard2.1
run. error run. error
SQLite Error 1: 'no such table: Blogs'. SQLite Error 1: 'no such table: Blogs'.

View File

@ -3,7 +3,10 @@
"LibationContext_sqlserver": "Server=(LocalDb)\\MSSQLLocalDB;Database=DataLayer.LibationContext;Integrated Security=true;", "LibationContext_sqlserver": "Server=(LocalDb)\\MSSQLLocalDB;Database=DataLayer.LibationContext;Integrated Security=true;",
"LibationContext": "Data Source=LibationContext.db;Foreign Keys=False;", "LibationContext": "Data Source=LibationContext.db;Foreign Keys=False;",
"// on windows sqlite paths accept windows and/or unix slashes": "", "// sqlite notes": "",
"// absolute path example": "Data Source=C:/foo/bar/sample.db",
"// relative path example": "Data Source=sample.db",
"// on windows: sqlite paths accept windows and/or unix slashes": "",
"MyTestContext": "Data Source=%DESKTOP%/sample.db" "MyTestContext": "Data Source=%DESKTOP%/sample.db"
} }
} }

View File

@ -27,10 +27,12 @@ namespace FileManager
= Policy.Handle<Exception>() = Policy.Handle<Exception>()
.WaitAndRetry(new[] { TimeSpan.FromMilliseconds(100) }); .WaitAndRetry(new[] { TimeSpan.FromMilliseconds(100) });
public static void Save(string productId, string tags) public static void Save(IEnumerable<(string productId, string tags)> tagsCollection)
{ {
ensureCache(); ensureCache();
// 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; cache[productId] = tags;
lock (locker) lock (locker)