3
votes

I'm using code-first Entity Framework with a basic context that just consists of standard IDbSet collections where T is just a POCO class. On my context, I've disabled lazy loading. While there are "navigation properties" in my model classes, I've removed the virtual keyword from them.

The "Get All" methods in the repository do some custom filtering to ensure that the current user only sees the data that they own unless they are an administrator. I'm having a particular problem where I'm logged in as an administrator that also is associated with some records. Since the entity that I'm logged in as is loaded in the context, even though I have lazy loading disabled, virtual removed and am not using Include or Load, the objects in the results that have an association to my profile have the navigation property set automatically.

This is not code from my project, just an example to show the idea of what I'm doing. It probably has typos and syntax errors.

public class Record
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public Owner Owner { get; set; } //No virtual keyword
    public Guid OwnerId { get; set; }
}

public class Owner
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public Collection<Record> Records { get; set; } //No virtual keyword
}

public class Context : DbContext
{
    IDbSet<Owner> Owners { get; set; }
    IDbSet<Record> Records { get; set; }
    public static Context Create()
    {
        Context context = new Context();
        context.Configuration.LazyLoadingEnabled = false; //Lazy loading disabled
        return context;
    }
}

public class Repository
{
    private Context Context { get; set; }
    public Owner CurrentOwner { get; private set; }
    public Repository()
    {
        Context = Context.Create();

        //Code here to get the application user and look up an associated "owner"
        //entity if the user is an "owner" (they could just be an administrator)
        //but the GetCurrentOwnerOrNull uses the Context to find the user
        CurrentOwner = GetCurrentOwnerOrNull();
    }

    public IQueryable<Record> GetRecords(bool asAdmin)
    {
        IQueryable<Record> records = Context.Records; //Not including or loading Owner
        if (asAdmin)
        {
           //Verify that the application user is an admin and throw exception otherwise
        }
        else
        {
            if (CurrentOwner == null)
            {
                //Throw a security exception
            }
            records = records.Where(r => r.OwnerId == CurrentOwner.Id);
        }
        return records;
    }
}

So again, the problem with the above is that if I was to run that code as an Owner, whether administrator or not, then those Records that I Own will have the Owner property set instead of null. I want the entity framework to get out of my business and not automatically set this. It's causing problems downstream, especially when running the code as an administrator and an owner, so you get some records back with Owner = null and some with Owner set. It's annoying. Make it stop for me please.

1
As an update, I just tested and if in the code to GetCurrentOwnerOrNull I create a new context to do that querying with and then dispose of it, it gets rid of this problem... but I'm leaving the question open for now in case someone knows of another way to simply turn the "feature" off in Entity Framework.Dewey Vozel
Do you need your contexts to hold onto these object in it's cache?Erik Philips

1 Answers

4
votes

This bug is actually a feature. Entity Framework will automagically wire-up associations within the same context even if the entities are loaded independent of each other.

Lets assume the following:

public class Person
{
  public Person()
  {
    this.Pets = new List<Pet>();
  }

  public int Id { get; set; }
  public string Name { get; set; }

  public virtual ICollection<Pet> Pets { get; set; }
}

public class Pet
{
  public int Id { get; set; }
  public string Name { get; set; }
  public int PersonId { get; set; }

  public virtual Person Owner { get; set; }
}

Lets also assume that these are properly associated DB-First or with Attributes/Fluent API code-first.

Database:

Persons
Id Name
1  Erik Philips

Pets
Id Name PersonId
1  Joe  1

Here is what will happen:

var pet = DbContext.Pets.FirstOrDefault(id => id == 1);
var person = DbContext.Persons.FirstOrDefault(id => id == 1);

Assert.AreEqual(person.Pets.Count(), 1);
Assert.IsNotNull(pet.Person);
Assert.AreEqual(pet.Person, person);

The reason this can occurs is because the context will hold onto the objects in it's cache. If you don't care about the context holding onto these objects, you will need to use AsNoTracking().