I've made a custom rule that enables the user to get a date field from any facet and see if that date's passed. I've made two variants, one for personalization and one for list segmentation/Marketing Automation. They use the same code to get the facet value. The personalization rule works fine; however when I try to segment a list an error is shown
and the log shows this beauty (YourLinqIsTooStrongException):
11856 17:37:43 ERROR Application error.
Exception: System.Web.HttpUnhandledException
Message: Exception of type 'System.Web.HttpUnhandledException' was thrown.
Source: System.Web
at System.Web.UI.Page.HandleError(Exception e)
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
at System.Web.UI.Page.ProcessRequest(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
at System.Web.UI.Page.ProcessRequest()
at System.Web.UI.Page.ProcessRequest(HttpContext context)
at System.Web.HttpApplication.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute()
at System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step)
at System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously)
Nested Exception
Exception: System.Reflection.TargetInvocationException
Message: Exception has been thrown by the target of an invocation.
Source: mscorlib
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Sitecore.Reflection.ReflectionUtil.InvokeMethod(MethodInfo method, Object[] parameters, Object obj)
at Sitecore.Nexus.Pipelines.NexusPipelineApi.Resume(PipelineArgs args, Pipeline pipeline)
at Sitecore.Web.UI.Sheer.ClientPage.ResumePipeline()
at Sitecore.Web.UI.Sheer.ClientPage.OnPreRender(EventArgs e)
at System.Web.UI.Control.PreRenderRecursiveInternal()
at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean includeStagesAfterAsyncPoint)
Nested Exception
Exception: Sitecore.XConnect.Search.YourLinqIsTooStrongException
Message: Unsupported query: (VisitMethodCall): value(VGZ.Sitecore.Foundation.MarketingAutomation.Rules.ExmDateCondition).Evaluate([contact])
Source: Sitecore.Xdb.Common.Web
at Sitecore.Xdb.Common.Web.Synchronous.SynchronousExtensions.SuspendContextLock[TResult](Func\`1 taskFactory)
at Sitecore.XConnect.Client.XConnectSynchronousExtensions.SuspendContextLock[TResult](Func\`1 taskFactory)
at Sitecore.XConnect.Client.XConnectSynchronousExtensions.GetBatchEnumeratorSync[TEntity](IAsyncQueryable\`1 asyncQueryable, Int32 defaultBatchSize)
at Sitecore.XConnect.Segmentation.SegmentationEngine.<>c.<CountContacts>b__24_0(IAsyncQueryable\`1 contacts)
at Sitecore.XConnect.Segmentation.SegmentationEngine.PerformSearch[T](IContactSearchQueryFactory queryFactory, Func\`2 searchWork, Byte[] syncToken, ContactExpandOptions expandOptions)
at Sitecore.XConnect.Segmentation.SegmentationEngine.CountContacts(IContactSearchQueryFactory queryFactory)
at Sitecore.Shell.Applications.Analytics.SegmentBuilder.SegmentBuilderForm.EditFilterCondition(ClientPipelineArgs args)
The class that's giving me headaches:
public class ExmDateCondition : ICondition, IMappableRuleEntity, IContactSearchQueryFactory
{
/// <summary>Gets or sets the facet that contains a date.</summary>
/// <value>facet.value</value>
public string DateFacet { get; set; }
public Expression<Func<Contact, bool>> CreateContactSearchQuery(IContactSearchQueryContext context)
{
return contact => Evaluate(contact);
}
public bool Evaluate(IRuleExecutionContext context)
{
return Evaluate(context.Fact<Contact>());
}
private bool Evaluate(Contact contact)
{
if (string.IsNullOrWhiteSpace(DateFacet))
{
Log.Info(GetType() + ": DateFacet is null", this);
return false;
}
string[] propertyPath = DateFacet.Split('.');
if (propertyPath.Length == 0)
{
Log.Info(GetType() + ": facet path is empty", this);
return false;
}
DateTime utcNow = DateTimeProvider.GetUtcNow();
var values = FacetProvider.GetFacetValue<DateTime>(contact, propertyPath);
if (utcNow >= values.ToUniversalTime())
{
return true;
}
// maybe the facet value was nullable datetime
var value = FacetProvider.GetFacetValue<DateTime?>(contact, propertyPath);
return value.HasValue && utcNow >= value.Value.ToUniversalTime();
}
}
They're both configured with this rule text:
when the date in [DateFacet,Text,,facetpath (Facet.ValueName)] has passed
Using the rule in a Marketing Automation listener doesn't generate an error but nothing seems to happen either.
Has anyone encountered this error before and how do I resolve it?
I can imagine it has something to do with Sitecore not liking the complexity of my rule, but I'd very much like to be able to query a facet value in a generic way like this.
EDIT: Even after updating my code to directly call GetFacetValue(), Sitecore now trips over that method. The solution was to let go of trying to make a generic rule and making separate rules for the facet values we need.
public Expression<Func<Contact, bool>> CreateContactSearchQuery(IContactSearchQueryContext context)
{
return contact => FacetProvider.GetFacetValue<DateTime>(contact, DateFacet.Split('.')) >= DateTimeProvider.GetUtcNow() || FacetProvider.GetFacetValue<DateTime?>(contact, DateFacet.Split('.')) >= DateTimeProvider.GetUtcNow();
}
resulted in
Exception: Sitecore.XConnect.Search.YourLinqIsTooStrongException
Message: Unsupported query: (VisitMethodCall): GetFacetValue([contact], value(System.String[]))