2

I have a User that is assigned to a Client. When pulling out the User object, I get the Client object as part of it. Simple.

This works as it should when logging in. The User object has a Client, no matter who I log in as.

However, using the exact same method to get the User as when logging in, to edit it via the admin menus, the Client is sometimes null.

I say sometimes:

1) In Firefox - When attempting to view the details of most, but not all, users (and myself), the Client attached to the User will be null. Only a couple of the Users will be viewable due to the Client actually existing.

2) In Chrome - All users (EXCEPT myself) are visible. Only when attempting to view my own user will the Client be null.

I don't understand; Both browsers are simply going to the same URLs, i.e. /Users/EditGet/28 and even using two different methods (GetById and GetByUserName) it provides the same results - though admittedly both make use of the base Get function:

Edit: The BaseService class together rather over edits.

internal CustomContext context;
internal DbSet<TEntity> dbSet;

public BaseService(CustomContext context)
{
    this.context = context;
    this.dbSet = context.Set<TEntity>();
}

public virtual IEnumerable<TEntity> Get(
    Expression<Func<TEntity, bool>> filter = null,
    Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
    string includeProperties = "")
{
    IQueryable<TEntity> query = dbSet.Where(e => !e.Deleted);

    if (filter != null)
    {
        query = query.Where(filter);
    }

    foreach (var includeProperty in includeProperties.Split(new [] {','}, StringSplitOptions.RemoveEmptyEntries))
    {
        query = query.Include(includeProperty);
    }

    return orderBy != null ? orderBy(query).ToList() : query.ToList();
}

I'm not sure why the choice of browser should affect the results of a back-end query at all. Surely it should return the Client with the User regardless of what browser I use.

I assume perhaps a basic fault with the base Get method, but it doesn't explain the behaviour that I'm seeing...

If anyone could shed any light on this, I would be most appreciative.

Edit 2: CustomContext:

public class CustomContext : DbContext, ICustomContext
{
    public IDbSet<User> Users { get; set; }
    public IDbSet<Client> Clients { get; set; }
}
Krenom
  • 1,894
  • 1
  • 13
  • 20
  • 2
    Considering that all the code in question runs on the server i doubt that this is browser dependent - but it is as you observe so could it perhaps be that the `includeProperties` is a parameter passed to one of the mvc pages and is formatted incorrectly so that the split does not work? Put a break point on the `query = query.Include(includeProperty);` line and see if it's alway hit – Quinton Bernhardt Jun 05 '13 at 16:12
  • @QuintonBernhardt: Hits it every time. No issue with using the comma separation, it correctly hits it for each included property. Even the SQL generated by EF is correct (using SQL Profiler), and running the query returns everything it should, but the `Client` just isn't being generated and bound. – Krenom Jun 06 '13 at 07:51
  • Why is variable query of type `IQueryable' instead of the expected `DbQuery`? IQueryable does not have an `Include` method so i don't see how this does not give a compile error on the .Include line – Quinton Bernhardt Jun 06 '13 at 08:00
  • PTFC: paste the code - i see dbset is constructed elsewhere; please past at least where the dbset is constructed. Maybe therein lies the issue. – Quinton Bernhardt Jun 06 '13 at 11:28

2 Answers2

0

You may also want to change from a comma separated list of includes - which may be prone to formatting bugs; you could change it to param or an array like such:

        public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null,
                                                    Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
                                                    params string[] includeProperties) {

            var query = ((DbQuery<TEntity>)dbset);

            query = includeProperties.Aggregate(query, (q, s) => q.Include(s));

            query = query.Where(e => !e.IsDeleted);


            if (filter != null) {
                query = query.Where(filter);
            }                

            return orderBy != null ? orderBy(query).ToList() : query.ToList();
        }

I'm thinking that if it is browser dependent as you say there may be some string formatting issues... worth a shot anyway.

EDIT: change query back to DbQuery

EDIT2: Fix is to put call to Include before the Where as Include must be called on the DbSet or DbQuery. The reason it seemed random is that when there was no filter, then the includes were called on the DbSet/DbQuery where they were supposed to be.

Not easy to spot as the code had a call to IQueryable<T>.Include... which is not standard out of the box. The Include is on the DbSet or DbQuery.

Quinton Bernhardt
  • 4,773
  • 19
  • 28
  • Thing is, I'm not passing any strings through browsers. At all. The URL only has the Id of the `User` and that's it. The `Client` include is a simple const string "Client", no other entities are associated with the `User` object and as mentioned, it works when logging in. – Krenom Jun 06 '13 at 07:30
  • Yeah; well http://stackoverflow.com/a/2929109/1099260 answer has something that may be usefull. I was also wondering, your query variable is of type `IQuerable` (instead of the exepected `DbQuery`. IQueryable does not have an `inlcude` method. – Quinton Bernhardt Jun 06 '13 at 07:36
  • @Qinton Changing the includes to an array hasn't changed anything - but I think I'll keep it, I prefer it that way. As for why it's an `IQueryable`, I don't remember now. It was months ago that it was first put it and we've had no problems with it at all until recently. Not that any of us have changed it or any related code recently - the whole issue has us stumped at the moment. As a note, we are using EF5, if that makes a difference to our circumstances. – Krenom Jun 06 '13 at 09:54
  • Yeah, but your code should only compile if you have written an `Include` extension method yourself for IQueryable - this is normally for unit testing purposes.... change the type to `var query` or `DbQuery query` just as a try. Won't hurt. – Quinton Bernhardt Jun 06 '13 at 10:09
  • The `dbSet.Where...` returns an `IQueryable`, so using var seems pointless. Specifying `DbQuery` means all the `.Where`'s need to be cast. The result of which still does not change the outcome, unfortunately. – Krenom Jun 06 '13 at 10:19
  • @Krenom: Ha! you just solved your own problem - The call to include has to be BEFORE the where. So the reason why it seems random is that when there is no filter it'll work as the include is called first; else not. move the includes to before the fitler. – Quinton Bernhardt Jun 06 '13 at 10:22
  • But when pulling out the `User`, there is *always* a filter for the `Client`. Also, it's doing a `Where` on the initialization: `var query = dbSet.Where(e => !e.Deleted);` which surely defeats the purpose of moving the include merely above the rest of the filters? – Krenom Jun 06 '13 at 10:37
  • Where is the dbset being constructed? not in this method. Oh, my edit was bad the include must be before the inital where as well. see edit. – Quinton Bernhardt Jun 06 '13 at 10:58
  • [EF5](http://stackoverflow.com/questions/5256692/linq-to-entities-include-method-not-found) seems to have the `Include` extensions available for `IQueryable`s, which would explain why it works against your expectations :) – Krenom Jun 06 '13 at 12:19
0

I seems that the issue stems from the way we store the currently logged in User.

When attempting to retrieve another of that same User object, it then fails on also pulling out the Client.

Still not quite sure why this is. I've had a couple of hints that it has to do with the Object State Manager and tracking multiple objects - though the currently logged in User object isn't actually pulled from the DB...

What has fixed the issue, though, is do to a simple check to see if the User pulled out is the same as the currently logged in User and if the problem exists (the Client is null) then pull out the Client and attach it to the User object.

private User GetUser(long id)
{
    var user = Services.UserService.GetById(id);
    if (user.Client == null && CurrentUser.Id == user.Id)
        user.Client = Services.ClientService.GetByClient(CurrentUser.Client);

    return user;
}

Crude, and far from a decent answer as to how to properly work this problem out, but it works and all Users are fully editable once again.

Krenom
  • 1,894
  • 1
  • 13
  • 20