views:

191

answers:

2

I asked a question about this previously but my database structure has changed, and while it made other things simpler, now this part is more complicated. Here is the previous question.

At the time, my EF Context had a UsersProjects object because there were other properties. Now that I've simplified that table, it is just the keys, so all my EF context knows about is Users and Projects and the M2M relationship between them. There is no more UsersProjects as far as EF knows.

So my goal is to say "show me all the users who are working on projects with me."

in SQL, this would go something like:

SELECT * FROM Users INNER JOIN UsersProjects ON Users.ID=UsersProjects.UserID
WHERE ProjectID IN (SELECT ProjectID FROM UsersProjects WHERE UserID=@UserID)

and I started in EF with something like this:

            var myProjects =
                (from p in edmx.Projects
                 where p.Users.Contains(edmx.Users.FirstOrDefault(u => u.Email == UserEmail))
                 orderby p.Name
                 select p).ToList();

            var associatedUsers =
                (from u in edmx.Users
                 where myProjects.Contains(?????????)
                 //where myProjects.Any(????????)
                 select u);

The trick is finding what to put in the ????????. Anyone help here?

A: 
var me = context
    .Users
    .First(user => user.Email = "[email protected]");

// Note that there is no call to ToList() or AsEnumerable().
var myProjects = context
    .Projects
    .Where(project => project.Users.Contains(me));

var associatedUsers = context
    .Users
    .Where(user => myProjects.Any(project => user.Project.Contains(project)));

But there are several other possible solutions. For example

var associatedUsers = myProjects
    .SelectMany(project => project.Users)
    .Distinct();

which I would prefer.

Further note that it is much easier to obtain myProjects using a navigation property instead of using Contains().

var myProjects = me.Projects;
Daniel Brückner
Nice. Makes sense, so I'm marking it as the answer, but when I plugged it in, I got some interesting errors that I had to work around. See my post below...
Jorin
A: 

Daniel, I tried what you had and ran into some issues. Can you explain what these errors mean?

I tried:

        // Doesn't work.
        using (var edmx = new MayflyEntities())
        {
            var me = edmx.Users.First(user => user.Email == UserEmail);
            var myProjects = edmx.Projects.Where(project => project.Users.Contains(me));
            var associatedUsers = myProjects.SelectMany(project => project.Users).Distinct();
        }

but got the two following exceptions:

Unable to create a constant value of type 'DomainModel.User'. Only primitive types ('such as Int32, String, and Guid') are supported in this context.

and

The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.

So, I moved some things around and this works fine, but now I'm curious as to why? In SQL Profiler, it all executes in one query, so why does it show that the context has been disposed? Also, why can it not use the me object instead of the lambda?

        // Works fine
        var edmx = new MayflyEntities();
        var myProjects = edmx.Projects.Where(project => project.Users.Contains(edmx.Users.First(user => user.Email == UserEmail)));
        var associatedUsers = myProjects.SelectMany(project => project.Users).Distinct();
Jorin
That's an oversight of me. The Entity Framework can only handle primitive type constants. Instead of project.Users.Contains(me) one has to use project.Users.Any(user => user.Id == me.Id). I am not sure why the Entity Framework does not translate queries involving entities to queries involving the key of the entity (but it may be due to handling of database generated keys). LINQ to SQL for example supports queries like project.Users.Contains(me).
Daniel Brückner
Why you got the ObjectDisposedException is hard to tell but I am quite sure it was due to a bug in your code.
Daniel Brückner
edmx.Users.Where(user => user.Email == userEmail).SelectMany(user => user.Projects) is another suggestion to obtain myProjects. This avoids nesting lambda expression three levels deep and is in my opinion much more readable.
Daniel Brückner
I agree. Much cleaner. I'll work on why my context is disposing before I'm done with it...Thanks!
Jorin