ignore for now: authorProperties [PK/FK contributor id, AUDIBLE-PK author id] notes in Contributor.cs for later refactoring c# enum only, not their own tables: roles [AIPK id, name]. seeded: author, narrator, publisher. could expand (eg: translator, editor) without each needing a new table origins [AIPK id, name]. seeded: library. detail. json. series -- begin SCHEMA --------------------------------------------------------------------------------------------------------------------- any audible keys should be indexed SCHEMA ====== contributors [AIPK id, name]. people and publishers books [AIPK id, AUDIBLE-PK product id, title, desc, lengthInMinutes, picture id, 3 ratings, category id, origin id] - product instances. each edition and version is discrete: unique and disconnected from different editions of the same book - on book re-import update: update book origin and series origin with the new source type overwrite simple fields invoke complex contributor updates details page gets un/abridged release date language publisher series info incl name categories if new == series: ignore. do update series info. do not update book info else if old == json: update (incl if new == json) else if old == library && new == detail: update else: ignore book contributors [FK book id, FK contributor id, FK role id, order -- cluster PK across all FKs] supplements [AIPK id, FK book id, download url] categories [AIPK id, AUDIBLE-PK category id, name, parent]. may only nest 1 deep user defined [PK/FK book id, 3 ratings, tagsRaw] series [AIPK id, AUDIBLE-PK series id/asin, name, origin id] series books [FK series id, FK book id, index -- cluster PK across all FKs] - "index" not "order". display this number; don't just put in this sequence - index is float instead of int to allow for in-between books. eg 2.5 - to show 2 editions as the same book in a series, give them the same index - re-import using series page, there will need to be a re-eval of import logic library [PK/FK book id, date added, bookdownloadlink] -- end SCHEMA --------------------------------------------------------------------------------------------------------------------- -- begin SIMPLIFIED DDD --------------------------------------------------------------------------------------------------------------------- combine domain and persistence (C(r)UD). no repository pattern. encapsulated in domain objects; direct calls to EF Core https://www.thereformedprogrammer.net/creating-domain-driven-design-entity-classes-with-entity-framework-core/ // pattern for x-to-many public void AddReview(int numStars, DbContext context = null) { if (_reviews != null) _reviews.Add(new Review(numStars)); else if (context == null) throw new Exception("need context"); else if (context.Entry(this).IsKeySet) context.Add(new Review(numStars, BookId)); else throw new Exception("Could not add"); } // pattern for optional one-to-one MyPropClass MyProps { get; private set; } public void AddMyProps(string s, int i, DbContext context = null) { // avoid a trip to the db if (MyProps != null) { MyProps.Update(s, i); return; } if (BookId == 0) { MyProps = new MyPropClass(s, i); return; } if (context == null) throw new Exception("need context"); // per Jon P Smith, this single trip to db loads the property if there is one // note: .Reference() is for single object references. for collections use .Collection() context.Entry(this).Reference(s => s.MyProps).Load(); if (MyProps != null) MyProps.Update(s, i); else MyProps = new MyPropClass(s, i); } repository reads are 'query object'-like extension methods https://www.thereformedprogrammer.net/is-the-repository-pattern-useful-with-entity-framework-core/#1-query-objects-a-way-to-isolate-and-hide-database-read-code -- and SIMPLIFIED DDD ---------------------------------------------------------------------------------------------------------------------