Datatable populates struct in BP but not in C++

Good morning! (or afternoon/evening)

I’ve been working on creating inventory items with data contained in a FItemDataStruct. Everything is working great but I want to move the data for each item into a data table and populate the item’s struct from the table on construction. I can do it in blueprint extremely easy (everything works perfectly) but I can’t get it to work from C++. After pouring through forum posts on Unreal for days, I need help!

As suggested on the unreal docs, I made a blueprint exposed DataTableRowHandle variable, which lets me, in the designer, assign a table to use and a row. I’m sure further implementation works in BP, but setting those values does nothing for C++ code (maybe a race for data causing the problem?).

My AInventoryItem.h is

#pragma once

#include "Runtime/Engine/Classes/Engine/DataTable.h"
#include "PaperSpriteComponent.h"
#include "GameFramework/Actor.h"
#include "InventoryItem.generated.h"

UENUM(BlueprintType)
enum class EEquipType : uint8
{
 NONE UMETA(DisplayName = "Not Equipment"),
 Head UMETA(DisplayName = "Head"),
 Neck UMETA(DisplayName = "Neck"),
 Shoulders UMETA(DisplayName = "Shoulders"),
 Chest UMETA(DisplayName = "Chest"),
 Wrists UMETA(DisplayName = "Wrists"),
 Hands UMETA(DisplayName = "Hands"),
 Waist UMETA(DisplayName = "Waist"),
 Legs UMETA(DisplayName = "Legs"),
 Feet UMETA(DisplayName = "Feet"),
 Ring1 UMETA(DisplayName = "Ring"),
 Ring2 UMETA(DisplayName = "Ring")
};

USTRUCT(BlueprintType)
struct FItemDataStruct: public FTableRowBase
{
 GENERATED_USTRUCT_BODY()

 UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Item Data")
 FName ItemName = "";

 //Hold the item class for static function calls
 UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Item Data")
 TSubclassOf<class AInventoryItem> ItemClass;

 UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Item Data")
 int32 ItemQuantity = 0;

 //Holds sprite for item
 UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Item Data")
 UPaperSprite* ItemSprite = nullptr;

 //Holds UMG Button icon for item
 UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Item Data")
 UTexture* Icon = nullptr;

 //Defines item type and affects which inventory item is stored in
 UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Item Data")
 bool bIsResource = false;

 //Equipment slot for equippable items
 UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Item Data")
 EEquipType EquipType = EEquipType::NONE;

 //How long the item effect lasts in seconds
 UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Item Effects")
 int32 EffectDuration = 0;

 //Can the item stack
 UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Item Effects")
 bool bIsStackable = true;

 bool operator==(const FItemDataStruct& Other) const
 {
 return ItemClass == Other.ItemClass;
 }

//Assignment doesn't work
// bool operator=(const FItemDataStruct& Other)
// {
// ItemName = Other.ItemName;
// ItemClass = Other.ItemClass;
// ItemQuantity = Other.ItemQuantity;
// ItemSprite = Other.ItemSprite;
// Icon = Other.Icon;
// bIsResource = Other.bIsResource;
// EquipType = Other.EquipType;
// EffectDuration = Other.EffectDuration;
// bIsStackable = Other.bIsStackable;
// 
// return ItemName == Other.ItemName;
// }
};

UCLASS()
class MYTHIC2D_API AInventoryItem : public AActor
{
 GENERATED_BODY()
 
public: 
 // Sets default values for this actor's properties
 AInventoryItem();
 UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "Setup")
 FItemDataStruct ItemData;
 
 // Called when the game starts or when spawned
 virtual void BeginPlay() override;
 
 // Called every frame
 virtual void Tick( float DeltaSeconds ) override;

 //Figure out how to call one function for all children without instantiation
 //Universal function for all item children

 UFUNCTION(BlueprintCallable, Category = "Use Item")
 static void UseItem();

 UFUNCTION(BlueprintCallable, Category = "Item Information")
 bool GetIsStackable();

 UFUNCTION(BlueprintCallable, Category = "Item Information")
 int32 GetEffectDuration();

 UFUNCTION(BlueprintCallable, Category = "Item Information")
 int32 SetItemQuantity(int32 NewAmount);

 //Create a function to set ItemData from data table
 //Called in constructor

protected:
 UDataTable* DataTable;

 UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Setup")
 FDataTableRowHandle DataTableHandle;

 UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "Setup")
 FName TableRowName = "INV_Watermelon_BP";
};

The .cpp is

#include "Mythic2D.h"
#include "InventoryItem.h"
// Sets default values
AInventoryItem::AInventoryItem()
{
  // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
 PrimaryActorTick.bCanEverTick = true;

 ConstructorHelpers::FObjectFinder<UDataTable> ItemDataTable(TEXT("DataTable'/Game/Blueprints/Databases/ItemDataTable.ItemDataTable'")); // A reference to my Blueprint data table.
 const FString ContextString = TEXT("Inventory Item");
 DataTableHandle.RowName = "INV_Watermelon_BP";

 if (ItemDataTable.Succeeded())
 {
 DataTableHandle.DataTable = ItemDataTable.Object;
 }
 else if (!ItemDataTable.Succeeded())
 {
 UE_LOG(LogTemp, Warning, TEXT("Data NOT Found in C++!")); <--Always happens
 }

 FItemDataStruct* TempData = DataTableHandle.GetRow<FItemDataStruct>(ContextString);
 //ItemData = *DataTableHandle.GetRow<FItemDataStruct>(ContextString); <-- This crashes engine

 if (DataTableHandle.GetRow<FItemDataStruct>(ContextString))
 {
 UE_LOG(LogTemp, Warning, TEXT("Data Found! Class is %s, Quantity is %i"), *TempData->ItemClass->GetName(), TempData->ItemQuantity);
 
//Data is not assigned. Reason unknown
 ItemData.ItemName = TempData->ItemName;
 ItemData.ItemClass = TempData->ItemClass;
 ItemData.ItemQuantity = TempData->ItemQuantity;
 ItemData.ItemSprite = TempData->ItemSprite;
 ItemData.Icon = TempData->Icon;
 ItemData.bIsResource = TempData->bIsResource;
 ItemData.EquipType = TempData->EquipType;
 ItemData.EffectDuration = TempData->EffectDuration;
 ItemData.bIsStackable = TempData->bIsStackable;
 }
 else if (!DataTableHandle.GetRow<FItemDataStruct>(ContextString))
 {
 UE_LOG(LogTemp, Warning, TEXT("Data NOT Found!"));
 }
}

// Called when the game starts or when spawned
void AInventoryItem::BeginPlay()
{
 Super::BeginPlay();
 
}

// Called every frame
void AInventoryItem::Tick( float DeltaTime )
{
 Super::Tick( DeltaTime );
}

void AInventoryItem::UseItem()
{
    //Create new Object based on desired item
    //Do a unique function
    //Handle object destruction when the item effect ends
}

bool AInventoryItem::GetIsStackable()
{
 return ItemData.bIsStackable;
}

int32 AInventoryItem::GetEffectDuration()
{
 return ItemData.EffectDuration;
}

int32 AInventoryItem::SetItemQuantity(int32 NewAmount)
{
 ItemData.ItemQuantity = NewAmount;
 return ItemData.ItemQuantity;
}

I need it in C++ over BP since I can’t create new Object in BP (to my knowledge) to call a virtual void UseItem() override; without spawning the item in the world. If all else fails, would it be a valid course to create a function that makes the new Object and returns it to the UseItem function that is overridden in BP? That would let me inherit the database call from BP and still create a new Object and call a unique UseItem, right?