A critical vulnerability was discovered in React Server Components (Next.js). Our systems remain protected but we advise to update packages to newest version. Learn More

Quan Mai
Mar 27, 2018
  9038
(3 votes)

Clean up catalog metadata system

When I read this forum post: http://world.episerver.com/Forum/Developer-forum/EPiServer-Commerce/Thread-Container/2014/10/How-to-clean-commerce-installation-from-metadata/ , I realized we had problems with garbage metaclasses/metafields in our catalog system. When the catalog import/export tool exports the catalog, it will export all the metaclasses existing in the system, even the metaclasses you don’t want to include in the catalog package. And those classes will come along with all of the sites importing that package, become garbage that no one wants and no one cares. Usually, they are harmless. But we once had a problem when our test data package accidentally contains too many unused metaclasses, it makes the import/export process much slower. And because we have hundreds of tests which requires import/export, our server build greatly slowed down, from 10 minutes to more than 20 minutes. We had to strip all the unused metaclasses to make the build fast again. Even when you don’t have that kind of problem, it’s still a good idea to keep your metadata system tidy and clean. That’s why I come up with this quick and dirty code. It’s basically an ASPX, which you can drop in your project, build it then run.

The code behind:

using Mediachase.BusinessFoundation.Data.Sql;
using Mediachase.Commerce.Storage;
using Mediachase.MetaDataPlus;
using Mediachase.MetaDataPlus.Configurator;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;

namespace EPiServer.Commerce.Sample
{
    public partial class CleanMetaData : System.Web.UI.Page
    {
        private List _unused = new List();
        private List _mfList = new List();
        ///  
        /// Handles the Load event of the Page control. 
        ///  
        /// The source of the event. 
        /// The  instance containing the event data. 
        protected void Page_Load(object sender, EventArgs e)
        {
            //Finding unused metaclasses
            var metaClassCollection = Mediachase.MetaDataPlus.Configurator.MetaClass.GetList(MetaDataContext.Instance, true)
                .Cast().Where(c => c.IsUser && c.Parent.Namespace.Equals("Mediachase.Commerce.Catalog.System", StringComparison.InvariantCultureIgnoreCase)
                &&!c.Name.Equals("CatalogNodeEx", StringComparison.InvariantCultureIgnoreCase) 
                && !c.Name.Equals("CatalogEntryEx", StringComparison.InvariantCultureIgnoreCase));
            var unused = metaClassCollection.ToList();
            foreach (var mc in metaClassCollection)
            {
                var mcQuery = SqlHelper.ExecuteReader(ConfigurationManager.ConnectionStrings["EcfSqlConnection"].ConnectionString, CommandType.Text,
            string.Format("Select CatalogEntryId from CatalogEntry WHERE MetaClassId = {0} UNION" +
                           " SELECT CatalogNodeId from CatalogNode WHERE MetaClassId = {0} ", mc.Id));
                if (mcQuery.Read())
                {
                    unused.Remove(mc);
                }
            }

            GridView1.DataSource = unused;
            GridView1.DataBind();

            var unusedMetaFieldIds = new List();
            var mfQuery = SqlHelper.ExecuteReader(ConfigurationManager.ConnectionStrings["EcfSqlConnection"].ConnectionString, CommandType.Text,
            string.Format("Select MetaFieldId from dbo.MetaField WHERE NameSpace like 'Mediachase.Commerce.Catalog%' " +
             " AND MetaFieldId not in (select distinct(metaFieldId) from dbo.MetaClassMetaFieldRelation) "));
                while (mfQuery.Read())
                {
                    unusedMetaFieldIds.Add(mfQuery.GetInt32(0));
                }

            _mfList = new List();
            foreach(var mf in unusedMetaFieldIds)
            {
                _mfList.Add(MetaField.Load(MetaDataContext.Instance, mf));
            }

            GridView2.DataSource = _mfList.Select(c => new {c.Id,  c.Name, c.FriendlyName, c.Namespace, c.DataType});
            GridView2.DataBind();
        }


