Spread the love

As part of the journey, for the randomised novel, I got stuck in a small delay. On a randomised novel, I have to think of a ton more to add to the story, then when inspiration hits, I don’t have a quick way to even jot down the idea.

Writing Slump on the 4th

I thought about it a ton, and decided the easiest way for me to be able to jot things down within the right context would be to upgrade my own adaption of NaNoE.V2. For those who don’t know, that’s the novel software that got me through ~108k words for Accidental Distances, which I designed, styled, and created, for myself. My new version, NaNoEAUno, will stay super private for now, but takes it off of local data storage, and puts it on Azure through Uno Platform. Android first, obviously.

The goal is to be able to add to, adjust, and even just make notes, from wherever I am, in my novels. The simple goal was to work out how to put it together since the 4th, spend the weekend getting it to work, and then from there I can continue this November’s goal of a novel of over 50k words! It will also help me to stop making excuses for publishing the one mentioned above.

With that being said, over this weekend, I had a few small challenges. Using the latest libraries, in some cases, doesn’t fit the intended outcome on a project. I can at least spread the method actions to the extent they will work, most of the time, but that was a time sink. I intended on having the words caught up yesterday with the new app.

Today’s Progress Mark

As of the 12th my word count is 2,120 of 50k and need 2,816 words a day to achieve the goal. It doesn’t help I’m part of an invite only Hackathon.

The big pause I needed

Entity Framework Delay

Using Entity Framework since 2008, in a few of my Microsoft internships and the student partner program, I used the styles I grew accustomed to in the past. This, through some stroke of luck, didn’t make it easy, this time. For future it will be, again, as I know what the issue is to look out for.

The DbContext couldn’t retrieve inserted rows through the DbSet link to the table. Simply put, in a daft example:

// Where novelItemID is the newly inserted NovelItem using .Add(...)
var novelItem = ADB.NovelItems.Where(it => it.NovelItemID == novelItemID).FirstOrDefault();

The novelItem would be null, despite the SQL database showing the row. As it turns out, it was easiest to extend my “creation logic” just a little bit. Instead of .Save(…) I created the SQL-ised logic for inserts and updates, then in this method using the rawQuery it can retrieve the row needed. In its own context, it’s simple to understand:

private NovelItem CreateNovelItem(NovelItemControlType controlType)
{
    // the item it is before
    int itemBefore = -1;
    // the item it is after
    int itemAfter = -2;
    // => I am "before" -2, the end, and "after" -1, the start
    if (LoadedNovel.CurrentNovelItem is not null)
    {
        itemBefore = LoadedNovel.CurrentNovelItem.NovelItemID;
        itemAfter = LoadedNovel.CurrentNovelItem.NovelItemIDThatsAfter;
    }
    var newNovItem = new NovelItem
    {
        Data = CurrentParagraphText,
        ControlType = (int)controlType,
        NovelID = LoadedNovel.Loaded is not null ? LoadedNovel.Loaded.NovelID : throw new Exception(),
        NovelItemIDThatsAfter = itemAfter,
        NovelItemIDThatsBefore = itemBefore
    };
    newNovItem.Insert();

    var before = ADB.NovelItems.Where(it => it.NovelItemID == newNovItem.NovelItemIDThatsBefore).FirstOrDefault();
    if (before is not null) before.NovelItemIDThatsAfter = newNovItem.NovelItemID;
    var after = ADB.NovelItems.Where(it => it.NovelItemID == newNovItem.NovelItemIDThatsAfter).FirstOrDefault();
    if (after is not null) after.NovelItemIDThatsBefore = newNovItem.NovelItemID;
    ADB.Save();

    using var rawQuery = ADB.AzureDb?.Database.GetDbConnection().CreateCommand();
    rawQuery!.CommandText = "SELECT * FROM NovelItem WHERE NovelItemID = @NovelItemID";
    rawQuery.Parameters.Add(new SqlParameter("@NovelItemID", newNovItem.NovelItemID));
    rawQuery?.Connection?.Open();
    var result2 = rawQuery!.ExecuteReader();
    result2.Read();
    var loadedModel = new NovelItem()
    {
        NovelItemID = result2.GetInt32(0),
        NovelID = result2.GetInt32(1),
        ControlType = result2.GetInt32(2),
        Data = result2.GetString(3),
        NovelItemIDThatsBefore = result2.GetInt32(4),
        NovelItemIDThatsAfter = result2.GetInt32(5),
        Deleted = result2.GetBoolean(6)
    };
    rawQuery?.Connection?.Close();
    LoadedNovel.CurrentNovelItem = loadedModel;

    PreviousParagraphText = CurrentParagraphText;
    CurrentParagraphText = "";
    UpdatePreviousItemType();

    return newNovItem;
}

