4

I am having an issue getting my LINQ query to output as I would like. I am unsure I am taking the right approach.

Tables:

I have two tables Contacts and Permissions with which I perform a LEFT OUTER JOIN.

Two Tables

Join Query:

from contact in Contacts
join permission in Permissions on contact.Id equals permission.ObjectId into permissionGrp
from p in permissionGrp.DefaultIfEmpty()
where (p==null && isAllowed) || (p!=null && /* ... further conditions */))
orderby /* ... ordering removed */
select new { contact, permission = p };

This matches the permissions to a contact where applicable, and null when no matching permission exists.

Joined

Desired

I don't wish to have duplicate contacts, I am only interested in the first Contact-Permission record. Like so:

Desired

Attempt:

So I assumed that I need to Group By my contact.Id and somehow select FirstOrDefault() on the permissions collection.

Approach

from contact in Contacts
join permission in Permissions on contact.Id equals permission.ObjectId into permissionGrp
from p in permissionGrp.DefaultIfEmpty()
where (p==null && isAllowed) || (p!=null && /* ... further conditions */))
orderby /* ... ordering removed */
group p by contact into contactPermissionsGrp
select new { contact = contactPermissionsGrp.Key, permission = contactPermissions.FirstOrDefault() };

Result:

Unfortunately this results in a NotSupportedException: Specific method is not supported.. But I am not sure if my approach is correct or a limitation of the LightSpeed ORM.

Any advise would be appreciated.

Scott
  • 21,211
  • 8
  • 65
  • 72

3 Answers3

1

Answer question with more questions

  • Is your query possible in raw SQL in the database you are targeting? If the answer is No then what hope have you of replicating it in LINQ?

I personally don't think that what you are trying to achieve is possible to do via LINQ against MySQL. It would be possible in MS SQL in raw T-SQL by defining an expression and attaching a RANK() column to it then doing a query on that expression.

I feel that your available solutions are:

  1. Find out how to write this query using raw SQL in your native dialect. Lightspeed will let you execute raw SQL and it will even (where you return enough columns) rehydrate that custom query into Entitys (however I don't think that is what you are after in this case).

  2. Give up on reducing the "duplicates" efficiently in the database. Pull the duplicates into memory and then reduce them in memory with LINQ queryies against the IEnumerable set that you get back.

  3. Change your database architecture so you can have a simpler query. Sometimes in situations like this I will find honour in having a column on the Contact table such as "MostSignificantPermssion". That has a number of advantages:

    • Simpler query to get the Contact and the ONE significant Permission record.
    • Makes it more obvious to other developers that one of the Permissions has a special significance.

Options!

Appendix - Example of implementing (1) in MS SQL

WITH LastUsagePerPerson AS (
    SELECT 
        ULE.PersonId, 
        ULE.[Device], 
        ULE.[AppVersion], 
        ULE.[CreatedOn], 
        ROW_NUMBER() OVER(PARTITION BY ULE.PersonId ORDER BY ULE.CreatedOn DESC) AS rk
    FROM [dbo].[UsageLogEntry] ULE
    )


SELECT 
     [FirstName]
    ,[LastName]
    ,[EmailAddress]
    ,[EmailAddressUnverified]     
    ,[MobileNumber]
    ,[MobileNumberUnverified]
    ,[LastDeviceUsed] = LastUsagePerPerson.Device
    ,[LastAppVersion] = LastUsagePerPerson.AppVersion
    ,[LastDeviceUsage] = LastUsagePerPerson.CreatedOn
    ,[LastLoggedInOn]

  FROM [dbo].[Person] P
    LEFT JOIN LastUsagePerPerson ON P.Id = LastUsagePerPerson.PersonId

WHERE rk = 1

ORDER BY [Id]
Jason Glover
  • 618
  • 5
  • 13
  • Thanks Jason, it is good advice. I will consider 1 and 2 and let you know the outcome. I don't think a 'MostSignificantPermission' will work, because the significance of a permission changes a lot depending on the multiple contexts the user is using the system under. – Scott Nov 27 '13 at 17:44
  • Yeah I didn't expect that would make sense in your situation. There are occasions where it does. ie. In my current project I maintain a FK to the Person's LastSuccessfulPayment and to their MostFrequentMerchant. While there is extra work to do maintaining these as FKs it makes the system overall more efficient and reduces the need for some massive indexes. – Jason Glover Nov 27 '13 at 22:22
0

I don't know what Lightspeed can or can't. Try to simplify you LINQ Query In EF i would do something like this.

from c in Contacts
let p = (from p in permission where p.ObjectId == c.Id select p).FirstOrDefault()
select new { ContactID = c.Id,
             Name = c.Name,
             Permission = p.PermissionId,
             Permitted = p.Permitted};
Ralf
  • 1,216
  • 10
  • 20
  • I can see where you're coming from and the LightSpeed ORM does support that, but it will do a query per contact record for this. It's very inefficient, which is why I am using the LEFT OUTER JOIN. – Scott Nov 26 '13 at 13:14
  • EF is clever enough to optimize this into a single sql (it uses an OUTER APPLY between contact and permission) and pressumably Lightspeed is equally clever. – Ralf Nov 26 '13 at 13:26
  • LightSpeed isn't translating into any OUTER APPLY because MySQL doesn't support it. So moving to EF would have the same problem. I am trying to setup nHibernate to see if it will support my query. – Scott Nov 26 '13 at 13:48
  • I tried your method using Entity Framework, and I get a `NotSupportedException` with message `Unable to create a constant value of type 'eftest.Permission'. Only primitive types or enumeration types are supported in this context` – Scott Nov 26 '13 at 19:06
  • Do you have used the p variable for something more than is shown in my example? As it is shown there is nothing that would need a constant value. – Ralf Nov 27 '13 at 09:09
  • I just copied your example. I hadn't attempted to make any modifications at this stage to it. – Scott Nov 27 '13 at 09:19
-2

I have understood what you are trying to get and I have solved your problem,just follow the below code what I did...

select * from contacts as a 

 left join permissions as b 

   on a.ContactId = b.ContactId

     group by a.ContactId ;

I have got the requeried result using the above code which you were trying to get.Just try that,your problem will be solved.

Abhik Dey
  • 403
  • 4
  • 12
  • This question is about LINQ not SQL. You have not "understood" what I am trying to do, and my problem is far more complex than a simple SQL join. – Scott Nov 27 '13 at 11:10
  • 1
    Agreed with @Scott, this answer totally misses the issue. – Jason Glover Nov 27 '13 at 17:34