Reset a facet on an xConnect contact

In 8.2 it was easy to reset a facet in code using the .Reset() extension on the facet. But in 9.x they removed this making it difficult to clear a facet. Lets fix that.

Reset a facet on an xConnect contact

This seams really easy, there is noooo need for a blog post Chris. Just new up a new facet, stick it on the contact, sync that mofo and get back to day drinking!

var contactReference = this.contactIdentificationRepository.GetContactReference();

using (var client = this.contactIdentificationRepository.CreateContext())
{
    var contact = client.Get(contactReference, new ContactExpandOptions(MembershipFacet.DefaultFacetKey));

    var membershipFacet = contact.GetFacet<MembershipFacet>(MembershipFacet.DefaultFacetKey);

    // Let's replace the existing one
    membershipFacet = new MembershipFacet();
        
    client.SetFacet(contact, MembershipFacet.DefaultFacetKey, membershipFacet);
    client.Submit();
    contactFacetsProvider.UpdateTracker();
}

No sir! Request for a fly-by denied!

What you end up with is the error "Facet already exists on the contact". No it doesn't, this is a brand new facet. Made it myself.

What is happening is that Sitecore already at some point put that facet on the contact and it keeps track of it. It is selfish and wants to ensure its data integrity. Can't denied that is probably a good thing. xConnect is using a ConcurrencyToken, that you can only see if you are debugging a facet, to keep track of what facet is on what contact. It won't let you replace the existing facet, but you can update it as much as you want. No issues there. I can just set each property to its default value.

But what if my facet is big and I want to clear it out completely and start over? Or if I hate the code bloat related to reseting each property of my facet. Well today is your lucky day.

In Sitecore 8.x, on a facet there was a .Reset() facet extension that would clear the facet for you. Unfortunately this was removed for 9.1. But with the extension below, you can take a facet, copy the ConcurrencyToken, new up a new facet and stick the ConcurrencyToken back on it without xConnect missing a lick.

public static T GetFacetWithDefaultValues<T>(this T facet) where T : Facet, new()
{
    var concurrencyToken = facet.ConcurrencyToken;
    var result = new T();
    var type = result.GetType();
    Condition.Ensures(type, type.Name).IsNotNull();
    var property = type.GetProperty("ConcurrencyToken");
    Condition.Ensures(property, property.Name).IsNotNull();
    property.SetValue(result, concurrencyToken);
    return result;
}

Its use is like this. You just replace you existing facet with the facet that comes back from the extension code. This is a brand new clean facet, but with the concurrency token set to the proper value.

var contactReference = this.contactIdentificationRepository.GetContactReference();

using (var client = this.contactIdentificationRepository.CreateContext())
{
    var contact = client.Get(contactReference, new ContactExpandOptions(MembershipFacet.DefaultFacetKey));

    var membershipFacet = contact.GetFacet<MembershipFacet>(MembershipFacet.DefaultFacetKey) ?? new MembershipFacet();
    membershipFacet = membershipFacet.GetFacetWithDefaultValues();

    client.SetFacet(contact, MembershipFacet.DefaultFacetKey, membershipFacet);
    client.Submit();
    contactFacetsProvider.UpdateTracker();
}

Bonus if you have an existing facet already populated, you can pass that in and use that.

public static T GetFacetWithDefaultValues<T>(this T facet, T newFacet) where T : Facet
{
    var concurrencyToken = facet.ConcurrencyToken;
    var result = newFacet;
    var type = result.GetType();
    Condition.Ensures(type, type.Name).IsNotNull();
    var property = type.GetProperty("ConcurrencyToken");
    Condition.Ensures(property, property.Name).IsNotNull();
    property.SetValue(result, concurrencyToken);
    return result;
}

Now you can go back to day drinkin' Cheers.