When and how should I use UMaterialInstance in C++?

Reading about the difference between UMaterialInterface, UMaterial, and UMaterialInstance it sounds like UMaterialInstance is the ideal choice if I just want to re-use an existing material with potentially different parameters. However, if I use the following architecture in my C++ class:
Header:

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/Actor.h"
...
#include "SpellBullet.generated.h"

UCLASS(ClassGroup = "Combat", Blueprintable, BlueprintType, meta = (DisplayName = "SpellBullet", BlueprintSpawnableComponent))
class RYDDELMYST_API ASpellBullet : public AActor
{
	GENERATED_BODY()
	
public:	
	// Sets default values for this actor properties
	ASpellBullet();

...

public:
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Collision)
	UHitBoxerComponent* HitBoxer;
	// Projectile particle FX
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Projectile)
	UNiagaraSystem* BulletParticles;
	// Projectile material
	UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Projectile)
	UMaterialInterface* BulletMaterial;
	...
};

SpellBullet.cpp:

ASpellBullet::ASpellBullet()
{
 	...
	// material setup if defined in editor
	if (BulletMaterial)
	{
		BulletMesh->SetMaterial(0, BulletMaterial);
	}
}

and the subclass BlasterBolt.cpp:

ABlasterBolt::ABlasterBolt()
{
 	...

	// default material setup if not defined in editor
	if (!BulletMaterial)
	{
		static ConstructorHelpers::FObjectFinder<UMaterial> Material(TEXT("'/Game/Ryddelmyst_Assets/Materials/M_GlowyPurpleLava.M_GlowyPurpleLava'"));
		if (Material.Succeeded())
		{
			BulletMaterial = UMaterialInstanceDynamic::Create(Material.Object, BulletMesh);
			BulletMesh->SetMaterial(0, BulletMaterial);
		}
		else
		{
			UE_LOG(LogTemp, Error, TEXT("BlasterBolt ctor; failed to load default glowy purple lava material asset"));
		}
	}
}

but then I get the following error trying to save a Blueprint derivative of that class:
Can't save ...NewBlueprint.uasset: Graph is linked to external private object MaterialInstanceEditorOnlyData /Script/Ryddelmyst.Default__BlasterBolt:BlasterBoltMeshComponent.MaterialInstanceDynamic_0.MaterialInstanceDynamic_0EditorOnlyData (EditorOnlyData)

The default material reads in the editor as MaterialInstanceDynamic_0, which certainly doesn’t seem correct. If I override that with a material selected from content browser the error on save goes away. How should I use/create UMaterialInstanceDynamic* in C++ so that I can have a default material that is overridable in Blueprints via the editor?

I’m still not sure quite what the error is about, but following the docs at Instanced Materials | Unreal Engine 4.27 Documentation I was able to work around the problem. I think the editor was confused to find a UMaterialInstanceDynamic being created in my class constructor since everything worked fine when I moved the code I had to BeginPlay(), though I’m not sure why this is a problem and the docs even show creating UMaterialInstanceDynamic from a Construction Script node in BP. Anyway, since I don’t even need to modify params on my material of interest, I created a Material Instance in the Content Browser (a.k.a. a UMaterialInstanceConstant) and changed the implementation in the constructor to:

ABlasterBolt::ABlasterBolt()
{
...
        // material setup
	static ConstructorHelpers::FObjectFinder<UMaterialInstanceConstant> MaterialInstance(TEXT("'/Game/Ryddelmyst_Assets/Materials/MI_GlowyPurpleLava.MI_GlowyPurpleLava'"));
	if (MaterialInstance.Succeeded())
	{
		BulletMaterial = MaterialInstance.Object;
		BulletMesh->SetMaterial(0, BulletMaterial);
	}
	else
	{
		UE_LOG(LogTemp, Error, TEXT("BlasterBolt ctor; failed to load default glowy purple lava material asset"));
	}
...
}