Improved importing

This commit is contained in:
Robert McRackan 2019-11-25 13:45:29 -05:00
parent c8e759c067
commit a2e30df51f
11 changed files with 79 additions and 47 deletions

View File

@ -9,7 +9,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace DataLayer.Migrations namespace DataLayer.Migrations
{ {
[DbContext(typeof(LibationContext))] [DbContext(typeof(LibationContext))]
[Migration("20191119144803_Fresh")] [Migration("20191125182309_Fresh")]
partial class Fresh partial class Fresh
{ {
protected override void BuildTargetModel(ModelBuilder modelBuilder) protected override void BuildTargetModel(ModelBuilder modelBuilder)
@ -129,6 +129,13 @@ namespace DataLayer.Migrations
b.HasIndex("Name"); b.HasIndex("Name");
b.ToTable("Contributors"); b.ToTable("Contributors");
b.HasData(
new
{
ContributorId = -1,
Name = ""
});
}); });
modelBuilder.Entity("DataLayer.LibraryBook", b => modelBuilder.Entity("DataLayer.LibraryBook", b =>

View File

@ -200,6 +200,11 @@ namespace DataLayer.Migrations
columns: new[] { "CategoryId", "AudibleCategoryId", "Name", "ParentCategoryCategoryId" }, columns: new[] { "CategoryId", "AudibleCategoryId", "Name", "ParentCategoryCategoryId" },
values: new object[] { -1, "", "", null }); values: new object[] { -1, "", "", null });
migrationBuilder.InsertData(
table: "Contributors",
columns: new[] { "ContributorId", "AudibleContributorId", "Name" },
values: new object[] { -1, null, "" });
migrationBuilder.CreateIndex( migrationBuilder.CreateIndex(
name: "IX_BookContributor_BookId", name: "IX_BookContributor_BookId",
table: "BookContributor", table: "BookContributor",

View File

@ -127,6 +127,13 @@ namespace DataLayer.Migrations
b.HasIndex("Name"); b.HasIndex("Name");
b.ToTable("Contributors"); b.ToTable("Contributors");
b.HasData(
new
{
ContributorId = -1,
Name = ""
});
}); });
modelBuilder.Entity("DataLayer.LibraryBook", b => modelBuilder.Entity("DataLayer.LibraryBook", b =>

View File

@ -62,7 +62,8 @@ namespace DataLayer
string description, string description,
int lengthInMinutes, int lengthInMinutes,
IEnumerable<Contributor> authors, IEnumerable<Contributor> authors,
IEnumerable<Contributor> narrators) IEnumerable<Contributor> narrators,
Category category)
{ {
// validate // validate
ArgumentValidator.EnsureNotNull(audibleProductId, nameof(audibleProductId)); ArgumentValidator.EnsureNotNull(audibleProductId, nameof(audibleProductId));
@ -80,8 +81,7 @@ namespace DataLayer
_seriesLink = new HashSet<SeriesBook>(); _seriesLink = new HashSet<SeriesBook>();
_supplements = new HashSet<Supplement>(); _supplements = new HashSet<Supplement>();
// since category/id is never null, nullity means it hasn't been loaded Category = category;
CategoryId = Category.GetEmpty().CategoryId;
// simple assigns // simple assigns
Title = title; Title = title;
@ -233,8 +233,8 @@ 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 // since category is never null, nullity means it hasn't been loaded. non null means we're correctly loaded. just overwrite
if (Category != null || CategoryId == Category.GetEmpty().CategoryId) if (Category != null)
{ {
Category = category; Category = category;
return; return;

View File

@ -18,8 +18,7 @@ namespace DataLayer
public class Category public class Category
{ {
// Empty is a special case. use private ctor w/o validation // Empty is a special case. use private ctor w/o validation
public static Category GetEmpty() => new Category { CategoryId = -1, AudibleCategoryId = "", Name = "", ParentCategory = null }; public static Category GetEmpty() => new Category { CategoryId = -1, AudibleCategoryId = "", Name = "" };
public bool IsEmpty() => string.IsNullOrWhiteSpace(AudibleCategoryId) || string.IsNullOrWhiteSpace(Name) || ParentCategory == null;
internal int CategoryId { get; private set; } internal int CategoryId { get; private set; }
public string AudibleCategoryId { get; private set; } public string AudibleCategoryId { get; private set; }

View File

@ -6,6 +6,9 @@ namespace DataLayer
{ {
public class Contributor public class Contributor
{ {
// Empty is a special case. use private ctor w/o validation
public static Contributor GetEmpty() => new Contributor { ContributorId = -1, Name = "" };
// contributors search links are just name with url-encoding. space can be + or %20 // contributors search links are just name with url-encoding. space can be + or %20
// author search link: /search?searchAuthor=Robert+Bevan // author search link: /search?searchAuthor=Robert+Bevan
// narrator search link: /search?searchNarrator=Robert+Bevan // narrator search link: /search?searchNarrator=Robert+Bevan

View File

@ -60,6 +60,9 @@ namespace DataLayer
modelBuilder modelBuilder
.Entity<Category>() .Entity<Category>()
.HasData(Category.GetEmpty()); .HasData(Category.GetEmpty());
modelBuilder
.Entity<Contributor>()
.HasData(Contributor.GetEmpty());
// views are now supported via "query types" (instead of "entity types"): https://docs.microsoft.com/en-us/ef/core/modeling/query-types // views are now supported via "query types" (instead of "entity types"): https://docs.microsoft.com/en-us/ef/core/modeling/query-types
} }

View File

@ -63,28 +63,39 @@ namespace DtoImporterService
private static Book createNewBook(Item item, LibationContext context) private static Book createNewBook(Item item, LibationContext context)
{ {
// absence of authors is very rare, but possible
if (!item.Authors?.Any() ?? true)
item.Authors = new[] { new Person { Name = "", Asin = null } };
// nested logic is required so order of names is retained. else, contributors may appear in the order they were inserted into the db // nested logic is required so order of names is retained. else, contributors may appear in the order they were inserted into the db
var authors = item var authors = item
.Authors .Authors
.Select(a => context.Contributors.Local.Single(c => a.Name == c.Name)) .Select(a => context.Contributors.Local.Single(c => a.Name == c.Name))
.ToList(); .ToList();
var narrators
= item.Narrators is null || !item.Narrators.Any()
// if no narrators listed, author is the narrator // if no narrators listed, author is the narrator
if (item.Narrators is null || !item.Narrators.Any()) ? authors
item.Narrators = item.Authors;
// nested logic is required so order of names is retained. else, contributors may appear in the order they were inserted into the db // nested logic is required so order of names is retained. else, contributors may appear in the order they were inserted into the db
var narrators = item : item
.Narrators .Narrators
.Select(n => context.Contributors.Local.Single(c => n.Name == c.Name)) .Select(n => context.Contributors.Local.Single(c => n.Name == c.Name))
.ToList(); .ToList();
// categories are laid out for a breadcrumb. category is 1st, subcategory is 2nd
// absence of categories is very rare, but possible
var lastCategory = item.Categories.LastOrDefault()?.CategoryId ?? "";
var category = context.Categories.Local.SingleOrDefault(c => c.AudibleCategoryId == lastCategory);
var book = context.Books.Add(new Book( var book = context.Books.Add(new Book(
new AudibleProductId(item.ProductId), new AudibleProductId(item.ProductId),
item.Title, item.Title,
item.Description, item.Description,
item.LengthInMinutes, item.LengthInMinutes,
authors, authors,
narrators) narrators,
category)
).Entity; ).Entity;
var publisherName = item.Publisher; var publisherName = item.Publisher;
@ -94,11 +105,6 @@ namespace DtoImporterService
book.ReplacePublisher(publisher); book.ReplacePublisher(publisher);
} }
// categories are laid out for a breadcrumb. category is 1st, subcategory is 2nd
var category = context.Categories.Local.SingleOrDefault(c => c.AudibleCategoryId == item.Categories.LastOrDefault().CategoryId);
if (category != null)
book.UpdateCategory(category, context);
book.UpdateBookDetails(item.IsAbridged, item.DatePublished); book.UpdateBookDetails(item.IsAbridged, item.DatePublished);
if (!string.IsNullOrWhiteSpace(item.SupplementUrl)) if (!string.IsNullOrWhiteSpace(item.SupplementUrl))

View File

@ -33,8 +33,11 @@ namespace DtoImporterService
.Except(localIds) .Except(localIds)
.ToList(); .ToList();
// load existing => local
// remember to include default/empty/missing
var emptyName = Contributor.GetEmpty().Name;
if (remainingCategoryIds.Any()) if (remainingCategoryIds.Any())
context.Categories.Where(c => remainingCategoryIds.Contains(c.AudibleCategoryId)).ToList(); context.Categories.Where(c => remainingCategoryIds.Contains(c.AudibleCategoryId) || c.Name == emptyName).ToList();
} }
// only use after loading contributors => local // only use after loading contributors => local

View File

@ -47,11 +47,10 @@ namespace DtoImporterService
.ToList(); .ToList();
// load existing => local // load existing => local
// remember to include default/empty/missing
var emptyName = Contributor.GetEmpty().Name;
if (remainingContribNames.Any()) if (remainingContribNames.Any())
context.Contributors.Where(c => remainingContribNames.Contains(c.Name)).ToList(); context.Contributors.Where(c => remainingContribNames.Contains(c.Name) || c.Name == emptyName).ToList();
// _________________________________^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// i tried to extract this pattern, but this part prohibits doing so
// wouldn't work anyway for Books.GetBooks()
} }
// only use after loading contributors => local // only use after loading contributors => local

View File

@ -29,12 +29,12 @@ namespace InternalUtilities
{ {
var exceptions = new List<Exception>(); var exceptions = new List<Exception>();
// a book having no authors is rare but allowed
if (items.Any(i => string.IsNullOrWhiteSpace(i.ProductId))) if (items.Any(i => string.IsNullOrWhiteSpace(i.ProductId)))
exceptions.Add(new ArgumentException($"Collection contains item(s) with blank {nameof(Item.ProductId)}", nameof(items))); exceptions.Add(new ArgumentException($"Collection contains item(s) with blank {nameof(Item.ProductId)}", nameof(items)));
if (items.Any(i => string.IsNullOrWhiteSpace(i.Title))) if (items.Any(i => string.IsNullOrWhiteSpace(i.Title)))
exceptions.Add(new ArgumentException($"Collection contains item(s) with blank {nameof(Item.Title)}", nameof(items))); exceptions.Add(new ArgumentException($"Collection contains item(s) with blank {nameof(Item.Title)}", nameof(items)));
if (items.Any(i => i.Authors is null))
exceptions.Add(new ArgumentException($"Collection contains item(s) with null {nameof(Item.Authors)}", nameof(items)));
return exceptions; return exceptions;
} }