public void ParseWithXElementMangaTest() { XDocument doc = XDocument.Parse(Helpers.GetResourceText("test_manga_clean.xml")); MalUserLookupResults results = MalAppInfoXml.ParseResults(doc); DoMangaAsserts(results); }
static void Main(string[] args) { // No logging support for MalApi in .NET Core yet. // At this time there is no Common.Logging adapter for NLog that supports .NET core. using (MyAnimeListApi api = new MyAnimeListApi()) { api.UserAgent = "MalApiExample"; api.TimeoutInMs = 15000; MalUserLookupResults userLookup = api.GetAnimeListForUser("LordHighCaptain"); foreach (MyAnimeListEntry listEntry in userLookup.AnimeList) { Console.WriteLine("Rating for {0}: {1}", listEntry.AnimeInfo.Title, listEntry.Score); } Console.WriteLine(); Console.WriteLine(); RecentUsersResults recentUsersResults = api.GetRecentOnlineUsers(); foreach (string user in recentUsersResults.RecentUsers) { Console.WriteLine("Recent user: {0}", user); } Console.WriteLine(); Console.WriteLine(); int eurekaSevenID = 237; AnimeDetailsResults eurekaSeven = api.GetAnimeDetails(eurekaSevenID); Console.WriteLine("Eureka Seven genres: {0}", string.Join(", ", eurekaSeven.Genres)); } }
static void Main(string[] args) { // MalApi uses the Common.Logging logging abstraction. // You can hook it up to any logging library that has a Common.Logging adapter. // See App.config for an example of hooking up MalApi to NLog. // Note that you will also need the appropriate NLog and Common.Logging.NLogXX packages installed. // Hooking up logging is not necessary but can be useful. // With the configuration in this example and with this example program, you will see lines like: // Logged from MalApi: Getting anime list for MAL user LordHighCaptain using URI https://myanimelist.net/malappinfo.php?status=all&type=anime&u=LordHighCaptain // Logged from MalApi: Successfully retrieved anime list for user LordHighCaptain using (MyAnimeListApi api = new MyAnimeListApi()) { api.UserAgent = "MalApiExample"; api.TimeoutInMs = 15000; var animeUpdateInfo = new AnimeUpdate() { Episode = 26, Status = AnimeCompletionStatus.Completed, Score = 9, }; string userUpdateAnime = api.UpdateAnimeForUser(1, animeUpdateInfo, "user", "password"); var mangaUpdateInfo = new MangaUpdate() { Chapter = 20, Volume = 3, Score = 8, Status = MangaCompletionStatus.Completed }; string userUpdateManga = api.UpdateMangaForUser(952, mangaUpdateInfo, "user", "password"); MalUserLookupResults userLookup = api.GetAnimeListForUser("user"); foreach (MyAnimeListEntry listEntry in userLookup.AnimeList) { Console.WriteLine("Rating for {0}: {1}", listEntry.AnimeInfo.Title, listEntry.Score); } Console.WriteLine(); Console.WriteLine(); RecentUsersResults recentUsersResults = api.GetRecentOnlineUsers(); foreach (string user in recentUsersResults.RecentUsers) { Console.WriteLine("Recent user: {0}", user); } Console.WriteLine(); Console.WriteLine(); int eurekaSevenID = 237; AnimeDetailsResults eurekaSeven = api.GetAnimeDetails(eurekaSevenID); Console.WriteLine("Eureka Seven genres: {0}", string.Join(", ", eurekaSeven.Genres)); } }
public void ParseWithTextReaderMangaTest() { using (TextReader reader = Helpers.GetResourceStream("test_manga.xml")) { MalUserLookupResults results = MalAppInfoXml.Parse(reader); DoMangaAsserts(results); } }
private void DoAnimeAsserts(MalUserLookupResults results) { Assert.Equal("LordHighCaptain", results.CanonicalUserName); Assert.Equal(158667, results.UserId); Assert.Equal(163, results.AnimeList.Count); MyAnimeListEntry entry = results.AnimeList.Where(anime => anime.AnimeInfo.AnimeId == 853).First(); Assert.Equal("Ouran Koukou Host Club", entry.AnimeInfo.Title); Assert.Equal(MalAnimeType.Tv, entry.AnimeInfo.Type); entry.AnimeInfo.Synonyms.Should().BeEquivalentTo(new List <string>() { "Ohran Koko Host Club", "Ouran Koukou Hosutobu", "Ouran High School Host Club" }); Assert.Equal(7, entry.NumEpisodesWatched); Assert.Equal(7, entry.Score); Assert.Equal(AnimeCompletionStatus.Watching, entry.Status); // Test tags with Equal, not equivalent, because order in tags matters Assert.Equal(new List <string>() { "duck", "goose" }, entry.Tags); entry = results.AnimeList.Where(anime => anime.AnimeInfo.AnimeId == 7311).First(); Assert.Equal("Suzumiya Haruhi no Shoushitsu", entry.AnimeInfo.Title); Assert.Equal(MalAnimeType.Movie, entry.AnimeInfo.Type); entry.AnimeInfo.Synonyms.Should().BeEquivalentTo(new List <string>() { "The Vanishment of Haruhi Suzumiya", "Suzumiya Haruhi no Syoshitsu", "Haruhi movie", "The Disappearance of Haruhi Suzumiya" }); Assert.Equal((decimal?)null, entry.Score); Assert.Equal(0, entry.NumEpisodesWatched); Assert.Equal(AnimeCompletionStatus.PlanToWatch, entry.Status); Assert.Equal(new List <string>(), entry.Tags); entry = results.AnimeList.Where(anime => anime.AnimeInfo.AnimeId == 889).First(); Assert.Equal("Black Lagoon", entry.AnimeInfo.Title); // Make sure synonyms that are the same as the real name get filtered out entry.AnimeInfo.Synonyms.Should().BeEquivalentTo(new List <string>()); entry = results.AnimeList.Where(anime => anime.AnimeInfo.Title == "Test").First(); // Make sure that <series_synonyms/> is the same as <series_synonyms></series_synonyms> entry.AnimeInfo.Synonyms.Should().BeEquivalentTo(new List <string>()); Assert.Equal(new UncertainDate(2010, 2, 6), entry.AnimeInfo.StartDate); Assert.Equal(UncertainDate.Unknown, entry.AnimeInfo.EndDate); Assert.Equal("https://cdn.myanimelist.net/images/anime/9/24646.jpg", entry.AnimeInfo.ImageUrl); Assert.Equal(new UncertainDate(year: null, month: 2, day: null), entry.MyStartDate); Assert.Equal(UncertainDate.Unknown, entry.MyFinishDate); Assert.Equal(new DateTime(year: 2011, month: 4, day: 2, hour: 22, minute: 50, second: 58, kind: DateTimeKind.Utc), entry.MyLastUpdate); Assert.Equal(new List <string>() { "test&test", "< less than", "> greater than", "apos '", "quote \"", "hex ö", "dec !", "control character" }, entry.Tags); }
private void DoMangaAsserts(MalUserLookupResults results) { Assert.Equal("naps250", results.CanonicalUserName); Assert.Equal(5544903, results.UserId); Assert.Equal(6, results.MangaList.Count); MyMangaListEntry entry = results.MangaList.Where(manga => manga.MangaInfo.MangaId == 2).First(); Assert.Equal("Berserk", entry.MangaInfo.Title); Assert.Equal(MalMangaType.Manga, entry.MangaInfo.Type); entry.MangaInfo.Synonyms.Should().BeEquivalentTo(new List <string>() { "Berserk: The Prototype" }); Assert.Equal(352, entry.NumChaptersRead); Assert.Equal(10, entry.Score); Assert.Equal(MangaCompletionStatus.Reading, entry.Status); // Test tags with Equal, not equivalent, because order in tags matters Assert.Equal(new List <string>() { "CLANG", "Miura pls" }, entry.Tags); entry = results.MangaList.Where(manga => manga.MangaInfo.MangaId == 9115).First(); Assert.Equal("Ookami to Koushinryou", entry.MangaInfo.Title); Assert.Equal(MalMangaType.Novel, entry.MangaInfo.Type); entry.MangaInfo.Synonyms.Should().BeEquivalentTo(new List <string>() { "Okami to Koshinryo", "Spice and Wolf", "Spice & Wolf" }); Assert.Equal((decimal?)null, entry.Score); Assert.Equal(0, entry.NumChaptersRead); Assert.Equal(MangaCompletionStatus.Completed, entry.Status); Assert.Equal(new List <string>(), entry.Tags); entry = results.MangaList.Where(manga => manga.MangaInfo.MangaId == 1).First(); Assert.Equal("Monster", entry.MangaInfo.Title); // Make sure synonyms that are the same as the real name get filtered out entry.MangaInfo.Synonyms.Should().BeEquivalentTo(new List <string>()); entry = results.MangaList.Where(manga => manga.MangaInfo.Title == "Test").First(); // Make sure that <series_synonyms/> is the same as <series_synonyms></series_synonyms> entry.MangaInfo.Synonyms.Should().BeEquivalentTo(new List <string>()); Assert.Equal(new UncertainDate(2010, 2, 6), entry.MangaInfo.StartDate); Assert.Equal(UncertainDate.Unknown, entry.MangaInfo.EndDate); Assert.Equal("https://myanimelist.cdn-dena.com/images/manga/2/159423.jpg", entry.MangaInfo.ImageUrl); Assert.Equal(new UncertainDate(year: null, month: 2, day: null), entry.MyStartDate); Assert.Equal(UncertainDate.Unknown, entry.MyFinishDate); Assert.Equal(new DateTime(year: 2011, month: 4, day: 2, hour: 22, minute: 50, second: 58, kind: DateTimeKind.Utc), entry.MyLastUpdate); Assert.Equal(new List <string>() { "test&test", "< less than", "> greater than", "apos '", "quote \"", "hex ö", "dec !", "control character" }, entry.Tags); }
public void GetMangaListForUser() { string username = "******"; using (MyAnimeListApi api = new MyAnimeListApi()) { MalUserLookupResults userLookup = api.GetMangaListForUser(username); Assert.NotEmpty(userLookup.MangaList); } }
public async Task <IActionResult> GetRecs([FromBody] AnimeRecsInputJson input, [FromServices] IOptionsSnapshot <Config.RecommendationsConfig> recConfig, [FromServices] IMyAnimeListApiFactory malApiFactory, [FromServices] IAnimeRecsClientFactory recClientFactory, [FromServices] IAnimeRecsDbConnectionFactory dbConnFactory, [FromServices] IRazorViewEngine viewEngine, [FromServices] ITempDataProvider tempProvider) { if (!ModelState.IsValid) { AjaxError error = new AjaxError(ModelState); _logger.LogDebug("Invalid input received for GetRecs: {0}", error.Message); return(BadRequest(error)); } if (input.RecSourceName == null) { input.RecSourceName = recConfig.Value.DefaultRecSource; } try { MalUserLookupResults userLookup = await GetUserLookupAsync(input, malApiFactory).ConfigureAwait(false); Dictionary <int, MalListEntry> animeList = new Dictionary <int, MalListEntry>(); foreach (MyAnimeListEntry listEntry in userLookup.AnimeList) { animeList[listEntry.AnimeInfo.AnimeId] = new AnimeRecs.RecEngine.MAL.MalListEntry((byte?)listEntry.Score, listEntry.Status, (short)listEntry.NumEpisodesWatched); } Dictionary <int, MalListEntry> animeWithheld = WithholdAnime(input, animeList); MalRecResults <IEnumerable <IRecommendation> > recResults = await GetRecommendationsAsync(input, recConfig.Value, animeList, animeWithheld, recClientFactory).ConfigureAwait(false); GetRecsViewModel viewModel = new GetRecsViewModel( results: recResults, userId: userLookup.UserId, userName: userLookup.CanonicalUserName, userLookup: userLookup, userAnimeList: animeList, maximumRecommendationsToReturn: recConfig.Value.MaximumRecommendationsToReturn, maximumRecommendersToReturn: recConfig.Value.MaximumRecommendersToReturn, animeWithheld: animeWithheld, dbConnectionFactory: dbConnFactory ); RecResultsAsHtmlJson resultsJson = await GetResultHtmlAsync(viewModel, input, viewEngine, tempProvider).ConfigureAwait(false); return(Ok(resultsJson)); } catch (ShortCircuitException ex) { return(ex.Result); } }
public void GetAnimeListForUser() { string username = "******"; using (MyAnimeListApi api = new MyAnimeListApi()) { MalUserLookupResults userLookup = api.GetAnimeListForUser(username); // Just a smoke test that checks that getting an anime list returns something Assert.NotEmpty(userLookup.AnimeList); } }
public GetRecsViewModel(MalRecResults <IEnumerable <IRecommendation> > results, int userId, string userName, MalUserLookupResults userLookup, IDictionary <int, MalListEntry> userAnimeList, int maximumRecommendationsToReturn, int maximumRecommendersToReturn, IDictionary <int, MalListEntry> animeWithheld, IAnimeRecsDbConnectionFactory dbConnectionFactory) { Results = results; UserId = userId; UserName = userName; UserLookup = userLookup; UserAnimeList = userAnimeList; MaximumRecommendationsToReturn = maximumRecommendationsToReturn; MaximumRecommendersToReturn = maximumRecommendersToReturn; DbConnectionFactory = dbConnectionFactory; StreamsByAnime = new Dictionary <int, ICollection <streaming_service_anime_map> >(); AnimeWithheld = animeWithheld; }
static bool UserMeetsCriteria(MalUserLookupResults userLookup, NpgsqlConnection conn, NpgsqlTransaction transaction) { // completed, rated >= X, and user is not in DB int completedRated = userLookup.AnimeList.Count(anime => anime.Score.HasValue && anime.Status == CompletionStatus.Completed); if (completedRated < config.MinimumAnimesCompletedAndRated) { return(false); } Logging.Log.DebugFormat("Really checking if {0} is in the database by user id.", userLookup.CanonicalUserName); bool isInDb = mal_user.UserIsInDb(userLookup.UserId, conn, transaction); Logging.Log.DebugFormat("{0} really in database = {1}", userLookup.CanonicalUserName, isInDb); return(!isInDb); }
private void DoAsserts(MalUserLookupResults results) { Assert.That(results.CanonicalUserName, Is.EqualTo("LordHighCaptain")); Assert.That(results.UserId, Is.EqualTo(158667)); Assert.That(results.AnimeList.Count, Is.EqualTo(163)); MyAnimeListEntry entry = results.AnimeList.Where(anime => anime.AnimeInfo.AnimeId == 853).First(); Assert.That(entry.AnimeInfo.Title, Is.EqualTo("Ouran Koukou Host Club")); Assert.That(entry.AnimeInfo.Type, Is.EqualTo(MalAnimeType.Tv)); Assert.That(entry.AnimeInfo.Synonyms, Is.EquivalentTo(new List<string>() { "Ohran Koko Host Club", "Ouran Koukou Hosutobu", "Ouran High School Host Club" })); Assert.That(entry.NumEpisodesWatched, Is.EqualTo(7)); Assert.That(entry.Score, Is.EqualTo(7)); Assert.That(entry.Status, Is.EqualTo(CompletionStatus.Watching)); Assert.That(entry.Tags, Is.EqualTo(new List<string>() { "duck", "goose" })); entry = results.AnimeList.Where(anime => anime.AnimeInfo.AnimeId == 7311).First(); Assert.That(entry.AnimeInfo.Title, Is.EqualTo("Suzumiya Haruhi no Shoushitsu")); Assert.That(entry.AnimeInfo.Type, Is.EqualTo(MalAnimeType.Movie)); Assert.That(entry.AnimeInfo.Synonyms, Is.EquivalentTo(new List<string>() { "The Vanishment of Haruhi Suzumiya", "Suzumiya Haruhi no Syoshitsu", "Haruhi movie", "The Disappearance of Haruhi Suzumiya" })); Assert.That(entry.Score, Is.EqualTo((decimal?)null)); Assert.That(entry.NumEpisodesWatched, Is.EqualTo(0)); Assert.That(entry.Status, Is.EqualTo(CompletionStatus.PlanToWatch)); Assert.That(entry.Tags, Is.EqualTo(new List<string>())); entry = results.AnimeList.Where(anime => anime.AnimeInfo.AnimeId == 889).First(); Assert.That(entry.AnimeInfo.Title, Is.EqualTo("Black Lagoon")); // Make sure synonyms that are the same as the real name get filtered out Assert.That(entry.AnimeInfo.Synonyms, Is.EquivalentTo(new List<string>())); entry = results.AnimeList.Where(anime => anime.AnimeInfo.Title == "Test").First(); // Make sure that <series_synonyms/> is the same as <series_synonyms></series_synonyms> Assert.That(entry.AnimeInfo.Synonyms, Is.EquivalentTo(new List<string>())); Assert.That(entry.AnimeInfo.StartDate, Is.EqualTo(new UncertainDate(2010, 2, 6))); Assert.That(entry.AnimeInfo.EndDate, Is.EqualTo(UncertainDate.Unknown)); Assert.That(entry.AnimeInfo.ImageUrl, Is.EqualTo("http://cdn.myanimelist.net/images/anime/9/24646.jpg")); Assert.That(entry.MyStartDate, Is.EqualTo(new UncertainDate(year: null, month: 2, day: null))); Assert.That(entry.MyFinishDate, Is.EqualTo(UncertainDate.Unknown)); Assert.That(entry.MyLastUpdate, Is.EqualTo(new DateTime(year: 2011, month: 4, day: 2, hour: 22, minute: 50, second: 58, kind: DateTimeKind.Utc))); Assert.That(entry.Tags, Is.EqualTo(new List<string>() { "test&test", "< less than", "> greater than", "apos '", "quote \"", "hex ö", "dec !", "control character" })); }
static int Main(string[] args) { CommandLineArgs commandLine; try { commandLine = new CommandLineArgs(args); if (commandLine.ShowHelp) { commandLine.DisplayHelp(Console.Out); return((int)ExitCode.Success); } IConfigurationBuilder configBuilder = new ConfigurationBuilder() .AddXmlFile(commandLine.ConfigFile); IConfigurationRoot rawConfig = configBuilder.Build(); config = rawConfig.Get <Config>(); if (config.LoggingConfigPath != null) { Logging.SetUpLogging(config.LoggingConfigPath); } else { Console.Error.WriteLine("No logging configuration file set. Logging to console."); Logging.SetUpConsoleLogging(); } } catch (Exception ex) { Console.Error.WriteLine("Fatal error: {0}", ex, ex.Message); return((int)ExitCode.Failure); } try { Logging.Log.Debug($"Command line args parsed. ConfigFile={commandLine.ConfigFile}"); using (IMyAnimeListApi basicApi = new MyAnimeListApi() { TimeoutInMs = config.MalTimeoutInMs, UserAgent = config.MalApiUserAgentString }) using (IMyAnimeListApi rateLimitingApi = new RateLimitingMyAnimeListApi(basicApi, TimeSpan.FromMilliseconds(config.DelayBetweenRequestsInMs))) using (IMyAnimeListApi malApi = new RetryOnFailureMyAnimeListApi(rateLimitingApi, config.NumMalRequestFailuresBeforeGivingUp, config.DelayAfterMalRequestFailureInMs)) using (NpgsqlConnection conn = new NpgsqlConnection(config.ConnectionStrings.AnimeRecs)) { conn.Open(); int usersAddedSoFar = 0; while (usersAddedSoFar < config.UsersPerRun) { RecentUsersResults recentMalUsers = malApi.GetRecentOnlineUsers(); foreach (string user in recentMalUsers.RecentUsers) { using (var transaction = conn.BeginTransaction(System.Data.IsolationLevel.RepeatableRead)) { if (!UserIsInDatabase(user, conn, transaction)) { MalUserLookupResults userLookup = malApi.GetAnimeListForUser(user); if (UserMeetsCriteria(userLookup, conn, transaction)) { InsertUserAndRatingsInDatabase(userLookup, conn, transaction); usersAddedSoFar++; Logging.Log.Debug("Committing transaction."); transaction.Commit(); Logging.Log.Debug("Transaction committed."); if (usersAddedSoFar == config.UsersPerRun) { break; } } else { Logging.Log.InfoFormat("{0} does not meet criteria for inclusion, skipping", user); } } else { Logging.Log.InfoFormat("{0} is already in the database, skipping.", user); } } } } using (var transaction = conn.BeginTransaction(System.Data.IsolationLevel.RepeatableRead)) { TrimDatabaseToMaxUsers(config.MaxUsersInDatabase, conn, transaction); transaction.Commit(); } } } catch (Exception ex) { Logging.Log.FatalFormat("Fatal error: {0}", ex, ex.Message); return((int)ExitCode.Failure); } return((int)ExitCode.Success); }
static void InsertUserAndRatingsInDatabase(MalUserLookupResults userLookup, NpgsqlConnection conn, NpgsqlTransaction transaction) { Logging.Log.InfoFormat("Inserting anime and list entries for {0} ({1} entries).", userLookup.CanonicalUserName, userLookup.AnimeList.Count); List <mal_anime> animesToUpsert = new List <mal_anime>(); Dictionary <int, List <mal_anime_synonym> > synonymsToUpsert = new Dictionary <int, List <mal_anime_synonym> >(); List <mal_list_entry> entriesToInsert = new List <mal_list_entry>(); List <mal_list_entry_tag> tagsToInsert = new List <mal_list_entry_tag>(); // Buffer animes, anime synonyms, list entries, and tags. // For animes not upserted this session, upsert animes all at once, clear synonyms, insert synonyms // insert user // insert list entries all at once // insert tags all at once foreach (MyAnimeListEntry anime in userLookup.AnimeList) { if (!AnimesUpserted.ContainsKey(anime.AnimeInfo.AnimeId)) { mal_anime animeRow = new mal_anime( _mal_anime_id: anime.AnimeInfo.AnimeId, _title: anime.AnimeInfo.Title, _mal_anime_type_id: (int)anime.AnimeInfo.Type, _num_episodes: anime.AnimeInfo.NumEpisodes, _mal_anime_status_id: (int)anime.AnimeInfo.Status, _start_year: (short?)anime.AnimeInfo.StartDate.Year, _start_month: (short?)anime.AnimeInfo.StartDate.Month, _start_day: (short?)anime.AnimeInfo.StartDate.Day, _end_year: (short?)anime.AnimeInfo.EndDate.Year, _end_month: (short?)anime.AnimeInfo.EndDate.Month, _end_day: (short?)anime.AnimeInfo.EndDate.Day, _image_url: anime.AnimeInfo.ImageUrl, _last_updated: DateTime.UtcNow ); animesToUpsert.Add(animeRow); List <mal_anime_synonym> synonymRowsForThisAnime = new List <mal_anime_synonym>(); foreach (string synonym in anime.AnimeInfo.Synonyms) { mal_anime_synonym synonymRow = new mal_anime_synonym( _mal_anime_id: anime.AnimeInfo.AnimeId, _synonym: synonym ); synonymRowsForThisAnime.Add(synonymRow); } synonymsToUpsert[anime.AnimeInfo.AnimeId] = synonymRowsForThisAnime; } mal_list_entry dbListEntry = new mal_list_entry( _mal_user_id: userLookup.UserId, _mal_anime_id: anime.AnimeInfo.AnimeId, _rating: (short?)anime.Score, _mal_list_entry_status_id: (short)anime.Status, _num_episodes_watched: (short)anime.NumEpisodesWatched, _started_watching_year: (short?)anime.MyStartDate.Year, _started_watching_month: (short?)anime.MyStartDate.Month, _started_watching_day: (short?)anime.MyStartDate.Day, _finished_watching_year: (short?)anime.MyFinishDate.Year, _finished_watching_month: (short?)anime.MyFinishDate.Month, _finished_watching_day: (short?)anime.MyFinishDate.Day, _last_mal_update: anime.MyLastUpdate ); entriesToInsert.Add(dbListEntry); foreach (string tag in anime.Tags) { mal_list_entry_tag dbTag = new mal_list_entry_tag( _mal_user_id: userLookup.UserId, _mal_anime_id: anime.AnimeInfo.AnimeId, _tag: tag ); tagsToInsert.Add(dbTag); } } // For animes not upserted this session, upsert animes, clear synonyms all at once, insert synonyms all at once Logging.Log.DebugFormat("Upserting {0} animes.", animesToUpsert.Count); foreach (mal_anime animeToUpsert in animesToUpsert) { Logging.Log.TraceFormat("Checking if anime \"{0}\" is in the database.", animeToUpsert.title); bool animeIsInDb = mal_anime.IsInDatabase(animeToUpsert.mal_anime_id, conn, transaction); if (!animeIsInDb) { // Not worth optimizing this by batching inserts because once there are a couple hundred users in the database, // inserts will be relatively few in number. Logging.Log.Trace("Not in database. Inserting it."); animeToUpsert.Insert(conn, transaction); Logging.Log.TraceFormat("Inserted anime \"{0}\" in database.", animeToUpsert.title); AnimesUpserted[animeToUpsert.mal_anime_id] = animeToUpsert; } else { Logging.Log.TraceFormat("Already in database. Updating it."); animeToUpsert.Update(conn, transaction); Logging.Log.TraceFormat("Updated anime \"{0}\".", animeToUpsert.title); AnimesUpserted[animeToUpsert.mal_anime_id] = animeToUpsert; } } Logging.Log.DebugFormat("Upserted {0} animes.", animesToUpsert.Count); if (synonymsToUpsert.Count > 0) { List <mal_anime_synonym> flattenedSynonyms = synonymsToUpsert.Values.SelectMany(synonyms => synonyms).ToList(); // clear synonyms for all these animes Logging.Log.DebugFormat("Clearing {0} synonyms for this batch.", flattenedSynonyms.Count); mal_anime_synonym.Delete(synonymsToUpsert.Keys, conn, transaction); Logging.Log.DebugFormat("Cleared {0} synonyms for this batch.", flattenedSynonyms.Count); // insert synonyms for all these animes Logging.Log.DebugFormat("Inserting {0} synonyms for this batch.", flattenedSynonyms.Count); mal_anime_synonym.Insert(flattenedSynonyms, conn, transaction); Logging.Log.DebugFormat("Inserted {0} synonyms for this batch.", flattenedSynonyms.Count); } else { Logging.Log.Debug("No synonyms in this batch."); } // Insert user mal_user user = new mal_user( _mal_user_id: userLookup.UserId, _mal_name: userLookup.CanonicalUserName, _time_added: DateTime.UtcNow ); Logging.Log.DebugFormat("Inserting {0} into DB.", userLookup.CanonicalUserName); user.Insert(conn, transaction); Logging.Log.DebugFormat("Inserted {0} into DB.", userLookup.CanonicalUserName); // insert list entries all at once if (entriesToInsert.Count > 0) { Logging.Log.DebugFormat("Inserting {0} list entries for user \"{1}\".", entriesToInsert.Count, userLookup.CanonicalUserName); mal_list_entry.Insert(entriesToInsert, conn, transaction); Logging.Log.DebugFormat("Inserted {0} list entries for user \"{1}\".", entriesToInsert.Count, userLookup.CanonicalUserName); } // insert tags all at once if (tagsToInsert.Count > 0) { Logging.Log.DebugFormat("Inserting {0} tags by user \"{1}\".", tagsToInsert.Count, userLookup.CanonicalUserName); mal_list_entry_tag.Insert(tagsToInsert, conn, transaction); Logging.Log.DebugFormat("Inserted {0} tags by user \"{1}\".", tagsToInsert.Count, userLookup.CanonicalUserName); } Logging.Log.InfoFormat("Done inserting anime and list entries for {0}.", userLookup.CanonicalUserName); }