CreateMetaField is not adding entry to mcmd_MetaField table

Vote:
 

We're having an issue where the Business Foundation API for creating a new MetaField on a class is not working properly.

This is the code that I'm executing:

using (var myEditScope = DataContext.Current.MetaModel.BeginEdit())
{
	metaClass.CreateMetaField(name, friendlyName, typeName, isNullable, defaultValue ?? string.Empty, attributes);
	myEditScope.SaveChanges();
}

And this does create the column in the MetaClass's own SQL table, but it does not add an entry to the mcmd_MetaField table.

I've experimented with a few different approaches, and have tested it with various MetaFieldTypes (Integer, String, DateTime, etc) but keep getting the same result. I've double checked that I'm adding the correct attributes (I am checking against the MetaFieldBuiler code as a reference).

What really has me scratching my head is that this has always worked on previous projects reliably for me, and even in the early days of this project.. I guess maybe something is uniquely wrong with our state of data perhaps, but I can't figure what could have changed to cause this..?

We're using Commerce v12.11 (with CMS v11.10.6).

Anyone have any ideas/tips?

#199692
Dec 06, 2018 2:53
Vote:
 

Oh, I forgot to say, there doesn’t appear to be anything relevant being logged when this issue occurs.

#199693
Dec 06, 2018 5:02
Vote:
 
using (MetaFieldBuilder builder = new MetaFieldBuilder(mc))
                    {
builder.CreateText("abc", "xyz", true, 100, false);
builder.SaveChanges();
}

probably works for you 

#199714
Dec 07, 2018 11:02
Vote:
 

I have the same issue and the above example did not help me. And the problem does not occur for all fields. I'm trying to add many fields, one (as I understood, the very first) is added both as a field in the table and as an entry in the table [mcmd_MetaField]. The rest are added only as a field in the table.

That's how I add these fields:

MetaClass metaClass = DataContext.Current.MetaModel.MetaClasses["Address"];

if (metaClass != null)
{
    metaClass.CreateMetaField("FirstField", "First Field", MetaFieldType.Text, true, "", new AttributeCollection());
    metaClass.CreateMetaField("SecondField", "Second Field", MetaFieldType.CheckboxBoolean, true, "", new AttributeCollection());
    metaClass.CreateMetaField("ThirdField", "Third Field", MetaFieldType.Text, true, "", new AttributeCollection());
...
}

So, the "FirstField" is added to both tables ans other fields are not added to mcmd_MetaField

#200064
Dec 28, 2018 16:21
Vote:
 

I made some changes and after that it worked, the entities were added to the table. But my changes don't seem right to me. I looked at the implementation of the CreateMetaField method from the MetaClass class and transformed my code a bit:

private static void InitializeMetaClass()
{
    MetaClass metaClass = DataContext.Current.MetaModel.MetaClasses["Address"];
    if (metaClass != null)
    {
        using (MetaClassManagerEditScope managerEditScope =
            DataContext.Current.MetaModel.BeginEdit())
        {
            metaClass.CreateField("FirstField", "First Field", MetaFieldType.Text);
            metaClass.CreateField("SecondField", "Second Field", MetaFieldType.Text);

            managerEditScope.SaveChanges();
        }
    }
}
...

// extension for MetaClass instance
public static void CreateField(
    this MetaClass metaClass,
    string fieldName,
    string friendlyName,
    string fieldType,
    bool allowNulls = true,
    string defaultValue = null)
{
    var metaField = new MetaField(fieldName, friendlyName)
    {
        Owner = MetaClassManagerEditScope.Current.Owner,
        AccessLevel = MetaClassManagerEditScope.Current.AccessLevel,
        TypeName = fieldType,
        IsNullable = allowNulls
    };
    metaField.Attributes.AddRange(DataContext.Current.MetaModel.RegisteredTypes[fieldType].Attributes);
    metaField.Attributes.AddRange(new AttributeCollection());
    metaField.DefaultValue = defaultValue ?? string.Empty;
    metaClass.Fields.Add(metaField);
    IMetaFieldInstaller installer = MetaFieldType.GetInstaller(fieldType);
    if (installer != null)
    {
        installer.AssignDataSource(metaField);
        installer.AssignValidators(metaField);
    }
}
#200065
Dec 28, 2018 19:19
Vote:
 

Is there any way that will help me?

#200340
Jan 09, 2019 11:34
Vote:
 

I have this problem too. I noticed that XSValidators (in mcmd_MetaClass) is not updated correctly when I try to add multiple meta fields "at once".

#201724
Edited, Feb 28, 2019 14:22
Vote:
 

Update on this - whenever a meta field is added, an MetaClassManagerEditScope is created. For some reason, it's only the first MetaClassManagerEditScope that works. So, lets say you try to add two meta fields in these ways:

