Why Use Icollection and Not Ienumerable or List<T> on Many-Many/One-Many Relationships

Why use ICollection and not IEnumerable or List T on many-many/one-many relationships?

Usually what you choose will depend on which methods you need access to. In general - IEnumerable<> (MSDN: http://msdn.microsoft.com/en-us/library/system.collections.ienumerable.aspx) for a list of objects that only needs to be iterated through, ICollection<> (MSDN: http://msdn.microsoft.com/en-us/library/92t2ye13.aspx) for a list of objects that needs to be iterated through and modified, List<> for a list of objects that needs to be iterated through, modified, sorted, etc (See here for a full list: http://msdn.microsoft.com/en-us/library/6sh2ey19.aspx).

From a more specific standpoint, lazy loading comes in to play with choosing the type. By default, navigation properties in Entity Framework come with change tracking and are proxies. In order for the dynamic proxy to be created as a navigation property, the virtual type must implement ICollection.

A navigation property that represents the "many" end of a relationship must return a type that implements ICollection, where T is the type of the object at the other end of the relationship. -Requirements for Creating POCO ProxiesMSDN

More information on Defining and Managing RelationshipsMSDN

EF Core one to many relationships: ICollection or Hashset?

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.

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.

ICollection T Vs List T in Entity Framework

Entity Framework would use ICollection<T> because it needs to support Add operations, which are not part of the IEnumerable<T> interface.

Also note that you were using ICollection<T>, you were merely exposing it as the List<T> implementation. List<T> brings along with it IList<T>, ICollection<T>, and IEnumerable<T>.

As for your change, exposing via the interface is a good choice, despite List<T> working. The interface defines the contract but not the implementation. The implementation could change. In some instances, perhaps the implementation could be a HashSet<T>, for example. (This is a mindset you could use for more than just Entity Framework, by the way. A good object-oriented practice is to program towards the interface and not the implementation. Implementations can and will change.)

EF ICollection Vs List Vs IEnumerable Vs IQueryable

IQueryable:

  • Query isn't executed until you really iterate over the items, maybe by doing a .ToList() or a foreach. Which means you still can add filters, like a Where().
  • Extends IEnumerable

IEnumerable:

  • Forward-only list of items. You can't get at "item 4" without passing items 0-3.
  • Read-only list, you can't add to it or remove from it.
  • Still might use deferred execution (IQueryable is still an IEnumerable).

IList:

  • Random access to the full list
  • Probably entirely in memory (no deferred execution, but who knows what the exact class does that implements this?)
  • Supports adding and removing
  • Extends IEnumerable and ICollection

ICollection:

  • Is between IEnumerable and IList.
  • Extends IEnumerable

What is "best" depends on your requirements. Usually though an IEnumerable is "good enough" if you only want to display items. At least always use the generic variant.

EF Core - Unable to determine the relationship represented by navigation (One-to-Many)

If you specify the [ForeignKey] attribute, you need to do one of two things:

  • Add the attribute to the scalar property, and use the name of the navigation property; or
  • Add the attribute to the navigation property, and use the name of the scalar property.

So either:

[ForeignKey("Insurance")]
public int InsuranceId { get; set; }
public Insurance Insurance { get; set; }

or:

public int InsuranceId { get; set; }
[ForeignKey("InsuranceId")]
public Insurance Insurance { get; set; }

By putting the attribute on the scalar property and specifying the name of the scalar property, EF can't understand what you're trying to do.

This applies to all of your [ForeignKey("...")] attributes in the code.

NB: Since there is only one navigation property to each given entity type, and the scalar property names match the navigation property names with the Id suffix added, the [ForeignKey] attributes aren't actually required.



EDIT:

You have two navigation properties for the Insurance entity:

public Insurance Insurance { get; set; }
...
public Insurance Invoice { get; set; }

I suspect the second one should be:

public Invoice Invoice { get; set; }


Related Topics



Leave a reply



Submit