diff --git a/FakeXrmEasy.Tests/FakeContextTests/LinqTests/FakeContextTestLinqQueries.cs b/FakeXrmEasy.Tests/FakeContextTests/LinqTests/FakeContextTestLinqQueries.cs index b6810d0d..29fb7af6 100644 --- a/FakeXrmEasy.Tests/FakeContextTests/LinqTests/FakeContextTestLinqQueries.cs +++ b/FakeXrmEasy.Tests/FakeContextTests/LinqTests/FakeContextTestLinqQueries.cs @@ -39,6 +39,8 @@ where c.FirstName.Equals("Jordi") A.CallTo(() => service.Execute(A.That.Matches(x => x is RetrieveMultipleRequest && ((RetrieveMultipleRequest)x).Query is QueryExpression))).MustHaveHappened(); } + + [Fact] public void When_doing_a_crm_linq_query_with_an_equals_operator_record_is_returned() { @@ -72,6 +74,70 @@ where c.FirstName.Equals("Jordi") } + [Fact] + public void When_doing_a_crm_linq_query_and_proxy_types_and_a_selected_attribute_returned_projected_entity_is_thesubclass() + { + var fakedContext = new XrmFakedContext(); + var guid1 = Guid.NewGuid(); + var guid2 = Guid.NewGuid(); + + fakedContext.Initialize(new List() { + new Contact() { Id = guid1, FirstName = "Jordi" }, + new Contact() { Id = guid2, FirstName = "Other" } + }); + + var service = fakedContext.GetFakedOrganizationService(); + + using (XrmServiceContext ctx = new XrmServiceContext(service)) + { + var matches = (from c in ctx.CreateQuery() + where c.FirstName.Equals("Jordi") + select new + { + FirstName = c.FirstName, + CrmRecord = c + }).ToList(); + + Assert.True(matches.Count == 1); + Assert.True(matches[0].FirstName.Equals("Jordi")); + Assert.IsAssignableFrom(typeof(Contact), matches[0].CrmRecord); + Assert.True(matches[0].CrmRecord.GetType() == typeof(Contact)); + + } + + } + + [Fact] + public void When_doing_a_crm_linq_query_and_proxy_types_projection_must_be_applied_after_where_clause() + { + var fakedContext = new XrmFakedContext(); + fakedContext.ProxyTypesAssembly = Assembly.GetExecutingAssembly(); + + var guid1 = Guid.NewGuid(); + var guid2 = Guid.NewGuid(); + + fakedContext.Initialize(new List() { + new Contact() { Id = guid1, FirstName = "Jordi", LastName = "Montana" }, + new Contact() { Id = guid2, FirstName = "Other" } + }); + + var service = fakedContext.GetFakedOrganizationService(); + + using (XrmServiceContext ctx = new XrmServiceContext(service)) + { + var matches = (from c in ctx.CreateQuery() + where c.LastName == "Montana" //Should be able to filter by a non-selected attribute + select new + { + FirstName = c.FirstName + }).ToList(); + + Assert.True(matches.Count == 1); + Assert.True(matches[0].FirstName.Equals("Jordi")); + } + + } + [Fact] public void When_doing_a_crm_linq_query_with_an_equals_operator_and_nulls_record_is_returned() { diff --git a/FakeXrmEasy.Tests/FakeContextTests/LinqTests/MetadataInferenceTests.cs b/FakeXrmEasy.Tests/FakeContextTests/LinqTests/MetadataInferenceTests.cs new file mode 100644 index 00000000..01a1ba55 --- /dev/null +++ b/FakeXrmEasy.Tests/FakeContextTests/LinqTests/MetadataInferenceTests.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using FakeItEasy; +using FakeXrmEasy; +using Xunit; +using Microsoft.Xrm.Sdk; +using Microsoft.Xrm.Sdk.Query; +using Microsoft.Xrm.Sdk.Client; +using Crm; +using Microsoft.Xrm.Sdk.Messages; +using System.Reflection; //TypedEntities generated code for testing + + + +namespace FakeXrmEasy.Tests.FakeContextTests.LinqTests +{ + public class MetadataInferenceTests + { + [Fact] + public static void When_using_proxy_types_assembly_the_entity_metadata_is_inferred_from_the_proxy_types_assembly() + { + var fakedContext = new XrmFakedContext(); + fakedContext.ProxyTypesAssembly = Assembly.GetExecutingAssembly(); + + //Empty contecxt (no Initialize), but we should be able to query any typed entity without an entity not found exception + + var service = fakedContext.GetFakedOrganizationService(); + + using (XrmServiceContext ctx = new XrmServiceContext(service)) + { + var contact = (from c in ctx.CreateQuery() + where c.FirstName.Equals("Anything!") + select c).ToList(); + + Assert.True(contact.Count == 0); + } + } + + [Fact] + public static void When_using_proxy_types_assembly_the_attribute_metadata_is_inferred_from_the_proxy_types_assembly() + { + var fakedContext = new XrmFakedContext(); + fakedContext.ProxyTypesAssembly = Assembly.GetExecutingAssembly(); + + var contact1 = new Entity("contact") { Id = Guid.NewGuid() }; contact1["fullname"] = "Contact 1"; contact1["firstname"] = "First 1"; + var contact2 = new Entity("contact") { Id = Guid.NewGuid() }; contact2["fullname"] = "Contact 2"; contact2["firstname"] = "First 2"; + + fakedContext.Initialize(new List() { contact1, contact2 }); + + var guid = Guid.NewGuid(); + + //Empty contecxt (no Initialize), but we should be able to query any typed entity without an entity not found exception + + var service = fakedContext.GetFakedOrganizationService(); + + using (XrmServiceContext ctx = new XrmServiceContext(service)) + { + var contact = (from c in ctx.CreateQuery() + where c.FirstName.Equals("First 1") + select c).ToList(); + + Assert.True(contact.Count == 1); + } + } + } +} diff --git a/FakeXrmEasy.Tests/FakeContextTests/TranslateQueryExpressionTests/ConditionExpressionTests.cs b/FakeXrmEasy.Tests/FakeContextTests/TranslateQueryExpressionTests/ConditionExpressionTests.cs index 44b43489..91afedea 100644 --- a/FakeXrmEasy.Tests/FakeContextTests/TranslateQueryExpressionTests/ConditionExpressionTests.cs +++ b/FakeXrmEasy.Tests/FakeContextTests/TranslateQueryExpressionTests/ConditionExpressionTests.cs @@ -111,5 +111,47 @@ public void When_executing_a_query_expression_with_contains_operator_right_resul Assert.True(result.Count() == 2); } + + [Fact] + public void When_executing_a_query_expression_with_null_operator_right_result_is_returned() + { + var context = new XrmFakedContext(); + var contact1 = new Entity("contact") { Id = Guid.NewGuid() }; contact1["fullname"] = "1 Contact"; + var contact2 = new Entity("contact") { Id = Guid.NewGuid() }; contact2["fullname"] = null; + var contact3 = new Entity("contact") { Id = Guid.NewGuid() }; + + context.Initialize(new List() { contact1, contact2, contact3 }); + + var qe = new QueryExpression() { EntityName = "contact" }; + qe.ColumnSet = new ColumnSet(true); + qe.Criteria = new FilterExpression(LogicalOperator.And); + var condition = new ConditionExpression("fullname", ConditionOperator.Null); + qe.Criteria.AddCondition(condition); + + var result = XrmFakedContext.TranslateQueryExpressionToLinq(context, qe).ToList(); + + Assert.True(result.Count() == 2); + } + + [Fact] + public void When_executing_a_query_expression_with_a_not_null_operator_right_result_is_returned() + { + var context = new XrmFakedContext(); + var contact1 = new Entity("contact") { Id = Guid.NewGuid() }; contact1["fullname"] = "1 Contact"; + var contact2 = new Entity("contact") { Id = Guid.NewGuid() }; contact2["fullname"] = null; + var contact3 = new Entity("contact") { Id = Guid.NewGuid() }; + + context.Initialize(new List() { contact1, contact2, contact3 }); + + var qe = new QueryExpression() { EntityName = "contact" }; + qe.ColumnSet = new ColumnSet(true); + qe.Criteria = new FilterExpression(LogicalOperator.And); + var condition = new ConditionExpression("fullname", ConditionOperator.NotNull); + qe.Criteria.AddCondition(condition); + + var result = XrmFakedContext.TranslateQueryExpressionToLinq(context, qe).ToList(); + + Assert.True(result.Count() == 1); + } } } diff --git a/FakeXrmEasy.Tests/FakeXrmEasy.Tests.csproj b/FakeXrmEasy.Tests/FakeXrmEasy.Tests.csproj index 7f59c251..87d11ac2 100644 --- a/FakeXrmEasy.Tests/FakeXrmEasy.Tests.csproj +++ b/FakeXrmEasy.Tests/FakeXrmEasy.Tests.csproj @@ -79,6 +79,7 @@ + diff --git a/FakeXrmEasy/Extensions/EntityExtensions.cs b/FakeXrmEasy/Extensions/EntityExtensions.cs index 1d14d499..f125fdbc 100644 --- a/FakeXrmEasy/Extensions/EntityExtensions.cs +++ b/FakeXrmEasy/Extensions/EntityExtensions.cs @@ -43,8 +43,24 @@ public static Entity ProjectAttributes(this Entity e, ColumnSet columnSet, XrmFa } else { - //Return selected list of attributes - var projected = new Entity(e.LogicalName) { Id = e.Id }; + //Return selected list of attributes in a projected entity + Entity projected = null; + + //However, if we are using proxy types, we must create a instance of the appropiate class + if (context.ProxyTypesAssembly != null) + { + var subClassType = context.FindReflectedType(e.LogicalName); + if (subClassType != null) + { + var instance = Activator.CreateInstance(subClassType); + projected = (Entity)instance; + projected.Id = e.Id; + } + else + projected = new Entity(e.LogicalName) { Id = e.Id }; //fallback to generic type if type not found + } + else + projected = new Entity(e.LogicalName) { Id = e.Id }; foreach (var attKey in columnSet.Columns) { diff --git a/FakeXrmEasy/OrganizationFaults/OrganizationServiceFaultQueryBuilderNoAttribute.cs b/FakeXrmEasy/OrganizationFaults/OrganizationServiceFaultQueryBuilderNoAttribute.cs index b096c2d5..590a5d1b 100644 --- a/FakeXrmEasy/OrganizationFaults/OrganizationServiceFaultQueryBuilderNoAttribute.cs +++ b/FakeXrmEasy/OrganizationFaults/OrganizationServiceFaultQueryBuilderNoAttribute.cs @@ -20,7 +20,8 @@ public static void Throw(string sMissingAttributeName) { ErrorCode = ErrorCode, Message = string.Format("The attribute {0} does not exist on this entity.", sMissingAttributeName) - }); + }, + new FaultReason(string.Format("The attribute {0} does not exist on this entity.", sMissingAttributeName))); } } } diff --git a/FakeXrmEasy/XrmFakedContext.Crud.cs b/FakeXrmEasy/XrmFakedContext.Crud.cs index 60c54f0b..6d5943c6 100644 --- a/FakeXrmEasy/XrmFakedContext.Crud.cs +++ b/FakeXrmEasy/XrmFakedContext.Crud.cs @@ -190,8 +190,18 @@ protected static void FakeDelete(XrmFakedContext context, IOrganizationService f #region Other protected methods protected void EnsureEntityNameExistsInMetadata(string sEntityName) { - if (!Data.ContainsKey(sEntityName)) + //Entity metadata is checked differently when we are using a ProxyTypesAssembly => we can infer that from the generated types assembly + if (ProxyTypesAssembly != null) { + var subClassType = FindReflectedType(sEntityName); + if (subClassType == null) + { + throw new Exception(string.Format("Entity {0} does not exist in the metadata cache", sEntityName)); + } + } + else if (!Data.ContainsKey(sEntityName)) + { + //No Proxy Types Assembly throw new Exception(string.Format("Entity {0} does not exist in the metadata cache", sEntityName)); }; } @@ -235,10 +245,32 @@ protected internal void AddEntity(Entity e) AttributeMetadata.Add(e.LogicalName, new Dictionary()); //Update attribute metadata - foreach (var attKey in e.Attributes.Keys) + if (ProxyTypesAssembly != null) + { + //If the context is using a proxy types assembly then we can just guess the metadata from the generated attributes + var type = FindReflectedType(e.LogicalName); + if (type != null) + { + var props = type.GetProperties(); + foreach (var p in props) + { + if (!AttributeMetadata[e.LogicalName].ContainsKey(p.Name)) + AttributeMetadata[e.LogicalName].Add(p.Name, p.Name); + } + } + else + throw new Exception(string.Format("Couldnt find reflected type for {0}", e.LogicalName)); + + } + else { - if (!AttributeMetadata[e.LogicalName].ContainsKey(attKey)) - AttributeMetadata[e.LogicalName].Add(attKey, attKey); + //If dynamic entities are being used, then the only way of guessing if a property exists is just by checking + //if the entity has the attribute in the dictionary + foreach (var attKey in e.Attributes.Keys) + { + if (!AttributeMetadata[e.LogicalName].ContainsKey(attKey)) + AttributeMetadata[e.LogicalName].Add(attKey, attKey); + } } } diff --git a/FakeXrmEasy/XrmFakedContext.Queries.cs b/FakeXrmEasy/XrmFakedContext.Queries.cs index 81893045..bfe90347 100644 --- a/FakeXrmEasy/XrmFakedContext.Queries.cs +++ b/FakeXrmEasy/XrmFakedContext.Queries.cs @@ -138,8 +138,13 @@ public static IQueryable TranslateQueryExpressionToLinq(XrmFakedContext //Start form the root entity and build a LINQ query to execute the query against the In-Memory context: context.EnsureEntityNameExistsInMetadata(qe.EntityName); + IQueryable query = null; + //var proxyType = context.FindReflectedType(qe.EntityName); - var query = context.CreateQuery(qe.EntityName); + //if(proxyType != null) + // query = context.CreateQuery(qe.EntityName); + //else + query = context.CreateQuery(qe.EntityName); //Add as many Joins as linked entities foreach (LinkEntity le in qe.LinkEntities) @@ -147,16 +152,17 @@ public static IQueryable TranslateQueryExpressionToLinq(XrmFakedContext query = TranslateLinkedEntityToLinq(context, le, query, qe.ColumnSet); } - - //Project the attributes in the root column set - if(qe.ColumnSet != null && !qe.ColumnSet.AllColumns) - query = query.Select(x => x.ProjectAttributes(qe.ColumnSet, context)); - // Compose the expression tree that represents the parameter to the predicate. ParameterExpression entity = Expression.Parameter(typeof(Entity)); var expTreeBody = TranslateFilterExpressionToExpression(qe.Criteria, entity); Expression> lambda = Expression.Lambda>(expTreeBody, entity); query = query.Where(lambda); + + //Project the attributes in the root column set (must be applied after the where clause, not before!!) + if (qe.ColumnSet != null && !qe.ColumnSet.AllColumns) + query = query.Select(x => x.ProjectAttributes(qe.ColumnSet, context)); + + return query; }