Back to "Adding Entity Framework for Blog Posts (Pt. 4)"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

ASP.NET Entity Framework

Adding Entity Framework for Blog Posts (Pt. 4)

Saturday, 17 August 2024

Vedete le parti 1 e 2 e 3 per le fasi precedenti.

Introduzione

Nelle parti precedenti abbiamo trattato di come creare la base di dati, come sono strutturati i nostri controller e i nostri punti di vistaM SK1 e come funzionano i nostri servizi. In questa parte ci occupiamo di dettagli su come piantare la base con alcuni dati iniziali e come funzionino i servizi basati sull'EF .

Come di solito potete vedere la fonte di tutto questo sul mio GitHub. Qui., nella cartella Mostlylucid/Blog folderM SK2

La seminazione della base di dati

Nella parte precedente abbiamo parlato di come Inizialisare e creare i servizi.. In questa parte ci occuporemo di come seminare la base di dati con alcuni dati iniziali. EFBlogPopulator class. Questa classe è registrata come servizio nel SetupBlog metodo di estensione.

    public async Task Populate()
    {
        var posts = await _markdownBlogService.GetPages();
        var languages = _markdownBlogService.LanguageList();

        var languageEntities = await EnsureLanguages(languages);
        await EnsureCategoriesAndPosts(posts, languageEntities);

        await Context.SaveChangesAsync();
    }

Potete vederlo nel Populate Il metodo che chiamiamo il _markdownBlogService.GetPages() Questo va attraverso i nostri file makrdown e popula un sacco di file. BlogViewModels che contiene tutti i posti. Poi facciamo lo stesso per le lingue; questo guarda alla nostra translated archivio per tutti i file di marcazione tradotti che abbiamo generato usando EasyNMT (see Qui. per come facciamo quella parte).

Aggiungere le lingue

Poi chiamiamo il nostro EnsureLanguages metodo che assicura che tutte le lingue siano nella base di dati. Questo è un semplice metodo che controlla se la lingua esiste e se non la aggiunge alla base di datos.

  private async Task<List<LanguageEntity>> EnsureLanguages(Dictionary<string, List<string>> languages)
    {
        var languageList = languages.SelectMany(x => x.Value).ToList();
        var currentLanguages = await Context.Languages.Select(x => x.Name).ToListAsync();

        var languageEntities = new List<LanguageEntity>();
        var enLang = new LanguageEntity { Name =MarkdownBaseService.EnglishLanguage };

        if (!currentLanguages.Contains(MarkdownBaseService.EnglishLanguage)) Context.Languages.Add(enLang);
        languageEntities.Add(enLang);

        foreach (var language in languageList)
        {
            if (languageEntities.Any(x => x.Name == language)) continue;

            var langItem = new LanguageEntity { Name = language };

            if (!currentLanguages.Contains(language)) Context.Languages.Add(langItem);

            languageEntities.Add(langItem);
        }

        await Context.SaveChangesAsync(); // Save the languages first so we can reference them in the blog posts
        return languageEntities;
    }

Voi' vedrete che questo è ppretty semplice e ci assicura solo che tutte le lingue che abbiamo preso dai post di scarico siano nella base di dati; e come abbiamo specificato che gli ID sono generati automaticamente dobbiamo SaveChanges per assicurare che gli id siano generati.

Aggiungere le categorie e i posti

Poi chiamiamo il nostro EnsureCategoriesAndPosts metodo che assicura che tutte le categorie e i posti siano nella base di dati. Questo è un po' più complesso, perché dobbiamo assicurarci che le categorie siano nella database e poi dobbiamo assicurare che i posti sono nella database.

    private async Task EnsureCategoriesAndPosts(
        IEnumerable<BlogPostViewModel> posts,
        List<LanguageEntity> languageEntities)
    {
        var languages = languageEntities.ToDictionary(x => x.Name, x => x);
        var currentPosts = await PostsQuery().ToListAsync();
        foreach (var post in posts)
        {
            var existingCategories = Context.Categories.Local.ToList();
            var currentPost =
                currentPosts.FirstOrDefault(x => x.Slug == post.Slug && x.LanguageEntity.Name == post.Language);
            await AddCategoriesToContext(post.Categories, existingCategories);
            existingCategories = Context.Categories.Local.ToList();
            await AddBlogPostToContext(post, languages[post.Language], existingCategories, currentPost);
        }
    }

Qui usiamo il Contexto.Categori.Locale per tracciare le categorie che sono state aggiustate al Contextu M SK2le loro ' sono state conservate nella Database durante la SaveAsync chiamare). Potete vedere che noi chiediamo al PostsQuery il metodo della nostra classe base che è un semplice metodo che restituisce una domanda di BlogPostEntity Quindi possiamo fare una ricerca nella base di dati per i post.

  protected IQueryable<BlogPostEntity> PostsQuery()=>Context.BlogPosts.Include(x => x.Categories)
        .Include(x => x.LanguageEntity);
   

Aggiungere le categorie

