Bhawk90 / Dataverse.DataProviders.Samples

Data Provider samples for virtual entities that can be used in your Dataverse projects on the Microsoft Power Platform
MIT License
6 stars 1 forks source link

Doesn't work for EntityReference Properties #2

Open zappod opened 1 year ago

zappod commented 1 year ago

Hi.

Do you know how to make this work for properties of type EntityReference? I added a property to the Account model which is a Entity reference that reffers another account.

property: [AttributeLogicalName("mot_referedaccount")] public EntityReference mot_referedaccount { get => this.GetAttributeValue(nameof (mot_referedaccount)); set { this.OnPropertyChanging(nameof (mot_referedaccount)); this.SetAttributeValue(nameof (mot_referedaccount), (object) value); this.OnPropertyChanged(nameof (mot_referedaccount)); } }

Then I added this test: [TestMethod] public void EntityFilter_EntityReference() { // Create source entity list var accounts = GetAccountList();

        // Define queries to be appled on source
        var queryExpression = new QueryExpression();
        queryExpression.Criteria.AddCondition("mot_referedaccount", ConditionOperator.Equal, new EntityReference(Account.EntityLogicalName, accounts[1].Id));

        // Apply filters on entity list
        IEntityFilter<Account> entityFilter = new EntityFilter<Account>();
        var filteredResults = entityFilter.FilterBy(accounts, queryExpression);

        Assert.AreEqual(1, filteredResults.Count);
    }

GetAccountList2() looks like this: private List GetAccountList2() { var accountFaker = new Faker() .RuleFor(a => a.accountid, f => f.Random.Guid()) .RuleFor(a => a.name, f => $"{f.Company.CompanyName()} {f.Company.CompanySuffix()}") .RuleFor(a => a.accountnumber, f => String.Join("", f.Random.Digits(5))) .RuleFor(a => a.createdon, f => f.Date.Past(1)) .RuleFor(a => a.telephone1, f => f.Phone.PhoneNumber()) .RuleFor(a => a.emailaddress1, f => f.Internet.Email()) .RuleFor(a => a.fax, f => f.Phone.PhoneNumber()) .RuleFor(a => a.donotemail, f => f.Random.Bool()) .RuleFor(a => a.donotfax, f => f.Random.Bool()) .RuleFor(a => a.donotphone, f => f.Random.Bool()) .RuleFor(a => a.preferredcontactmethodcode, f => f.PickRandom()); var list = accountFaker.Generate(2).ToArray();

        list[0].mot_referedaccount = new EntityReference(Account.EntityLogicalName, list[1].Id);
        return list.ToList();
    }

The entity filter will always return 0 accounts. Any idea why?

Bhawk90 commented 1 year ago

Hi zappod!

I did a quick check by replicating your code and for me it seemed to be working properly. One thing I have had to change is the getter of your property as it gave me a compile error without specifying the template parameter for the GetAttributeValue method, so the updated code looks like as follows:

[AttributeLogicalName("mot_referedaccount")]
public EntityReference mot_referedaccount
{
    get => this.GetAttributeValue<EntityReference>(nameof(mot_referedaccount));
    set
    {
        this.OnPropertyChanging(nameof(mot_referedaccount));
        this.SetAttributeValue(nameof(mot_referedaccount), (object)value);
        this.OnPropertyChanged(nameof(mot_referedaccount));
    }
}

Just like you did, I have filled the new property for only one account:

fakeAccounts[2].mot_referedaccount = new Microsoft.Xrm.Sdk.EntityReference(fakeAccounts[0].LogicalName, fakeAccounts[0].Id);

The following test method returned with a success, the length of the result was one:

[TestMethod]
public void EntityFilter_EmptyFilter_ReferenceWorks()
{
    // Create source entity list
    var accounts = GetAccountList();

    // Define queries to be appled on source
    var queryExpression = new QueryExpression();
    queryExpression.Criteria.AddCondition("mot_referedaccount", ConditionOperator.NotNull);

    // Apply filters on entity list
    IEntityFilter<Account> entityFilter = new EntityFilter<Account>();
    var filteredResults = entityFilter.FilterBy(accounts, queryExpression);

    // Define queries to be appled on source
    var queryExpression2 = new QueryExpression();
    queryExpression2.Criteria.AddCondition("mot_referedaccount", ConditionOperator.Equal, filteredResults.First().mot_referedaccount);

    var filteredResuls2 = entityFilter.FilterBy(accounts, queryExpression2);

    Assert.AreEqual(1, filteredResuls2.Count);
}

Could you please check if just updating the getter of your new property would resolve the issue?

By the way, I would recommend you to use an Early Bound Generator tool (if you don't use it yet) such as DLaB.Xrm.EarlyBoundGenerator which can generate you an up-to-date version of the Entities file :)

aa-dit-yuh commented 8 months ago

It looks like ConditionOperator.Equal operator does not work with non-primitive types such as EntityReferences. The compiled LINQ reads:

$var1.mot_referedaccount == .Constant<Microsoft.Xrm.Sdk.EntityReference>(Microsoft.Xrm.Sdk.EntityReference)

The LINQ invokes the == operator on two EntityReference objects, which performs a reference equality (or opposed to calling the Equals method) and so always results in false.

aa-dit-yuh commented 8 months ago

In the example listed above:

    // Define queries to be appled on source
    var queryExpression2 = new QueryExpression();
    queryExpression2.Criteria.AddCondition("mot_referedaccount", ConditionOperator.Equal, filteredResults.First().mot_referedaccount);

    var filteredResuls2 = entityFilter.FilterBy(accounts, queryExpression2);

The value in the AddCondition method is an already existing reference to some EntityReference object. It succeeds the reference equality.

The following example would likely fail:


    // Define queries to be appled on source
    var queryExpression2 = new QueryExpression();
    queryExpression2.Criteria.AddCondition("mot_referedaccount", ConditionOperator.Equal, new EntityReference(filteredResults.First().mot_referedaccount.LogicalName, filteredResults.First().mot_referedaccount.Id));

    var filteredResuls2 = entityFilter.FilterBy(accounts, queryExpression2);
aa-dit-yuh commented 8 months ago

What works is modifying the mot_referedaccount prop to return a Guid instead of an EntityReference object. The == operator invoked by LINQ works fine with structs.

[AttributeLogicalName("mot_referedaccount")]
public System.Nullable<System.Guid> mot_referedaccount
{
    get => this.GetAttributeValue<EntityReference>(nameof(mot_referedaccount))?.Id;
}

Luckily, I do not need setters on such props.