2

UPDATE

Even doing a search on the contacts firstName or LastName causes issues:

var contacts =
            (
                from c in context.ContactSet
                join m in context.py3_membershipSet on c.ContactId equals m.py3_Member.Id
                where m.statuscode.Value == 1
                && ((c.FirstName != null && c.FirstName == searchTerm) || (c.LastName!=null && c.LastName == searchTerm) || (c.FullName != null && c.FullName == searchTerm))  
                orderby c.LastName
                select new
                {
                    ContactId = c.ContactId,
                    FirstName = c.FirstName,
                    LastName = c.LastName,
                    BranchCode = c.py3_BranchArea,
                    Branch = (c.FormattedValues != null && c.FormattedValues.Contains("py3_brancharea") ? c.FormattedValues["py3_brancharea"] : "N/a"),
                    JobTitle = c.JobTitle,
                    Organisation = (c.ParentCustomerId != null ? c.ParentCustomerId.Name : "N/a"),
                    joinedAsCode = c.py3_SOLACEMemberJoinedAs,
                    JoinedAs = (c.FormattedValues != null && c.FormattedValues.Contains("py3_solacememberjoinedas") ? c.FormattedValues["py3_solacememberjoinedas"] : "N/a"),
                    Expertise = (c.py3_SOLACEMemberAreasofExpertise != null && c.py3_SOLACEMemberAreasofExpertise.Trim() != String.Empty ? c.py3_SOLACEMemberAreasofExpertise : "N/a"),
                    Title = c.Salutation
                }
            );

Which screams back:

'py3_membership' entity doesn't contain attribute with Name = 'firstname'.


Ive got the following code that het some info from an Online CRM:

        var context = new XrmServiceContext();
    var contacts1 =
        (
            from c in context.ContactSet
            join m in context.py3_membershipSet on c.ContactId equals m.py3_Member.Id
            where m.statuscode.Value == 1

            orderby c.LastName
            select new
            {
                ContactId = c.ContactId,
                FirstName = c.FirstName,
                LastName = c.LastName,
                BranchCode = c.py3_BranchArea,
                Branch = (c.FormattedValues != null && c.FormattedValues.Contains("py3_brancharea") ? c.FormattedValues["py3_brancharea"] : "N/a"),
                JobTitle = c.JobTitle,
                Organisation = (c.ParentCustomerId != null ? c.ParentCustomerId.Name : "N/a"),
                joinedAsCode = c.py3_SOLACEMemberJoinedAs,
                JoinedAs = (c.FormattedValues != null && c.FormattedValues.Contains("py3_solacememberjoinedas") ? c.FormattedValues["py3_solacememberjoinedas"] : "N/a"),
                Expertise = (c.py3_SOLACEMemberAreasofExpertise != null && c.py3_SOLACEMemberAreasofExpertise.Trim() != String.Empty ? c.py3_SOLACEMemberAreasofExpertise : "N/a")
            }
        );

I then bind this to a datalist as an array, which all works fine.

However I want to be able to limit the results to a value selected from a dropdownlist, and expected the following to work:

        var context = new XrmServiceContext();
    var contacts1 =
        (
            from c in context.ContactSet
            join m in context.py3_membershipSet on c.ContactId equals m.py3_Member.Id
            where m.statuscode.Value == 1 &&
                c.FormattedValues["py3_brancharea"] == ddlBranchTags.SelectedItem.Value


            orderby c.LastName
            select new
            {
                ContactId = c.ContactId,
                FirstName = c.FirstName,
                LastName = c.LastName,
                BranchCode = c.py3_BranchArea,
                Branch = (c.FormattedValues != null && c.FormattedValues.Contains("py3_brancharea") ? c.FormattedValues["py3_brancharea"] : "N/a"),
                JobTitle = c.JobTitle,
                Organisation = (c.ParentCustomerId != null ? c.ParentCustomerId.Name : "N/a"),
                joinedAsCode = c.py3_SOLACEMemberJoinedAs,
                JoinedAs = (c.FormattedValues != null && c.FormattedValues.Contains("py3_solacememberjoinedas") ? c.FormattedValues["py3_solacememberjoinedas"] : "N/a"),
                Expertise = (c.py3_SOLACEMemberAreasofExpertise != null && c.py3_SOLACEMemberAreasofExpertise.Trim() != String.Empty ? c.py3_SOLACEMemberAreasofExpertise : "N/a")
            }
        );

However, this throws the following error:

Invalid 'where' condition. An entity member is invoking an invalid property or method.