var contactMetaClass = DataContext.Current.GetMetaClass(ContactEntity.ClassName);
contactMetaClass.CreateMetaField("Test1", "Test1", MetaFieldType.LongText, true, string.Empty, new AttributeCollection());
contactMetaClass.CreateMetaField("Test2", "Test2", MetaFieldType.LongText, true, string.Empty, new AttributeCollection());

... or ...

var contactMetaClass = DataContext.Current.GetMetaClass(ContactEntity.ClassName);
using (var builder = new MetaFieldBuilder(contactMetaClass))
{
  builder.CreateLongText("Test1", "Test1", true);
  builder.SaveChanges();
}

using (var builder = new MetaFieldBuilder(contactMetaClass))
{
  builder.CreateLongText("Test2", "Test2", true);
  builder.SaveChanges();
}

Then only Test1 will be added successfully. Test2 will be added as a column in the database table, but won't be added to mcmd_MetaFields.

I think I have spotted a bug here. After I added Test1, I can see that the field is also added to DataContext.Current.MetaModel.MetaClasses["Contact"].Fields, but after I added Test2, that field does not exist in that list. This is problematic, since new meta fields are synchronized to mcmd_MetaField in SqlSerializer.Serialize(MetaClassManager mcm), where DataContext.Current.MetaModel (MetaClassManager) is provided as parameter. It simply does not contain the latter meta field (Test2).

To sum up, it's clearly a bug that the non-first meta fields does not end up in MetaClassManager (DataContext.Current.MetaModel) when adding multiple meta fields using one of the methods described above. However, I think the solution is to add all meta fields within one MetaFieldBuilder using-scope:

using (var builder = new MetaFieldBuilder(contactMetaClass))
{
  builder.CreateLongText("Test1", "Test1", true);
  builder.CreateLongText("Test2", "Test2", true);
  builder.SaveChanges();
}

This will only create exactly one MetaClassManagerEditScope, which in turn will all fields to DataContext.Current.MetaModel, and thereby get synchronized, via SqlSerializer.Serialize(MetaClassManager mcm), to the database table mcmd_MetaField.

#201761
Mar 01, 2019 12:57
Vote:
 

Andreas, have you raise this issue with Episerver and did they get back to you.

I'm having the same problem. I was able to fix my existing fields by manually Serializing the fields and getting them inserted in mcmd_MetaField.

I'll try MetaFieldBuilder for new fields and raise a ticket with Episerver for metaClass.CreateMetaField.....

#203063
Apr 08, 2019 20:08
Vote:
 

@Matt V - No, I never opened a ticket on this issue.

#203085
Apr 09, 2019 10:00
Vote:
 

I have some time to look into the ticket, and I think you should be doing this

using (MetaClassManagerEditScope scope = DataContext.Current.MetaModel.BeginEdit())
{
     MetaClass mc =  ///getting your metaclass
     using (MetaFieldBuilder builder = new MetaFieldBuilder(mc))
				{
//do you stuffs
}
   scope.SaveChanges();
}
#203450
Apr 24, 2019 11:33
Vote:
 

Hi

I am having the same issue as above and my code is as you suggested Quan and I get the same issues.

It creates the column but it doesn't create the meta field. I am adding the field in an ApplicationMigrationStep : MigrationStep

class I have created. I have overridden the AddChanges() method

Has anyone solved this yet?

Thanks

Ali

#204639
Jun 12, 2019 10:34
Vote:
 

Yes, that is how we have always done it on other projects without any issues. Currently using Commerce 12.11.1

#204641
Jun 12, 2019 10:43
Vote:
 

Thank you Andreas, I can now see what I was doing incorrectly. I was adding a field in the same builder. But saving it after I added the first field. Instead of adding all of the fields and then saving it

#204647
Jun 12, 2019 11:58
Vote:
 

Hm, I would've assumed that it would be more correct to use exactly one MetaFieldBuilder and add all fields within that builder instance.

#204652
Jun 12, 2019 13:07
Vote:
 

For what it's worth, I had opened a ticket with EPiServer to explain this issue. It has been closed since. Quan if you want to take a look it's ticket #304091

I ultimately resolved our issues by seperating each metaclass operations. What was not working for me was that I "GetOrCeate" all my metaclasses at the beginning and than reuse all those same objects for all metafield creation.