        protected void Button1_Click(object sender, EventArgs e)
        {
            //Delete unused metaclasses
            foreach(var mc in _unused)
            {
                MetaClass.Delete(MetaDataContext.Instance, mc.Id);
            }

            //Delete unused metafields
            foreach(var mf in _mfList)
            {
                MetaField.Delete(MetaDataContext.Instance, mf.Id);
            }

            //Clear cache & refresh
            MetaHelper.ClearMetaClassCache();
            Response.Redirect(Request.RawUrl);
        }
    }
}

The markup, save this will generate the designer.cs file:

<%@ Page Language="C#" AutoEventWireup="true" Inherits="EPiServer.Commerce.Sample.CleanMetaData" CodeBehind="~/CleanMetaData.aspx.cs" %>
                                                                                                
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"> 
    <head runat="server"> 
        <title>Clean catalog metadata system</title> 
    </head> 
    <body> 
        <form id="aspnetForm" runat="server"> 
            <div class="epi-contentContainer epi-padding"> 
                <div class="epi-contentArea"> 
                    <h1 class="EP-prefix">Clean catalog metadata system</h1> 
                </div>
            </div>
            <p> 
                <asp:Label ID="Label3" runat="server">Unused metaclasses:</asp:Label> 
            </p> 
            <asp:GridView ID="GridView1" runat="server"> 
            </asp:GridView> 
            <br />
                <asp:Label ID="Label4" runat="server">Unused metafields:</asp:Label> 
            <br />
            <asp:GridView ID="GridView2" runat="server">
            </asp:GridView>
            <br />
                <asp:Button ID="Button1" runat="server" OnClick="Button1_Click" Text="Clean" /> 
        </form> 
    </body> 
</html>

And this is how it looks like:

Click clean and now you got rid of those unused metaclasses/metafields.

Please note that it’s highly recommended to backup your data first – as simple as just export a catalog.

Let’s go cleaning!

Mar 27, 2018

Comments

Petter Klang
Petter Klang Oct 10, 2014 04:03 PM

Sweet!
Thanks for sharing.

ChiChing Lam
ChiChing Lam Oct 29, 2020 09:40 AM

Hi Quan,

The image url that you uploaded in this post could not been found. Would you like to reupload it?

I am wondering where can I find this feature If I have added it to my project. 

Please login to comment.
Latest blogs
Looking back at Optimizely in 2025

Explore Optimizely's architectural shift in 2025, which removed coordination cost through a unified execution loop. Learn how agentic Opal AI and...

Andy Blyth | Dec 17, 2025 |

Cleaning Up Content Graph Webhooks in PaaS CMS: Scheduled Job

The Problem Bit of a niche issue, but we are building a headless solution where the presentation layer is hosted on Netlify, when in a regular...

Minesh Shah (Netcel) | Dec 17, 2025

A day in the life of an Optimizely OMVP - OptiGraphExtensions v2.0: Enhanced Search Control with Language Support and Synonym Slots

Supercharge your Optimizely Graph search experience with powerful new features for multilingual sites and fine-grained search tuning. As search...

Graham Carr | Dec 16, 2025

A day in the life of an Optimizely OMVP - Optimizely Opal: Specialized Agents, Workflows, and Tools Explained

The AI landscape in digital experience platforms has shifted dramatically. At Opticon 2025, Optimizely unveiled the next evolution of Optimizely Op...

Graham Carr | Dec 16, 2025

Optimizely CMS - Learning by Doing: EP09 - Create Hero, Breadcrumb's and Integrate SEO : Demo

  Episode 9  is Live!! The latest installment of my  Learning by Doing: Build Series  on  Optimizely Episode 9 CMS 12  is now available on YouTube!...

Ratish | Dec 15, 2025 |

Building simple Opal tools for product search and content creation

Optimizely Opal tools make it easy for AI agents to call your APIs – in this post we’ll build a small ASP.NET host that exposes two of them: one fo...

Pär Wissmark | Dec 13, 2025 |