Its the same even if I hard code the branchtag criteria rather than going of the DDL value. Ive also tried doing a select on the contacts1 set eg:

var results = contacts1.select(c=> c.BranchTag == ddlBranchTags.SelectedItem.Value

But that throws the same error.

If I remove the branchTag where clause it works as expected. I think its fair to assume that Ive gone wayward with this, so any useful / constructive pointers (for a LINQ newb) would be really appreciated. Thanks.

Phill Healey
  • 3,084
  • 2
  • 33
  • 67
  • See if http://stackoverflow.com/questions/10820663/error-within-where-statement-in-linq/10821137#10821137 fixes this. – Peter Majeed May 01 '13 at 20:50
  • @PeterMajeed No it doesnt. The issue here appears to be with the FormattedValues. – Phill Healey May 01 '13 at 21:16
  • @PeterMajeed You were correct it does! I was getting my self so confused with what I was trying to do, that I wasn't running the correct query. Anyway, brain switched on and engaged, and using the double 'where' actually fixes the problem. Such a simple solution. If you make and answer I'll happily mark you as the correct answer. – Phill Healey May 01 '13 at 22:22
  • no problem! Since this is actually a common issue with Dynamics' linq provider, I'll personally vote to close this question as a duplicate of the one I linked so that more traffic funnels there, but feel free to add your own answer here. – Peter Majeed May 02 '13 at 00:09

4 Answers4

5

LINQ-to-CRM is actually fairly limited in terms of the expression forms it can support. If you consider what it's doing behind the scenes – taking your entire LINQ expression and translating it into a CRM QueryExpression – this makes more sense.

Basically, it's not valid to expect to be able to embed arbitrary C# into your query (even if it compiles), as it's not possible to translate all code into a CRM query. The query provider could hypothetically be smart enough to determine what it can submit as a query to CRM, and what code it then has to execute client-side to get your final desired results, but that's generally a pretty hard problem – one which the LINQ provider does not attempt to solve.

In Where clauses specifically, the LINQ provider scans the expressions for basic forms of <attribute name accessor> <simple comparison> <value>. It understands early-bound codegen attribute accessors (entity.Attribute_name), indexer access (entity["attribute_name"]), and entity.GetAttributeValue<T>("attribute_name") access. Those are the only things you can use on the left side of a comparision in a Where clause (or in an OrderBy clause). In your second query, you're accessing FormattedValues, which the query provider doesn't understand/support.

An additional limitation is that a single Where clause can only address one "target" entity. So the case where you have filters on both "m" and "c" in a single predicate is not supported. I'm less clear on the reasons for this limitation, but that's how it is. You can still accomplish the equivalent filter by splitting the two conditions (joined by an && in your current query) into two separate Where clauses.

It's natural to want to write LINQ-to-CRM queries as though you are using LINQ-to-Objects, but doing so will generally be frustrating. LINQ-to-CRM is somewhat limited, but many of those limitations are inherited from the underlying CRM SDK query systems it's built upon. It's still a decent API for writing CRM queries, as long as you keep the limitations in mind.

If you're writing a query that is going to require you to execute some arbitrary code to filter/order/map your results, the general technique that should be used is to separate your query into two parts: the query that should be submitted to CRM, and your aribitrary code transformation. You write the most data-efficient query you can for getting your CRM data, force evaluation of the query using something like ToList or ToArray (as LINQ is otherwise lazily-evaluated), and then you proceed with your code on the results of that evaluation.

  • I just tried running the following code, but it gave this error: Object of type 'Xrm.Contact' cannot be converted to type '<>f__AnonymousTyped`2[System.Nullable`1[System.Int32],System.String]' – Phill Healey May 01 '13 at 21:01
  • Ive updated my question with the details of what I think you were saying, and the result I had. – Phill Healey May 01 '13 at 21:17
1

This looks to be a limitation of the CRM LINQ Provider.

where: The left side of the clause must be an attribute name and the right side of the clause must be a value. You cannot set the left side to a constant. Both the sides of the clause cannot be constants. Supports the String functions Contains, StartsWith, EndsWith, and Equals.

(Emphasis mine)

It probably has to do with indexing into the FormattedValues property of your Contact Set. You can try using a let clause and see if that helps.

EDIT: Here's an example of how to use let. I'm not sure if this will actually help - I can't test it at the moment.

from c in context.ContactSet
let formattedValue = c.FormattedValues["py3_brancharea"]
join m in context.py3_membershipSet on c.ContactId equals m.py3_Member.Id
where m.statuscode.Value == 1 &&
    formattedValue == ddlBranchTags.SelectedItem.Value

Notice how I let formattedValue be the value from c.FormattedValue["py3_brancharea"] and then use that in the where clause. It might get you past the limitation.

Dave Zych
  • 21,581
  • 7
  • 51
  • 66
  • Thanks Dave, maybe its because Im tired but I cant get my head around how to make use of the let in this scenario. – Phill Healey Apr 30 '13 at 20:22
  • Dave! Thanks for that, its a great help & really appreciated. Ill give it a try in a minute. Thanks! – Phill Healey Apr 30 '13 at 20:41
  • Ok well, its not thorwing the same error, which I guess is a good thing. Om now getting: The method 'Join' cannot follow the method 'Select' or is not supported. Try writing the query in terms of supported methods or call the 'AsEnumerable' or 'ToList' method before calling unsupported methods. – Phill Healey Apr 30 '13 at 20:46
  • Purely as a guess, I put the join clause above the let line and this then threw a slightly different error: The method 'Where' cannot follow the method 'Select' or is not supported. Try writing the query in terms of supported methods or call the 'AsEnumerable' or 'ToList' method before calling unsupported methods. – Phill Healey Apr 30 '13 at 20:51
  • 1
    Looks like it's another limitation of the `CRM LINQ Provider`, as explained [here](http://stackoverflow.com/a/16175269/1630665). This is getting out of my realm of expertise, I don't think I can help anymore... – Dave Zych Apr 30 '13 at 21:10
  • Thanks Dave. Yeah this is proving to be a lot of hassle for what should be fairly straightforward. Thanks for your time & effort though. Its appreciated. – Phill Healey Apr 30 '13 at 21:48
0

I believe you can get around this issue by using method syntax for where instead of query syntax.

var context = new XrmServiceContext();
var contacts1 =
(
    from c in context.ContactSet.Where(c => c.FormattedValues["py3_brancharea"] == ddlBranchTags.SelectedItem.Value)
    join m in context.py3_membershipSet.Where(m => m.statuscode.Value == 1)
    on c.ContactId equals m.py3_Member.Id
    // ...
);

More insight on using method syntax. If you prefer a solution that lets you use query syntax, you can use LINQKit as explained here. As an aside, the LINQKit answer mentions that join gets performed on the client side, so you might want to change it to a where clause.

Community
  • 1
  • 1
Risky Martin
  • 2,491
  • 2
  • 15
  • 16
  • I just tried this and it returns the following error: System.NotSupportedException: Invalid 'where' condition. An entity member is invoking an invalid property or method. – Phill Healey May 01 '13 at 20:45
0

Based on @Peter Majeed's answer, I solved my problem by using the double where clause:

var contacts =
            (
                from c in context.ContactSet
                join m in context.py3_membershipSet on c.ContactId equals m.py3_Member.Id
                where m.statuscode.Value == 1
                where ((c.FirstName != null && c.FirstName == searchTerm) || (c.LastName!=null && c.LastName == searchTerm) || (c.FullName != null && c.FullName == searchTerm))  
                orderby c.LastName
                select new
                {
                    ContactId = c.ContactId,
                    FirstName = c.FirstName,
                    LastName = c.LastName,
                    BranchCode = c.py3_BranchArea,
                    Branch = (c.FormattedValues != null && c.FormattedValues.Contains("py3_brancharea") ? c.FormattedValues["py3_brancharea"] : "N/a"),
                    JobTitle = c.JobTitle,
                    Organisation = (c.ParentCustomerId != null ? c.ParentCustomerId.Name : "N/a"),
                    joinedAsCode = c.py3_SOLACEMemberJoinedAs,
                    JoinedAs = (c.FormattedValues != null && c.FormattedValues.Contains("py3_solacememberjoinedas") ? c.FormattedValues["py3_solacememberjoinedas"] : "N/a"),
                    Expertise = (c.py3_SOLACEMemberAreasofExpertise != null && c.py3_SOLACEMemberAreasofExpertise.Trim() != String.Empty ? c.py3_SOLACEMemberAreasofExpertise : "N/a"),
                    Title = c.Salutation
                }
            );

Essentially the answer is to split each logical 'chunk' of the query with its own where clause. It would appear this allows LINQ to make one query based on the first 'where' clause and then do a secondary filter on that via the second 'where' clause.

Phill Healey
  • 3,084
  • 2
  • 33
  • 67