Chrillewoodz
Chrillewoodz

Reputation: 28348

How to automatically convert all Enumerable to List when using Entity Framework?

I have this POST endpoint in my ASP.NET Core Web API (with Entity Framework):

[HttpPost]
public async Task<ActionResult<Game>> PostGame(Game game) {

  ...

  game.State = new GameState();
  game.State.Map.Rows = game.State.Map.Rows.ToList();

  ...
}

Then I have these two classes:

public class MapRow {

  public int Id { get; set; }
  public IEnumerable<MapColumn> Columns { get; set; }

  public MapRow() { }

  public MapRow(IEnumerable<MapColumn> columns) {
    Columns = columns;
  }
}

public class Map {

  public long Id { get; set; }
  public IEnumerable<MapRow> Rows { get; set; }

  public Map() { }

  public Map(IEnumerable<MapRow> rows) {
    Rows = rows;
  }
}

Which has two Enumerables that need to be converted to a List or Entity Framework will throw an error similar to this:

System.InvalidOperationException: The type of navigation property 'Columns' on the entity type 'MapRow' is 'SelectRangeIterator' which does not implement ICollection. Collection navigation properties must implement ICollection<> of the target type.

I solved this error for my Rows by calling .ToList() in the endpoint itself. However since there are multiple endpoints and you can have Enumerables that are quite nested it's quickly becoming unmaintainable having to convert every Enumerable to a List manually.

Is there an easier way that would allow me to automate this?

Upvotes: 0

Views: 338

Answers (1)

Steve Py
Steve Py

Reputation: 34783

For EF entities, navigation properties for collections should be declared as ICollection<T> rather than IEnumerable<T>. So for instance:

public class MapRow {

  public int Id { get; set; }
  public virtual ICollection<MapColumn> Columns { get; set; } = new List<MapColumn>();

  public MapRow() { }

  public MapRow(IEnumerable<MapColumn> columns) {
    Columns = columns;
  }
}

public class Map {

  public long Id { get; set; }
  public virtual IColleciton<MapRow> Rows { get; set; } = new List<MapRow>();

  public Map() { }

  public Map(IEnumerable<MapRow> rows) {
    Rows = rows;
  }
}

ICollection<T> extends IEnumerable<T> so any method expecting IEnumerable will be perfectly content with ICollection. It's a different story when you try passing an IEnumerable to something expecting IList or ICollection.

Declaring them as virtual also allows for EF to assign proxies to these properties to assist with lazy loading if they are needed and the DbContext is still available. It also helps to auto-initialize the properties to a new collection so that when new-ing up a Map, you can immediately start adding Rows and Columns without risking NullReferenceExceptions.

Upvotes: 1

Related Questions