views:

371

answers:

1

I have a WCF RIA Services app and a model with a UserRole type that contains a collection of UserPermission objects. I use .Include("UserPermission") in the domain service and when I debug it I've verified it definitely contains the UserPermission types before returning.

When I debug the Silverlight 3 client it returns the UserRoles but the UserPermission properties are all empty. These are the same UserRoles that show having UserPermissions on the service.

Since everything appears correct on the service and client, I'm focussing on the metadata class, but still can't find anything wrong.

[MetadataTypeAttribute(typeof(UserRole.UserRoleMetadata))]
public partial class UserRole
{
    internal sealed class UserRoleMetadata
    {
        public int RoleID;
        public string Name;

        [Include]
        [Association("UserPermissions", "RoleID", "PermissionID")]
        public EntityCollection<UserPermission> UserPermissions;
    }
}

Here's the domain service method:

public IEnumerable<UserRole> GetUserRoles()
{
    IEnumerable<UserRole> roles = this.ObjectContext.UserRole.Include("UserPermissions");
    return roles; // In debug, roles.First().UserPermissions.Count = 2 here
                  // For now, there is only one single role in the ObjectContext and it has
                  // two UserPermissions
}

Here's the Silverlight client method:

context.Load(context.GetUserRolesQuery(), loadOp =>
{
    IEnumerable<UserRole> roles = loadOp.Entities;
    // This should show 2, but shows 0:
    MessageBox.Show("Permissions loaded: " + roles.First().UserPermissions.Count.ToString());
}

Does anyone know of anything that might cause a loss of these included entities? I do this same thing in several other places and they work.

A: 

OK, solved it! I took a look at the serialized data being passed between the server and client using Fiddler and found that all the nested types were in fact being passed but the relationships between them weren't correct. After some tinkering, thinking, and online research, it turned out many-to-many relationships in the EF don't work as expected and if you're relying on an intermediary table to manage the relationships you need to include those tables in the model.

To get my app to work I did the following:

1) Went into the DB and updated the intermediary tables (the ones that manage the many-to-many relationships) by adding a primary key identity column. Once this is added the EF-generated model will include these tables when updated.

2) To wipe out my existing model completely, I used the trick of renaming my tables in the DB, updating the model, renaming the DB tables back, and then updating again and selecting the tables I want added. This might be overkill but due to issues I've had in the past I find it's the best way to ensure the tables are completely clean.

3) I had to add all the metadata classes for the new intermediate types as well as update the metadata classes for the existing types. I wrote a VS snippet (type 'meta') for adding these classes a little quicker. You can download the installer here.

4) In addition to adding/updating all the existing metadata classes, you need to ensure all your 'AssociationAttributes' use the intermediary types and specify the foreign key properties:

[MetadataTypeAttribute(typeof(UserPermissionMembers.UserPermissionMembersMetadata))]
public partial class UserPermissionMembers
{
    internal sealed class UserPermissionMembersMetadata
    {
        private UserPermissionMembersMetadata()
        {}

        public int ID;
        public UserRole UserRole;

        [Include]
        [Association("UserPermission", "fkPermissionID", "PermissionID", IsForeignKey = true)]
        public UserPermission UserPermission;
    }
}

5) I updated the domain service method with the new structure:

public IEnumerable<UserRole> GetUserRoles()
{
    IEnumerable<UserRole> roles = this.ObjectContext.UserRole.Include("UserPermissionMembers.UserPermission");
    return roles;
}

6) I updated the client method to utilize the new types.

context.Load(context.GetUserRolesQuery(), loadOp =>
{
    IEnumerable<UserRole> roles = loadOp.Entities;
    MessageBox.Show("Permissions loaded: " + roles.First().UserPermissionMembers.Count.ToString());
}

NOTE: Even knowing the problem it still took a while to properly configure all the AssociationAttributes so that they refer to the correct properties. If you're having issues, I suggest you double-check there.

This is a lot of pain for something that should be more elegant. I haven't looked at EF v4 yet but I hope it improves all this.

Nick Gotch