Here's a couple handler methods that we use. I apologize in advance for the length of this but I thought I'd share my findings since people seem to still have this issue.

        private MetaClass GetOrCreateMetaClass(string metaClassName, string metaClassFriendlyName)
        {
            var metaClass = DataContext.Current.MetaModel.MetaClasses.Cast<MetaClass>().FirstOrDefault(mc => mc.Name == metaClassName);

            if (metaClass == null)
            {
                try
                {
                    using (MetaClassManagerEditScope scope = DataContext.Current.MetaModel.BeginEdit())
                    {
                        metaClass = DataContext.Current.MetaModel.CreateMetaClass(metaClassName, metaClassFriendlyName);

                        scope.SaveChanges();
                    }
                }
                catch (Exception ex)
                {
                    Log.Error(String.Format("MetadataInitialization :: GetOrCreateMetaClass {0}", metaClassName), ex);
                }
            }

            return metaClass;
        }
        private void CreateMetaField(MetaFieldBuilder builder, MetaClass metaClass, string metaFieldName, string metaFieldFriendlyName, string type, bool isNullable = true)
        {
            if (!metaClass.Fields.Contains(metaFieldName))
            {
                switch (type)
                {
                    case MetaFieldType.CheckboxBoolean:
                        builder.CreateCheckBoxBoolean(metaFieldName, metaFieldFriendlyName, isNullable, false, metaFieldFriendlyName);
                        break;
                    case MetaFieldType.Text:
                        builder.CreateText(metaFieldName, metaFieldFriendlyName, isNullable, 255, false);
                        break;
                    case MetaFieldType.Url:
                        builder.CreateUrl(metaFieldName, metaFieldFriendlyName, isNullable, 255, false, "");
                        break;
                    case MetaFieldType.LongText:
                        builder.CreateLongText(metaFieldName, metaFieldFriendlyName, isNullable);
                        break;
                    case MetaFieldType.Currency:
                        builder.CreateCurrency(metaFieldName, metaFieldFriendlyName, isNullable, 0, true);
                        break;
                    case MetaFieldType.DateTime:
                        builder.CreateDateTime(metaFieldName, metaFieldFriendlyName, isNullable, false);
                        break;
                    case MetaFieldType.Date:
                        builder.CreateDate(metaFieldName, metaFieldFriendlyName, isNullable);
                        break;
                }
            }
        }
        private void CreateReferenceMetaField(MetaFieldBuilder builder, MetaClass parentMetaClass, MetaClass metaClass, string metaFieldName, string metaFieldFriendlyName)
        {
            var refMetaFieldName = string.Format(CommonConstants.ReferenceMetaFieldNameFormat, metaFieldName);
            if (metaClass.Fields[refMetaFieldName] == null)
            {
                builder.CreateReference(metaFieldName, metaFieldFriendlyName, true, parentMetaClass.Name, false);
            }
        }

Here's a simplified version of what was not working. We use to get all metaclasses and then reusing them accross the different MetaFieldBuilders...

        private void InitializeOrgContactMetaData()
        {
            var organizationMetaClass = GetOrCreateMetaClass(CommonConstants.Classes.Organization, CommonConstants.Classes.Organization);
            var contactMetaClass = GetOrCreateMetaClass(CommonConstants.Classes.Contact, CommonConstants.Classes.Contact);
            var addressMetaClass = GetOrCreateMetaClass(CommonConstants.Classes.Address, CommonConstants.Classes.Address);
            
            using (var builder = new MetaFieldBuilder(contactMetaClass))
            {
                CreateReferenceMetaField(builder, organizationMetaClass, contactMetaClass, "OrgContactLink", "OrgContactLink");
                CreateMetaField(builder, contactMetaClass, "SomeField", "SomeField", MetaFieldType.CheckboxBoolean);

                builder.SaveChanges();
            }

            using (var builder = new MetaFieldBuilder(organizationMetaClass))
            {
                CreateMetaField(builder, organizationMetaClass, "Field1", "Field1", MetaFieldType.Text);
                CreateMetaField(builder, organizationMetaClass, "Field2", "Field2", MetaFieldType.Text);

                builder.SaveChanges();
            }
        }

And here's what' works for me. It seems that "re-getting" the different MetaClasses for each builder blocks has fixed all our issues. It has been working fine ever since we've been doing it this way.

        private void InitializeContact()
        {
            var organizationMetaClass = GetOrCreateMetaClass(CommonConstants.Classes.Organization, CommonConstants.Classes.Organization);
            var contactMetaClass = GetOrCreateMetaClass(CommonConstants.Classes.Contact, CommonConstants.Classes.Contact);
            var addressMetaClass = GetOrCreateMetaClass(CommonConstants.Classes.Address, CommonConstants.Classes.Address);

            using (var builder = new MetaFieldBuilder(contactMetaClass))
            {
                CreateReferenceMetaField(builder, organizationMetaClass, contactMetaClass, "OrgContactLink", "OrgContactLink");
                CreateMetaField(builder, contactMetaClass, "SomeField", "SomeField", MetaFieldType.CheckboxBoolean);

                builder.SaveChanges();
            }
        }

        private void InitializeOrganization()
        {
            var organizationMetaClass = GetOrCreateMetaClass(CommonConstants.Classes.Organization, CommonConstants.Classes.Organization);

            using (var builder = new MetaFieldBuilder(organizationMetaClass))
            {
                CreateMetaField(builder, organizationMetaClass, "Field1", "Field1", MetaFieldType.Text);
                CreateMetaField(builder, organizationMetaClass, "Field2", "Field2", MetaFieldType.Text);

                builder.SaveChanges();
            }
        }

#204657
Jun 12, 2019 15:49