So, parameterised queries were a simple option. Note, the functions, names, and the likes I’ll likely adjust and revamp for cleaner code. This messy code is just to share.

using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
using NaNoEAUno.Services.Azure;

namespace NaNoEAUno.Models;

public enum NovelItemControlType
{
    Paragraph = 0,
    Chapter = 1,
    Note = 2,
    Bookmark = 3
}

public class NovelItem : Model
{
    public int NovelItemID { get; set; }
    public int NovelID { get; set; }
    public int ControlType { get; set; }
    public string Data { get; set; } = string.Empty;
    public int NovelItemIDThatsBefore { get; set; } = -1; // -1 => start
    public int NovelItemIDThatsAfter { get; set; } = -2; // -2 => end
    public bool Deleted { get; set; } = false;

    public override string InsertSQL()
    {
        return "INSERT INTO NovelItem (NovelID, ControlType, Data, NovelItemIDThatsBefore, NovelItemIDThatsAfter, Deleted) " +
               "VALUES (@NovelID, @ControlType, @Data, @NovelItemIDThatsBefore, @NovelItemIDThatsAfter, @Deleted)";
    }

    public override void Insert()
    {
        using var command = ADB.AzureDb?.Database.GetDbConnection().CreateCommand();
        if (command is null) throw new Exception();

        command.CommandText = InsertSQL();
        command.Parameters.Add(new SqlParameter("@NovelID", NovelID));
        command.Parameters.Add(new SqlParameter("@ControlType", ControlType));
        command.Parameters.Add(new SqlParameter("@Data", Data));
        command.Parameters.Add(new SqlParameter("@NovelItemIDThatsBefore", NovelItemIDThatsBefore));
        command.Parameters.Add(new SqlParameter("@NovelItemIDThatsAfter", NovelItemIDThatsAfter));
        command.Parameters.Add(new SqlParameter("@Deleted", Deleted));

        command.Connection?.Open();
        command.ExecuteNonQuery();
        command.Connection?.Close();
        ADB.Save();

        NovelItemID = (int)(ADB.NovelItems
            .OrderByDescending(it => it.NovelItemID)
            .FirstOrDefault()?.NovelItemID ?? throw new Exception());
    }

    public override string UpdateSQL()
    {
        return "UPDATE NovelItem SET NovelID = @NovelID, ControlType = @ControlType, Data = @Data, NovelItemIDThatsBefore = @NovelItemIDThatsBefore, NovelItemIDThatsAfter = @NovelItemIDThatsAfter, Deleted = @Deleted WHERE NovelItemID = @NovelItemID";
    }

    public override void Update()
    {
        // ...
    }
}

Bugfind: Previous Pages Troubles

Open Dropdown of MainPage in HelpersPage

As you can see above, it turns out that the previous views are still interactive when you navigate to a specific view model. I have yet to find a way to automate it, but it turned out the easiest semi-solution is to make a static reference and function. Both for elements being set to IsActive, and for updating lists.

public partial class MainViewModel : ObservableObject
{
    // ...
    [ObservableProperty]
    bool actionsEnabled;
    // ...
    public MainViewModel(
        IStringLocalizer localizer,
        IOptions<AppConfig> appInfo,
        INavigator navigator)
    {
        // ...
        _instance = this;
        // ...
        ActionsEnabled = true;
    }
    // ...
    private async Task GoToNovelView()
    {
        if (SelectedNovel is null) return;

        ActionsEnabled = false;
        LoadedNovel.Loaded = SelectedNovel;
        await _navigator.NavigateViewModelAsync<NovelWritingViewModel>(this);
    }

    private static MainViewModel? _instance;
    internal static void RefreshNovelsList()
    {
        if (_instance is null) throw new Exception("MainViewModel _instance is null...");
        var loadNovels = ADB.Novels
            .Where(it => it.Deleted == false)
            .OrderBy(it => it.Name)
            .ToList();
        _instance.Novels.Clear();
        _instance.Novels.AddRange(loadNovels);
        _instance.SelectedNovel = _instance.Novels.FirstOrDefault();
        _instance.ActionsEnabled = true;
    }
}

I dislike the need for dirty code to do these things, I most likely would need to read documentation, but I prefer to experience things from scratch without the need of documentation. That’s the only way, with my disability, I can learn – new methods from scratch, not reading text documentation.

On the other side, some models only work using the DbContext.Add(…) method; so, it’s an awkward 50/50 so far. Just wanted to get it to work. I swear I might be missing something in the latest EF implementation. I’m just not sure what I might have missed in changes, over time.