I am getting a list of expired users from third party so i have list of Email Id's for which i need to delete the contacts from Tridion Database tables (Sub management, CM_Email , Contacts, extended_details etc )

I will schedule this application so that all the InActive Users (Which I am getting from third party) should be deleted on daily basis from Tridion. I can see Contact.GetContact method in Tridion.Audiencemanger.Api dll but it needs a UserContext or a filter , I didn't find any method to delete by email.

  • Could you describe the use case? I'm curious at how the 3rd party manages the deletion status for Contacts and if existing or custom Audience Manager data could have worked. – Alvin Reyes Dec 5 at 21:58
  • When you say "Email Id's" do you mean their e-mail addresses? – Peter Kjaer Dec 6 at 9:33
  • @PeterKjaer Yes, thats correct – Neelesh Raghuvanshi Dec 6 at 13:46
  • @AlvinReyes We have some forms, when an end user submit a form , data ends up in Custom Db as well as Audience Manager. Custom Db managed by some other Team , and they want to delete some Records from custom Db and Audience Manager but by a schedule service. This schedule service will pick the Email id's from third party or custom Db and will delete the records from everywhere. – Neelesh Raghuvanshi Dec 6 at 13:49
  • Okay, makes sense and thanks for the description. It sounds like an interesting self-service setup. – Alvin Reyes Dec 6 at 21:43

Have you tried this? REMEMBER TO ADD A CONDITION - and that you need to run for each address book)...

ContactFilter filter = new ContactFilter(UserContext.Current);

// Create a copy of the streamed list
List<Contact> contactsToDelete = Contact.GetContacts(UserContext.Current, filter, addressBook).ToList();

// Loop over the new list and delete each Contact
foreach (Contact contact in contactsToDelete)
{
    contact.Delete();
}

Compliments of Peter Kjaer from a quick google - it's an old one but... maybe... .. https://pkjaer.wordpress.com/2011/12/01/looping-through-contacts/

  • 1
    This is indeed a valid approach. You'll have to evaluate if it will be faster to loop through all Contacts in an Address Book and compare it with the list, or loop through the list and look up each Contact by its e-mail address (using the filter). It depends on which list is likely to be bigger -- I imagine the second approach would be faster overall. – Peter Kjaer Dec 7 at 12:03

Keep in mind that if you delete a Contact, that's it. There is no audit trail and the data is lost forever. Doing that automatically based on input from an external source could lead to trouble (i.e. data being lost because of incorrect or unexpected input).

So another option that I would like to propose would be to create a "To be deleted" Address Book (you can name it whatever you like) and to not synchronize it anywhere and not add it to any Publications. Just leave it in the System Administration section.

Then write a small tool that uses the Import functionality of the API to move those Contacts to the new Address Book. Since they will be removed from their original Address Book into one that isn't synchronized anywhere, they will be removed from the website(s) too. And since you won't add it to any Publications, they won't be included in any Mailings (if you use Outbound E-mail).

Once you are confident that those Contacts can indeed be permanently deleted, you can then do so easily as they are all in the "To be deleted" Address Book. You can even do that automatically (see Saunders' answer) -- though again I would try to at least add some audit trail or extensive logging in case you need to recreate the Contact.

The import functionality works with CSV files and is pretty easy to use. I spent a few minutes creating this tool that does exactly what you are asking for, when given a file with the e-mail addresses of the Contacts to move:

using System;
using System.Globalization;
using Tridion.AudienceManagement.API;
using Tridion.AudienceManagement.API.Import;

namespace PseudoDelete
{
    class Program
    {
        // Set this to the Key field of your "To be deleted" Address Book
        private const string AddressBookKey = "TBR";

        private static void Main()
        {
            Console.WriteLine($"Import started by {UserContext.Current.UserName}...");

            UpdateContactsToRemove();

            if (System.Diagnostics.Debugger.IsAttached)
            {
                Console.WriteLine("Press ENTER to exit");
                Console.ReadLine();
            }
        }

        private static void UpdateContactsToRemove()
        {
            ImportManager manager = new ImportManager(UserContext.Current);

            // This file must be present in the ImportFolder configured in OutboundEmail.xml
            ImportDefinition definition = manager.GetImportDefinition("ContactsExport.csv", ImportType.ImportContacts);

            // Auto match the import file (Matches fields from the import file with equally named database fields).
            definition.MatchImportFile();

            // You have to set this to the 'All Contacts' Address Book in order to move Contacts across Address Books
            definition.AddressBookUri = TcmUri.NullUri;

            // Change the Address Book of the Contacts (i.e. move them)
            definition.Mappings["group_keyvalue"].SourceField = null;
            definition.Mappings["group_keyvalue"].DestinationField.DefaultValue = AddressBookKey;

            // Hook into the feedback event to show some feedback
            manager.Progress += ImportProgress;

            // Import and report the results
            ImportResult results = manager.ImportContacts(definition);
            Console.WriteLine(results.AllDataImported ? "All Contacts were successfully imported" : string.Format(CultureInfo.InvariantCulture, "Contacts were imported, but some errors occurred, please check the {0} and {1} log files", definition.ErrorFile, definition.LogFile));
            Console.WriteLine(string.Format(CultureInfo.InvariantCulture, "Imported {0} new contacts.", results.ContactsCreated));
            Console.WriteLine(string.Format(CultureInfo.InvariantCulture, "Updated {0} existing contacts.", results.ContactsUpdated));

        }

        private static void ImportProgress(object sender, ImportEventArgs e)
        {
            if (e.Progress > 0)
            {
                Console.WriteLine(string.Format("[{0}] > {1} rows copied)", e.Description, e.Progress));
            }
            else
            {
                Console.WriteLine(string.Format("[{0}] {1}: {2} {3}", DateTime.Now.ToString("s"), e.ImportStep.ToString().PadRight(22, ' '),
                    e.Description, e.Starting ? "(starting)" : "(complete)"));
            }
        }
    }
}

If you have no control over the file and the columns aren't named the same as your fields, you'll need to map the column in the file to the field in the database. You do that similar to how we set the "group_keyvalue" field -- you just need to set the SourceField like this:

definition.Mappings["name-of-field-in-AM"].SourceField = definition.SourceFields["name-of-column-in-the-file"];

This approach will be very fast (I just moved 10.000 Contacts in about a second), whereas looping over them one by one will not be.

Good luck! :)

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.