Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is DbSet<>.Local something to use with special care?

For a few days now, I have been struggling with retrieving my entities from a repository (DbContext).

I am trying to save all the entities in an atomic action. Thus, different entities together represent something of value to me. If all the entities are 'valid', then I can save them all to the database. Entity 'a' is already stored in my repository, and needs to be retrieved to 'validate' entity 'b'.

That's where the problem arises. My repository relies on the DbSet<TEntity> class which works great with Linq2Sql (Include() navigation properties e.g.). But, the DbSet<TEntity> does not contain entities that are in the 'added' state.

So I have (as far as I know) two options:

  • Use the ChangeTracker to see which entities are available and query them into a set based on their EntityState.
  • Use the DbSet<TEntity>.Local property.

The ChangeTracker seems to involve some extra hard work to get it working in a way such that I can use Linq2Sql to Include() navigation properties e.g.

The DbSet<TEntity>.Local seems a bit weird to me. It might just be the name. I just read something that it is not performing very well (slower than DbSet<> itself). Not sure if that is a false statement.

Could somebody with significant EntityFramework experience shine some light on this? What's the 'wise' path to follow? Or am I seeing ghosts and should I always use the .Local property?

Update with code examples:


An example of what goes wrong

    public void AddAndRetrieveUncommittedTenant()
    {
        _tenantRepository = new TenantRepository(new TenantApplicationTestContext());

        const string tenantName = "testtenant";

        // Create the tenant, but not call `SaveChanges` yet until all entities are validated 
        _tenantRepository.Create(tenantName);

        //
        // Some other code
        //

        var tenant = _tenantRepository.GetTenants().FirstOrDefault(entity => entity.Name.Equals(tenantName));

        // The tenant will be null, because I did not call save changes yet,
        // and the implementation of the Repository uses a DbSet<TEntity>
        // instead of the DbSet<TEntity>.Local.
        Assert.IsNotNull(tenant);

        // Can I safely use DbSet<TEntity>.Local ? Or should I play 
        // around with DbContext.ChangeTracker instead?
    }

An example of how I want to use my Repository

In my Repository I have this method:

    public IQueryable<TEntity> GetAll()
    {
        return Context.Set<TEntity>().AsQueryable();
    }

Which I use in business code in this fashion:

    public List<Case> GetCasesForUser(User user)
    {
        return _repository.GetAll().
            Where(@case => @case.Owner.EmailAddress.Equals(user.EmailAddress)).
            Include(@case => @case.Type).
            Include(@case => @case.Owner).
            ToList();
    }

That is mainly the reason why I prefer to stick to DbSet like variables. I need the flexibility to Include navigation properties. If I use the ChangeTracker I retrieve the entities in a List, which does not allow me to lazy load related entities at a later point in time.

If this is close to incomprehensible bullsh*t, then please let me know so that I can improve the question. I desperately need an answer.

Thx a lot in advance!

like image 372
bas Avatar asked Feb 26 '13 16:02

bas


People also ask

What is DbSet local?

The Local property of DbSet provides simple access to the entities of the set that are currently being tracked by the context and have not been marked as Deleted. Accessing the Local property never causes a query to be sent to the database. This means that it is usually used after a query has already been performed.

What is DbSet used for?

A DbSet represents the collection of all entities in the context, or that can be queried from the database, of a given type. DbSet objects are created from a DbContext using the DbContext.

What is difference between DbSet and DbContext?

Intuitively, a DbContext corresponds to your database (or a collection of tables and views in your database) whereas a DbSet corresponds to a table or view in your database. So it makes perfect sense that you will get a combination of both!

What is local in Entity Framework?

As you can see in the above output, local keeps track of entities whose state is Added, Modified or Unchanged where as DbSet collection contains all the entities whose state is Deleted, Modified or Unchanged.


2 Answers

If you want to be able to 'easily' issue a query against the DbSet and have it find newly created items, then you will need to call SaveChanges() after each entity is created. If you are using a 'unit of work' style approach to working with persistent entities, this is actually not problematic because you can have the unit of work wrap all actions within the UoW as a DB transaction (i.e. create a new TransactionScope when the UoW is created, and call Commit() on it when the UoW completed). With this structure, the changes are sent to the DB, and will be visible to DbSet, but not visible to other UoWs (modulo whatever isolation level you use).

If you don't want the overhead of this, then you need to modify your code to make use of Local at appropriate times (which may involve looking at Local, and then issuing a query against the DbSet if you didn't find what you were looking for). The Find() method on DbSet can also be quite helpful in these situations. It will find an entity by primary key in either Local or the DB. So if you only need to locate items by primary key, this is pretty convenient (and has performance advantages as well).

like image 161
Terry Coatta Avatar answered Sep 17 '22 12:09

Terry Coatta


As mentioned by Terry Coatta, the best approach if you don't want to save the records first would be checking both sources.

For example:

public Person LookupPerson(string emailAddress, DateTime effectiveDate)
{
    Expression<Func<Person, bool>> criteria = 
        p =>
            p.EmailAddress == emailAddress &&
            p.EffectiveDate == effectiveDate;

    return LookupPerson(_context.ObjectSet<Person>.Local.AsQueryable(), criteria) ?? // Search local
           LookupPerson(_context.ObjectSet<Person>.AsQueryable(), criteria); // Search database
}

private Person LookupPerson(IQueryable<Person> source, Expression<Func<Person, bool>> predicate)
{
    return source.FirstOrDefault(predicate);
}
like image 33
AndreFeijo Avatar answered Sep 19 '22 12:09

AndreFeijo