Quantcast
Channel: Development With A Dot
Viewing all articles
Browse latest Browse all 404

Implementing Missing Features in Entity Framework Core – Part 6: Lazy Loading

$
0
0

This will be the sixth post in my series of posts about bringing the features that were present in Entity Framework pre-Core into EF Core. The others are:

  • Part 1: Introduction, Find, Getting an Entity’s Id Programmatically, Reload, Local, Evict

  • Part 2: Explicit Loading

  • Part 3: Validations

  • Part 4: Conventions

  • Part 5: Getting the SQL for a Query

As you may know, the second major version of Entity Framework Core, 1.1, was released recently, however, some of the features that used to be in the non-Core versions still didn’t make it. One of these features is lazy loading of collections, and I set out to implement it… or, any way, something that I could use instead of it! Smile

Here’s what I came up with. First, let’s define a class that will act as a proxy to the collection to be loaded. I called it CollectionProxy<T>, and it goes like this:

internalsealedclass CollectionProxy<T> : IList<T> where T : class
{
privatebool _loaded;
privatebool _loading;
privatereadonly DbContext _ctx;
privatereadonlystring _collectionName;
privatereadonlyobject _parent;
privatereadonly List<T> _entries = new List<T>();

public CollectionProxy(DbContext ctx, object parent, string collectionName)
{
this._ctx = ctx;
this._parent = parent;
this._collectionName = collectionName;
}

privatevoid EnsureLoaded()
{
if (this._loaded == false)
{
if (this._loading == true)
{
return;
}

this._loading = true;

var entries = this
._ctx
.Entry(this._parent)
.Collection(this._collectionName)
.Query()
.OfType<T>()
.ToList();

this._entries.Clear();

foreach (var entry in entries)
{
this._entries.Add(entry);
}

this._loaded = true;
this._loading = false;
}
}

IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
this.EnsureLoaded();

returnthis._entries.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return (thisas ICollection<T>).GetEnumerator();
}

int ICollection<T>.Count
{
get
{
this.EnsureLoaded();
returnthis._entries.Count;
}
}

bool ICollection<T>.IsReadOnly
{
get
{
returnfalse;
}
}

void ICollection<T>.Add(T item)
{
this.EnsureLoaded();
this._entries.Add(item);
}

void ICollection<T>.Clear()
{
this.EnsureLoaded();
this._entries.Clear();
}

bool ICollection<T>.Contains(T item)
{
this.EnsureLoaded();
returnthis._entries.Contains(item);
}

void ICollection<T>.CopyTo(T[] array, int arrayIndex)
{
this.EnsureLoaded();
this._entries.CopyTo(array, arrayIndex);
}

bool ICollection<T>.Remove(T item)
{
this.EnsureLoaded();
returnthis._entries.Remove(item);
}

T IList<T>.this[int index]
{
get
{
this.EnsureLoaded();
returnthis._entries[index];
}

set
{
this.EnsureLoaded();
this._entries[index] = value;
}
}

int IList<T>.IndexOf(T item)
{
this.EnsureLoaded();
returnthis._entries.IndexOf(item);
}

void IList<T>.Insert(int index, T item)
{
this.EnsureLoaded();
this._entries.Insert(index, item);
}

void IList<T>.RemoveAt(int index)
{
this.EnsureLoaded();
this._entries.RemoveAt(index);
}

publicoverridestring ToString()
{
this.EnsureLoaded();
returnthis._entries.ToString();
}

publicoverrideint GetHashCode()
{
this.EnsureLoaded();
returnthis._entries.GetHashCode();
}
}

You can see that, in order to be as compliant as possible, I made it implement IList<T>; this way, it can be easily compared and switched with, for example, ICollection<T> and, of course, the mother of all collections, IEnumerable<T>. How it works is simple:

  1. It receives in its constructor a pointer to a DbContext, the collection’s parent, and the collection-to-be-made-lazy’s name;
  2. There is an EnsureLoaded method that essentially checks if the collection has already been loaded, and, if not the case, does so, through the new (in EF Core 1.1) explicit loading API; it populates an internal list with the loaded collection’s items;
  3. Inner fields _loading and _loaded act as defenses to prevent the collection to be loaded twice, or to enter an infinite loop (stack overflow);
  4. I implemented all inherited methods and properties as explicit implementations, but there was no need for that, just a personal preference; all of them ensure that the collection is loaded (EnsureLoaded) before delegating to its internal field list;
  5. ToString and GetHashCode delegate to the internal list as well.


I created as well an extension method to make it’s usage more simple:

publicstaticclass CollectionExtensions
{
publicstaticvoid Wrap<TParent, TChild>(this DbContext ctx, TParent parent, Expression<Func<TParent, IEnumerable<TChild>>> collection) where TParent : classwhere TChild : class
{
var prop = ((collection.Body as MemberExpression).Member as PropertyInfo);
var propertyName = prop.Name;

prop.SetValue(parent, new CollectionProxy<TChild>(ctx, parent, propertyName));
}
}

As you can see, I kept it very simple – no null/type checking or whatever, that is left to you, dear reader, as an exercise! Winking smile

Finally, here’s how to use it:

using (var ctx = new MyContext())
{
var parentEntity = ctx.MyParentEntities.First();

ctx.Wrap(parentEntity, x => x.MyChildren); //sets up the proxy collection

var childEntitiesCount = parentEntity.MyChildren.Count(); //forces loading

foreach (var child in parentEntity.MyChildren) //already loaded, so iterate in memory
{
child.ToString();
}
}

Hope you like it! Let me know your thoughts!






Viewing all articles
Browse latest Browse all 404

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>