Tuesday, August 2, 2011

Entity Framework's NHibernate session.Load

public static class Helpers
{

    public static Ent LoadStub<Ent>(this DbContext db, object id) where Ent : class
    {
        string primaryKeyName = typeof(Ent).Name + "Id";
        return db.LoadStub<Ent>(primaryKeyName, id);
    }

    public static Ent LoadStub<Ent>(this DbContext db, string primaryKeyName, object id) where Ent: class
    {
        var cachedEnt = 
            db.ChangeTracker.Entries().Where(x => ObjectContext.GetObjectType(x.Entity.GetType()) == typeof(Ent)).SingleOrDefault(x =>
            {
                var entType = x.Entity.GetType();
                var value = entType.InvokeMember(primaryKeyName, System.Reflection.BindingFlags.GetProperty, null, x.Entity, new object[] { });

                return value.Equals(id);
            });

        if (cachedEnt != null)
        {
            return (Ent) cachedEnt.Entity;
        }
        else
        {
            Ent stub = (Ent) Activator.CreateInstance(typeof(Ent));

            
            typeof(Ent).InvokeMember(primaryKeyName, System.Reflection.BindingFlags.SetProperty, null, stub, new object[] { id });


            db.Entry(stub).State = EntityState.Unchanged;

            return stub;
        }

    }
}

So instead of using this pattern:
movie.Genres = movie.Genres ?? new List<Genre>();
movie.Genres.Clear();
foreach (int g in input.SelectedGenres)
{
    DbEntityEntry<Genre> cachedGenre = db.ChangeTracker.Entries<Genre>().SingleOrDefault(x => x.Entity.GenreId == g);

    Genre gx = null;
    if (cachedGenre != null)
        gx = cachedGenre.Entity;
    else
    {
        gx = new Genre { GenreId = g };
        db.Entry(gx).State = EntityState.Unchanged;
    }

    movie.Genres.Add(gx);
                       
}

We could now use this:
movie.Genres = movie.Genres ?? new List<Genre>();
movie.Genres.Clear();
foreach (int g in input.SelectedGenres)
    movie.Genres.Add(db.LoadStub<Genre>(g));


Sample use: http://www.ienablemuch.com/2011/07/using-checkbox-list-on-aspnet-mvc-with_16.html

1 comment:

  1. It should be noted that this works when we have a conventions:

    1) PrimaryKey is a single property
    2) Property carrying the primary key is XXXId if the entity is named XXX

    Also, it should be trivially possible to cache the reflected properties and generated names to reduce overhead. Really, this should be something in EF itself.

    ReplyDelete