5

In a different post I asked for clarification when to use DbSet<TEntity>.Local. I did not get an answer yet, I think the question may contain too much information. After digging a little deeper I can ask a more specific question.

Hopefully you guys can explain which of the two options to choose in my scenario.

What do I want to achieve?

  1. I am trying to retrieve entities from my Repository. The entities are still in the 'added' state (i.e. I did not yet call the SaveChanges method). I need this, because I want to store the entities in one atomic action, so I must postpone the SaveChanges call until after I validated the complete model.

  2. Further more I need to conform to the IQueryable interface, because I have dependent code that uses the extension method Include on Queryable to lazy load navigation properties.

What did I try?

A load of things over the last few days, but it boiled down to the following two different approaches:

  • Retrieve entities that are in 'added' state via the DbSet<TEntity>.Local property
  • Retrieve entities that are in 'added' state via the DbContext.ChangeTracker

A code example to visualize the different approaches

    public void AddAndRetrieveUncommittedEntityFromDbContext()
    {
        Database.SetInitializer(new DropCreateDatabaseAlways<TestContext>());
        var testContext = new TestContext();

        // Initialize the entities and store them in the repository
        var tenant = new Tenant { Name = "test", Guid = Guid.NewGuid().ToString() };
        var user = new User { Name = "bas", EmailAddress = "bas@domain.com", Password = "password" };
        tenant.Users.Add(user);
        testContext.Tenants.Add(tenant);

        // NOTE: I did not call `SaveChanges` yet, but still I want to retrieve 
        // the tenant from my repository

        ///////////////////////////////////////////////
        // Alternative one, use the ChangeTracker... //
        ///////////////////////////////////////////////
        IEnumerable<DbEntityEntry<Tenant>> tenants = testContext.ChangeTracker.Entries<Tenant>();
        IQueryable<Tenant> query = tenants.Select(dbEntityEntry => dbEntityEntry.Entity).AsQueryable();
        Tenant storedTenant = query.
            Where(ent => ent.Name.Equals("test")).
            Include(ent => ent.Users).First();

        Assert.IsNotNull(storedTenant.Users.FirstOrDefault()); 
        // hurray, it passes

        ///////////////////////////////////////////////
        // Alternative two, use the 'Local' property //
        ///////////////////////////////////////////////
        IQueryable<Tenant> query2 = testContext.Tenants.Local.AsQueryable();
        Tenant storedTenant2 = query2.
            Where(ent => ent.Name.Equals("test")).
            Include(ent => ent.Users).First();

        Assert.IsNotNull(storedTenant2.Users.FirstOrDefault()); 
        // hurray, it passes

        ////////////////////////////////////////////////
        // For completeness => entity is not in DbSet //
        ////////////////////////////////////////////////

        IQueryable<Tenant> query3 = testContext.Tenants.AsQueryable();
        Tenant storedTenant3 = query3.
            Where(ent => ent.Name.Equals("test")).
            Include(ent => ent.Users).FirstOrDefault();

        Assert.IsNotNull(storedTenant3); 
        // Fails, because the entity is not yet available in the DbSet 'testContext.Tenants'
    }

The testContext is pretty straight forward:

public class TestContext : DbContext
{
    public DbSet<User> Users { get; set; }
    public DbSet<Tenant> Tenants { get; set; }

    public TestContext() : base("Test1") {}
}

The questions

  • What is the advised way to retrieve all entities (i.e. including entities in 'added' state)?
  • What are the pros and cons for DbSet<TEntity>.Local and ChangeTracker?

Many many thanks in advance!

Community
  • 1
  • 1
bas
  • 13,550
  • 20
  • 69
  • 146

1 Answers1

5

It really depends on what you want to do. None of your code specifically filters by DbEntityEntry.State so I assume you are certain that all entities that are tracked by the context are in the added state.

If you are not certain that all tracked entities are in the added state then ChangeTracker gives you the opportunity to filter DbEntityEntrys by EntityState.Added then get the reference to the actual entities to work on.

With Local you getting an ObservableCollection<T> of the entities themselves. If you are sure they are all in the added state then this is probably simplest. If you are not certain then you'd then have to enumerate over the collection and get the DbEntityEntry from the context to make sure:

foreach(var entity in collection)
{
  if(context.entry(entity).State == EntityState.Added)
  {
  //do stuff...
  }
}

If this is all to perform validation then there are other ways to go about it, some of which can be read here.

  • thx for you clear explanation. I am not looking for the set of 'added' entities, I am looking for 'all' entities that are known to my system. Something I'd expect to be returned by the `DbSet`. So I am not entirely sure if I have it at the right end now. The way I see it now, is that I **never** need the `DbSet` but I always need the `DbSet.Local`. But then again, doesn't everybody want to retrieve all entities known to their system? So why would I ever use `DbSet`. Am I making any sense or do I have it completely wrong? Thx a lot for you help!! +1 – bas Feb 27 '13 at 11:21
  • If you want the entities that are being tracked then you query the Local collection but obviously you still need DbSet for DB access. I get the feeling that your repository/context is too long lived and that's why you have this dilema between Local and db entities. If you are adding or modifying a bunch of entities do it in a single unit of work and be done with it, don't leave them hanging around while you move on to another routine. –  Feb 27 '13 at 12:37
  • Are there any guidelines that I can follow regarding the lifetime of repositories that you know of? – bas Feb 27 '13 at 15:34