xConnect tutorial #4: Search contacts and interactions

You need to complete xConnect tutorial #1, xConnect tutorial #2, and xConnect tutorial #3 before starting this tutorial.

The following tutorial demonstrates how to search contact and interaction data. Keep the following in mind when searching:

  • By default, anonymous contacts are not indexed and therefore not searchable

  • If a facet or property is marked PIISensitive, it is not indexed. This means you cannot search using this facet or property. You need to decide if you need to enforce PII compliance.  xConnect only gives you the mechanism to do so. Note that you can still return PII sensitive data (such as a contact’s first name) even though you cannot search for a contact by their first name.

  • Experience data is indexed, but not stored - only IDs and sync tokens are stored. When you use expand options to return facets as part of a query, that data is coming from the collection database - not from the index.

  1. Create a new console application or use the application from Tutorial #3.

  2. To search contacts, use client.Contacts followed by your query, as shown in the second try...catch block below. To search interactions, use client.Interactions:

    using Sitecore.XConnect;
    using Sitecore.XConnect.Client;
    using Sitecore.XConnect.Collection.Model;
    using Sitecore.XConnect.Schema;
    using System;
    using System.Linq;
    using System.Threading.Tasks;
    using Sitecore.Xdb.Common.Web;
    
    namespace Sitecore.Documentation
    {
        public class Program
        {
            private static void Main(string[] args)
            {
                MainAsync(args).ConfigureAwait(false).GetAwaiter().GetResult();
                Console.ForegroundColor = ConsoleColor.DarkGreen;
                Console.WriteLine("");
                Console.WriteLine("END OF PROGRAM.");
                Console.ReadKey();
            }
    
            private static async Task MainAsync(string[] args)
            {
                Console.WriteLine(" ");
    
                CertificateHttpClientHandlerModifierOptions options =
                CertificateHttpClientHandlerModifierOptions.Parse("StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue=15E6693B0AECB63DE57D991EC363CA462DC52432");
    
                var certificateModifier = new CertificateHttpClientHandlerModifier(options);
    
                List<IHttpClientModifier> clientModifiers = new List<IHttpClientModifier>();
                var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20));
                clientModifiers.Add(timeoutClientModifier);
    
                var collectionClient = new CollectionWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
                var searchClient = new SearchWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
                var configurationClient = new ConfigurationWebApiClient(new Uri("https://xconnect/configuration"), clientModifiers, new[] { certificateModifier });
    
                var cfg = new XConnectClientConfiguration(
                    new XdbRuntimeModel(CollectionModel.Model), collectionClient, searchClient, configurationClient);
    
                try
                {
                    cfg.Initialize(); // cfg.InitializeAsync();
    
                    // Print xConnect if configuration is valid
                    var arr = new[]
                    {
                                @"            ______                                                       __     ",
                                @"           /      \                                                     |  \    ",
                                @" __    __ |  $$$$$$\  ______   _______   _______    ______    _______  _| $$_   ",
                                @"|  \  /  \| $$   \$$ /      \ |       \ |       \  /      \  /       \|   $$ \  ",
                                @"\$$\/  $$| $$      |  $$$$$$\| $$$$$$$\| $$$$$$$\|  $$$$$$\|  $$$$$$$ \$$$$$$   ",
                                @" >$$  $$ | $$   __ | $$  | $$| $$  | $$| $$  | $$| $$    $$| $$        | $$ __  ",
                                @" /  $$$$\ | $$__/  \| $$__/ $$| $$  | $$| $$  | $$| $$$$$$$$| $$_____   | $$|  \",
                                @"|  $$ \$$\ \$$    $$ \$$    $$| $$  | $$| $$  | $$ \$$     \ \$$     \   \$$  $$",
                                @" \$$   \$$  \$$$$$$   \$$$$$$  \$$   \$$ \$$   \$$  \$$$$$$$  \$$$$$$$    \$$$$ "
                            };
                    Console.WindowWidth = 160;
                    foreach (string line in arr)
                        Console.WriteLine(line);
    
                }
                catch (XdbModelConflictException ce)
                {
                    Console.WriteLine("ERROR:" + ce.Message);
                    return;
                }
    
                // Initialize a client using the validated configuration
                using (var client = new XConnectClient(cfg))
                {
                    try
                    {
                        var results0 = client.Contacts.ToEnumerable().Count();
    
                        Console.WriteLine("Total contacts: " + results0.ToString());
    
                        // Use InteractionsCache instead of client.Contacts.Where(x => x.Interactions.Any()) as not all search providers support joins
                        var results = await client.Contacts.Where(c => c.InteractionsCache().InteractionCaches.Any()).GetBatchEnumerator();
    
                        Console.WriteLine("Contacts with interactions: " + results.TotalCount);
    
                        var results2 = await client.Contacts.Where(c => c.LastModified > DateTime.UtcNow.AddHours(-10)).GetBatchEnumerator();
    
                        Console.WriteLine("Updated 10hrs ago: " + results2.TotalCount);
    
                        var results3 = await client.Contacts.Where(c => c.GetFacet<PersonalInformation>().JobTitle == "Programmer Writer").GetBatchEnumerator();
    
                        Console.WriteLine("Programmer Writers: " + results3.TotalCount);
    
                        var results4 = await client.Interactions.Where(i => i.EndDateTime > DateTime.UtcNow.AddHours(-10)).GetBatchEnumerator();
    
                        Console.WriteLine("Interactions < 10hrs old: " + results4.TotalCount);
    
                        Console.ReadKey();
                    }
                    catch (XdbExecutionException ex)
                    {
                        // Deal with exception
                    }
                }
            }
        }
    }
    
  3. Press F5. Your numbers will be different depending on when and how many times you ran the program from tutorials #1 - #3:

    console-works-71.png
  4. You can use expand options to customize what data is returned, just as you did with .Get(). In the following example, ContactExpandOptions and InteractionExpandOptions are being used to return additional data with each contact and interaction:

    using Sitecore.XConnect;
    using Sitecore.XConnect.Client;
    using Sitecore.XConnect.Client.WebApi;
    using Sitecore.XConnect.Collection.Model;
    using Sitecore.XConnect.Schema;
    using Sitecore.Xdb.Common.Web;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Sitecore.Xdb.Common.Web;
    
    namespace Sitecore.Documentation
    {
        public class Program
        {
            private static void Main(string[] args)
            {
                MainAsync(args).ConfigureAwait(false).GetAwaiter().GetResult();
                Console.ForegroundColor = ConsoleColor.DarkGreen;
                Console.WriteLine("");
                Console.WriteLine("END OF PROGRAM.");
                Console.ReadKey();
            }
    
            private static async Task MainAsync(string[] args)
            {
                Console.WriteLine(" ");
    
                CertificateHttpClientHandlerModifierOptions options =
                CertificateHttpClientHandlerModifierOptions.Parse("StoreName=My;StoreLocation=LocalMachine;FindType=FindByThumbprint;FindValue=15E6693B0AECB63DE57D991EC363CA462DC52432");
    
                var certificateModifier = new CertificateHttpClientHandlerModifier(options);
    
                List<IHttpClientModifier> clientModifiers = new List<IHttpClientModifier>();
                var timeoutClientModifier = new TimeoutHttpClientModifier(new TimeSpan(0, 0, 20));
                clientModifiers.Add(timeoutClientModifier);
    
                var collectionClient = new CollectionWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
                var searchClient = new SearchWebApiClient(new Uri("https://xconnect/odata"), clientModifiers, new[] { certificateModifier });
                var configurationClient = new ConfigurationWebApiClient(new Uri("https://xconnect/configuration"), clientModifiers, new[] { certificateModifier });
    
                var cfg = new XConnectClientConfiguration(
                    new XdbRuntimeModel(CollectionModel.Model), collectionClient, searchClient, configurationClient);
                try
                {
                    cfg.Initialize(); // cfg.InitializeAsync();
    
                    // Print xConnect if configuration is valid
                    var arr = new[]
                    {
                                @"            ______                                                       __     ",
                                @"           /      \                                                     |  \    ",
                                @" __    __ |  $$$$$$\  ______   _______   _______    ______    _______  _| $$_   ",
                                @"|  \  /  \| $$   \$$ /      \ |       \ |       \  /      \  /       \|   $$ \  ",
                                @"\$$\/  $$| $$      |  $$$$$$\| $$$$$$$\| $$$$$$$\|  $$$$$$\|  $$$$$$$ \$$$$$$   ",
                                @" >$$  $$ | $$   __ | $$  | $$| $$  | $$| $$  | $$| $$    $$| $$        | $$ __  ",
                                @" /  $$$$\ | $$__/  \| $$__/ $$| $$  | $$| $$  | $$| $$$$$$$$| $$_____   | $$|  \",
                                @"|  $$ \$$\ \$$    $$ \$$    $$| $$  | $$| $$  | $$ \$$     \ \$$     \   \$$  $$",
                                @" \$$   \$$  \$$$$$$   \$$$$$$  \$$   \$$ \$$   \$$  \$$$$$$$  \$$$$$$$    \$$$$ "
                            };
                    Console.WindowWidth = 160;
                    foreach (string line in arr)
                        Console.WriteLine(line);
    
                }
                catch (XdbModelConflictException ce)
                {
                    Console.WriteLine("ERROR:" + ce.Message);
                    return;
                }
    
                // Initialize a client using the validated configuration
                using (var client = new XConnectClient(cfg))
                {
                    try
                    {
                        var results0 = client.Contacts.ToEnumerable().Count();
    
                        Console.WriteLine("Total contacts: " + results0.ToString());
    
                        var results = await client.Contacts.Where(c => c.InteractionsCache().InteractionCaches.Any()).WithExpandOptions(new ContactExpandOptions(PersonalInformation.DefaultFacetKey)
                        {
                            Interactions = new RelatedInteractionsExpandOptions(IpInfo.DefaultFacetKey)
                            {
                                EndDateTime = DateTime.MaxValue,
                                StartDateTime = DateTime.MinValue
                            }
                        })
                        .GetBatchEnumerator();
    
                        Console.WriteLine("Contacts with interactions: " + results.TotalCount);
    
                        var results2 = await client.Contacts.Where(c => c.LastModified > DateTime.UtcNow.AddHours(-10)).GetBatchEnumerator();
    
                        Console.WriteLine("Updated 10hrs ago: " + results2.TotalCount);
    
                        var results3 = await client.Contacts.Where(c => c.GetFacet<PersonalInformation>().JobTitle == "Programmer Writer").GetBatchEnumerator();
    
                        Console.WriteLine("Programmer Writers: " + results3.TotalCount);
    
                        var results4 = await client.Interactions.Where(i => i.EndDateTime > DateTime.UtcNow.AddHours(-10))
                                .WithExpandOptions(new InteractionExpandOptions(new string[] { IpInfo.DefaultFacetKey })
                                {
                                    Contact = new RelatedContactExpandOptions(PersonalInformation.DefaultFacetKey)
                                })
                                .GetBatchEnumerator();
    
                        Console.WriteLine("Interactions < 10hrs old: " + results4.TotalCount);
    
                        int interactionNumber = 0;
    
                        // Enumerate through batches of 200
                        while (await results4.MoveNext())
                        {
                            // Loop through interactions in current batch
                            foreach (var interaction in results4.Current)
                            {
                                interactionNumber++;
    
                                Console.WriteLine("Interaction #" + interactionNumber);
    
                                var ipInfoFacet = interaction.GetFacet<IpInfo>(IpInfo.DefaultFacetKey);
    
                                if (ipInfoFacet != null)
                                {
                                    Console.WriteLine("Interaction business name: " + ipInfoFacet.BusinessName);
                                }
                                else
                                {
                                    Console.WriteLine("No business name available.");
                                }
    
                                var contact = interaction.Contact;
    
                                if (contact != null)
                                {
                                    var realContact = contact as Contact;
    
                                    var personal = realContact.GetFacet<PersonalInformation>(PersonalInformation.DefaultFacetKey);
    
                                    if (personal != null)
                                    {
                                        Console.WriteLine("Interaction contact name: " + personal.FirstName);
                                    }
                                    else
                                    {
                                        Console.WriteLine("No contact name available.");
                                    }
                                }
    
                                Console.WriteLine();
                            }
                        }
                        Console.ReadKey();
                    }
                    catch (XdbExecutionException ex)
                    {
                        // Deal with exception
                    }
                }
            }
        }
    }
    
  5. Press F5 . The console will output the Name of each interaction's contact if a PersonalInfo facet exists, and the Business name of each interaction if an IpInfo facet exists:

    xconnect-tutorial-4-5.png