jordimontana82 / fake-xrm-easy

The testing framework for Dynamics CRM and Dynamics 365 which runs on an In-Memory context and deals with mocks or fakes for you
https://dynamicsvalue.com/get-started/overview?source=git
Other
262 stars 182 forks source link

One-to-many relationship metadata missing #451

Open sniarn opened 5 years ago

sniarn commented 5 years ago

Hi

I'm having an issue where I seemingly cannot query one-to-many relationships.

Here's my setup:

FakedContext = new XrmFakedContext
{
    ProxyTypesAssembly = Assembly.GetAssembly(typeof(DynamicsServiceContext))
};
FakedContext.InitializeMetadata(Assembly.GetAssembly(typeof(DynamicsServiceContext)));

...and my test code:

var account = new Account
{
    AccountId = Guid.NewGuid(),
    Name = "Foo",
};
var fax = new Fax
{
    ActivityId = Guid.NewGuid(),
    RegardingObjectId = account.ToEntityReference(),
    BillingCode = "Bar",
};
FakedContext.Initialize(new List<Entity> {account, fax});
using (var ctx = OrganizationServiceHelper.CreateContext<DynamicsServiceContext>())
{
    var actualAccount = ctx.AccountSet.Where(a => a.AccountId == account.Id).Single();
    ctx.LoadProperty(actualAccount, new Relationship(nameof(Account.Account_Faxes).ToLower()));
}

This causes a System.Exception "Relationship account_faxes does not exist in the metadata cache". Inspecting the XrmFakedContext instance reveals that the Relationships collection is empty even though I called InitializeMetadata. I get the same result if I use a RetrieveRequest instead and add a RelatedEntitiesQuery. Note that the relationship is an N:1. I thought that those were supposed to work without adding additional configuration?

jordimontana82 commented 5 years ago

Hi @sniarn

If you remove the .InitializeMetadata does it make any difference? Are also the early bound entities in the same assembly / namespace as the generated DynamicsServiceContext?

Try doing

ProxyTypesAssembly = Assembly.GetAssembly(typeof(Account))

instead please.

Finally, yes, relationships are supposed to work when queried by entity names (as in a LINQ query or linked entities for instance), but I believe since you're querying data using a relationship name (not the entity name or the lookup properties) it might possibly need to setup some fake relationship metadata as well (like the .Associate examples), it's just a wild guess.

sniarn commented 5 years ago

I'm currently on holiday, so I can't experiment with this. The early bound entities are in the same assembly as DynamicsServiceContext, so that shouldn't be an issue. I'll try your suggestions when I get back from my holiday.

sniarn commented 5 years ago

Hi @jordimontana82

I have tried you suggestions but to no avail. And yes, it's all in the same assembly. Where can I find the examples that you mentioned?

mtone commented 4 years ago

Currently, you need to manually add the relationships manually if referring to them by name.

InitializeMetadata() does read and store the relationships in entities - but not in the Relationships dictionary where the names are. So it should be possible to improve the framework.

It's possible to query those from CRM using a RetrieveRelationshipRequest and other metadata queries.

Here's a working snippet:

            var fakedContext = new XrmFakedContext();
            var orgService = fakedContext.GetOrganizationService();
            var ctx = new XrmServiceContext(orgService);
            fakedContext.AddRelationship("Account_Faxes", new XrmFakedRelationship
            {
                Entity1LogicalName = Fax.EntityLogicalName, // Referencing (1)
                Entity1Attribute = "regardingobjectid",
                Entity2LogicalName = Account.EntityLogicalName, // Referenced (N)
                Entity2Attribute = "accountid",
                RelationshipType = XrmFakedRelationship.enmFakeRelationshipType.OneToMany
            });

            var account = new Account
            {
                Name = "Foo"
            };
            account.Id = orgService.Create(account);
            var fax = new Fax
            {
                ActivityId = Guid.NewGuid(),
                RegardingObjectId = account.ToEntityReference(),
                BillingCode = "Bar"
            };
            fakedContext.Initialize(new List<Entity> {account, fax});
            var actualAccount = ctx.AccountSet.Where(a => a.AccountId == account.Id).Single();
            ctx.LoadProperty(actualAccount, new Relationship("Account_Faxes"));
sniarn commented 4 years ago

Yes, you can make it work by adding the relationship metadata yourself. But that's a whole lotta typing if you have a lot of unit tests.