You can project collection property as a child table.

Property MyList as list of Object(STORAGEDEFAULT="array");

Note that it changes class storage, so existing data should be moved to the correct new place.

Several months ago I wrote a small utility class to accomplish exactly that.

Or if you don't have a lot of different classes and data dependencies, you can just export to XML, add STORAGEDEFAULT and import XML.

Is it possible to change underlying data model so that 1 row = 1 main object?

Class UserPrefs {

Property Username;

///  Color/Number are keys, and this can be indexed
Property Preferences As Array Of %String(SQLPROJECTION = "table/column");

Parameter ROWLEVELSECURITY = 1;

///Updated for each row when the property 'Preferences' changes
Property %READERLIST As %String [ SqlComputeCode = {set {*} = ##class(Users).%SecurityPolicy()}, SqlComputed, SqlComputeOnChange = Preferences ];

}

You can use SQL to insert into child table. Not sure if it would actually trigger a %READERLIST recalculation (maybe try %%INSERT) but worth a try.

Using SQLComputeCode and SQLComputeOnChange %READERLIST property is stored but gets recalculated on every SQL and Object INSERT/UPDATE/%Save:

Class Utils.RLS Extends %Persistent
{

Parameter ROWLEVELSECURITY = 1;

Property %READERLIST As %String [ SqlComputeCode = {set {*} = ##class(Utils.RLS).GetAccess({%%ID})}, SqlComputed, SqlComputeOnChange = (%%INSERT, %%UPDATE) ];

Property data As %String;

ClassMethod GetAccess(Id) As %String
{
    return:Id>3 "_SYSTEM"
    return "%All"
}

/// do ##class(Utils.RLS).Fill()
ClassMethod Fill(N = 5)
{
    do ..%KillExtent()
    for i=1:1:N {
        &sql(insert into Utils.RLS(data) values(:i))
    }

    zw ^demo.testD,^demo.testI

    do ##class(%SQL.Statement).%ExecDirect(,"select * from "_$classname()).%Display()
}