Poi chiamiamo l'autorità. AddCategoriesToContext metodo che assicura che tutte le categorie siano nella base di dati. Questo è un semplice metodo che controlla se la categoria esiste e se non la aggiunge alla base di datos.

    private async Task AddCategoriesToContext(
        IEnumerable<string> categoryList,
        List<CategoryEntity> existingCategories)
    {
        foreach (var category in categoryList)
        {
            if (existingCategories.Any(x => x.Name == category)) continue;

            var cat = new CategoryEntity { Name = category };

             await Context.Categories.AddAsync(cat);
        }
    }

Ancora una volta questo controlla se la categoria esiste e se non la aggiunge alla base di dati.

Aggiungere i post del blog

Poi chiamiamo l'autorità. AddBlogPostToContext metodo, questo poi chiama al EFBaseService per salvare il post alla base di dati.

    private async Task AddBlogPostToContext(
        BlogPostViewModel post,
        LanguageEntity postLanguageEntity,
        List<CategoryEntity> categories,
        BlogPostEntity? currentPost)
    {
        await SavePost(post, currentPost, categories, new List<LanguageEntity> { postLanguageEntity });
    }

Lo facciamo chiamando la SavePost method which is a method that saves the post to the database. Questo metodo è un po' complesso perché deve controllare se il post ha cambiato e se lo ha aggiornato nella base di dati.


   public async Task<BlogPostEntity?> SavePost(BlogPostViewModel post, BlogPostEntity? currentPost =null ,
        List<CategoryEntity>? categories = null,
        List<LanguageEntity>? languages = null)
    {
        if (languages == null)
            languages = await Context.Languages.ToListAsync();

    var postLanguageEntity = languages.FirstOrDefault(x => x.Name == post.Language);
        if (postLanguageEntity == null)
        {
            Logger.LogError("Language {Language} not found", post.Language);
            return null;
        }
        categories ??= await Context.Categories.Where(x => post.Categories.Contains(x.Name)).ToListAsync();
         currentPost ??= await PostsQuery().Where(x=>x.Slug == post.Slug).FirstOrDefaultAsync();
        try
        {
            var hash = post.HtmlContent.ContentHash();
            var currentCategoryNames = currentPost?.Categories.Select(x => x.Name).ToArray() ?? Array.Empty<string>();
            var categoriesChanged = false;
            if (!currentCategoryNames.All(post.Categories.Contains) ||
                !post.Categories.All(currentCategoryNames.Contains))
            {
                categoriesChanged = true;
                Logger.LogInformation("Categories have changed for post {Post}", post.Slug);
            }

            var dateChanged = currentPost?.PublishedDate.UtcDateTime.Date != post.PublishedDate.ToUniversalTime().Date;
            var titleChanged = currentPost?.Title != post.Title;
            if (!titleChanged && !dateChanged && hash == currentPost?.ContentHash && !categoriesChanged)
            {
                Logger.LogInformation("Post {Post} has not changed", post.Slug);
                return currentPost;
            }

            
            var blogPost = currentPost ?? new BlogPostEntity();
            
            blogPost.Title = post.Title;
            blogPost.Slug = post.Slug;
            blogPost.OriginalMarkdown = post.OriginalMarkdown;
            blogPost.HtmlContent = post.HtmlContent;
            blogPost.PlainTextContent = post.PlainTextContent;
            blogPost.ContentHash = hash;
            blogPost.PublishedDate = post.PublishedDate;
            blogPost.LanguageEntity = postLanguageEntity;
            blogPost.Categories = categories.Where(x => post.Categories.Contains(x.Name)).ToList();

            if (currentPost != null)
            {
                Logger.LogInformation("Updating post {Post}", post.Slug);
                Context.BlogPosts.Update(blogPost); // Update the existing post
            }
            else
            {
                Logger.LogInformation("Adding new post {Post}", post.Slug);
                Context.BlogPosts.Add(blogPost); // Add a new post
            }
            return blogPost;
        }
        catch (Exception e)
        {
            Logger.LogError(e, "Error adding post {Post}", post.Slug);
        }

        return null;
    }

Come potete vedere questo ha un MIglio di rilevamento dei cambiamenti per assicurarci che non'non riM SK1add posts that haven't't changedMSC3 Controlliamo l'hash del contenutoMST4 le categorieMst5 la data e il titoloM st6 Se uno di questi è cambiato noi aggiornamo il post nella base di datiMSt7

Una cosa da notare è quanto sia fastidioso controllare un DateTimeOffset è; dobbiamo convertirlo a UTC e poi ottenere la data per confrontarlo. Questo perché il DateTimeOffset Ha un componente temporale e vogliamo confrontare solo la data.

var dateChanged = currentPost?.PublishedDate.UtcDateTime.Date != post.PublishedDate.ToUniversalTime().Date;

Conclusione

Ora abbiamo un sistema di blog completamente funzionante che può essere riempito da file di marcamento e tradotto file di markdown. Nella parte successiva ci porremo sul semplice servizio che usiamo per mostrare i post immagazzinati nella database.

logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.