views:

37

answers:

1

Not sure if I am using the correct term, but it's not child/parent since they aren't nested objects. In my application I've got a User, UserUserGroup, and UserGroup objects which are pretty standard, UserUserGroup is a linking object with corresponding IDs. I am using Linq-to-SQL so unforunately it doesn't used nested objects, but it still knows the relation.

Consider the following class and query where I am trying to return a list of Users that are associated with a specific UserGroup:

    Public Class UserDao
    Inherits EntityDao(Of User)

    Public Function getListOfUsersByUserGroupName(ByVal userGroupName As String) As IList(Of User)

        Dim userList As New List(Of User)

        If Not userGroupName Is Nothing Then
            Dim userGroupDao As New UserGroupDao()
            Dim userUserGroupDao As New UserUserGroupDao()

            Dim userGroup As New UserGroup()
            userGroup = userGroupDao.getOneByValueOfProperty("Name", userGroupName)

            If Not userGroup Is Nothing Then

                Dim userUserGroup As IQueryable(Of UserUserGroup) = userUserGroupDao.getListByValueOfProperty("UserGroupId", userGroup.Id)

                If Not userUserGroup Is Nothing Then
                    Dim userDao As New UserDao()
                    Dim user As New User()

                    For Each entry As UserUserGroup In userUserGroup
                        Dim result As UserUserGroup = entry
                        user = userDao.getOneByValueOfProperty("Id", result.Id)
                        userList.Add(user)
                    Next

                End If
            End If

        End If

        Return userList

    End Function

End Class

Is there anyway to make this better with less lines and more efficient? It seems incredibly inefficient for me to first get the ID of the supplied usergroup, then get the results in UserUserGroup where UserGroupId equals that, then get all the User Ids from that query and then for each of those Ids, query the User table to grab the object corresponding to that ID. It just seems bad for one 'result' I am running at least 3 queries, with each of them creating and disposings dataContexts. It seems like it would be taxing, especially on the for each loop if there was a ton of results returned.

+1  A: 

I think what you're looking for can be done relatively easily in LINQ to SQL, but the structure of your data layer is unexpected from the perspective of LINQ. I would expect, if you use the designer and have the relationships defined in the database, that you'd have entity sets and could use those. A more natural way using the data context, in say the Repository pattern, as the base for the query would probably work better. Essentially, what you want are any users who have any associations in which the set of groups that they are associated with contains the group with the name in question.

EDIT: I think what I'm actually saying is let LINQ be LINQ. LINQ is a data context-centric ORM (lightweight), and you need to think about using LINQ from a data context perspective to make it really work for you. If you stick with your previous, data-object centered perspective, I think you'll be disappointed in LINQ.

Example (in C#) -- this is not a full example on how to implement a Repository, just for illustrative purposes.

public class UserRepository : IDisposable
{
   private DataContext Context { get; set; }
   private bool DisposeContext { get; set; }

   public UserRepository() : this( null ) { }

   public UserRepository( DataContext context ) // manual constructor injection
   {
      this.Context = context ?? new MyDataContext();
      this.DisposeContext = context == null;
   }

   public IQueryable<User> GetListOfUsersByUserGroupName( string userGroupName )
   {     
      return Context.Users
                    .Where( u => u.UserUserGroups
                                  .Any( uug => uug.Groups
                                                  .Any( ug => ug.GroupName == userGroupName ) );
   }

   ...

   public void SaveChanges()
   {
        Context.SubmitChanges();
   }

   // insert canonical IDisposable pattern here and dispose of
   // the data context if needed, don't dispose if injected
}

Used as:

 using (var repository = new UserRepository())
 {
     var users = repository.GetListOfUsersByUserGroupName( "admin" );
     foreach (var admin in users)
     {
         SendMessage( admin, notification );
         admin.LastNotified = DateTime.Now;
     }
     repository.SaveChanges();
 }
tvanfosson
Amazing! So much more simple! Thanks! Also I am not sure what you mean by my structure of the data layer causing problems. The purpose of my UserDao seems to mimic the purpose of your UserRepository. I was just using other DAOs generic functions to move through the relation chain. But for more complex stuff like the above query, I wouldn't use the generic stuff anyway, that's just the only way I knew how to do it. One Question: Isn't using the same context across all methods bad? Since they represent multiple units of work. And how would you write a generic updated method then? Continued...
sah302
The method would have to have it's own data context, but your query for getting a result for an update would be using a different one woudln't it? I've read very bad things about trying to update something that wasn't retrieved using the same datacontext that is trying to update.
sah302
Oh one more thing: I was able to make it work with the following code: `Return db.Users.Where(Function(user) user.UserUserGroups.Any(Function(userUserGroup) userUserGroup.UserGroup.Name = userGroupName))` Didn't need the last any.
sah302
Sorry for the spam, but you seriously opened up so many possibilities for me hehe. Would there be any way to do this semi-generically in that I could provide the property name and as well as the value, otherwise I would have to do your a version of your above query for every property of every object.
sah302
@sah302 - Look at Dynamic LINQ: http://weblogs.asp.net/scottgu/archive/2008/01/07/dynamic-linq-part-1-using-the-linq-dynamic-query-library.aspx
tvanfosson
@sah302 - typically in the Repository pattern you would also use it in a unit of work fashion. I haven't shown it but it would typically implement IDisposable and dispose of the data context when done. Essentially, you'd create a repository in a using block, then do what you needed to do with it, and have it disposed at the end. I should have been more clear that the sample code omits a lot. I'll update it to make that more clear.
tvanfosson
I am actually using Dynamic Linq but it doesn't seem to allow the the string and parameterized usage on nested methods (like above), or on any .Any() method. It seems only usable in basic queries.
sah302