Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

EF Core one to many relationships: ICollection or Hashset?

I am reading "Entity Framework Core in Action" by Jon P Smith. There it says:

enter image description here

Could you provide some examples when Hashet is a proper collection type? And why?

like image 707
Deivydas Voroneckis Avatar asked Feb 23 '19 10:02

Deivydas Voroneckis


Video Answer


1 Answers

UPDATE: I have just finished updating my book Entity Framework Core in Action and I did some performance tests which shows HashSet<T> is quicker if you are doing a normal query (i.e. without AsNoTracking in the query). That's because EF Core does something called Identity Resolution (read about this in one of my articles), which is slower with non-HashSet collections. So, if you have LOTs of entries in your collections, then HashSet<T> is better.

I personally use ICollection<T> for normal properties, just because its a well-known interface with minimal overheads, i.e. it is very slightly quicker to create an ICollection than a IList. You can of course use HashSet<T>, which is what EF Core uses, but I find HashSet's are a little harder to set than ICollection<T>, which takes a List<T>.

The one place you have to use HashSet<T> if you are using uninitialized backing field collections - see code below:

private HashSet<Review> _reviews;
public IEnumerable<Review> Reviews => _reviews?.ToList();

UPDATE: With EF Core 3 the limitation of having to use HashSet<T> for uninitialized collections has been removed (see this issue). You can use ICollection<T>, List<T> etc.

IEnumerable<T> is a special case, because it turns a collection into a read-only version, due to IEnumerable not having a Add or Remove method. Backing fields plus IEnumerable<T> (see code above) allows you to "lock down" a collection relationship so that it can only be changed from inside the class (see my article Domain-Driven Design in EF Core).

When I use backing field collections I leave them uninitialized, so they need to be HashSet<T>. This allows me to detect when I forgot to use .Include when loading an entity, e.g. if I loaded a book without .Include(p => p.Reviews) and then accessed the Reviews property I would get a null reference exception. This is just a safe way of programming.

If you initialise a backing field collection then it can be ICollection etc., but I don't recommend initialising a backing field collection because it can cause problems if you forget the Include and then add a item to the collection. In that case EF Core deletes any existing reviews and replaces them with the the new one you added. From EF Core's point of view its doing what you said, but its most likely NOT what you intended.

like image 107
Jon P Smith Avatar answered Nov 07 '22 11:11

Jon P Smith