Reputation: 123
I'm trying to understand the resultant type from a LINQ select action. I'm having trouble understanding how to utilize the result.
Consider the following:
public class ReportContract
{
public int ContractId { get; set; }
public string ContractName { get; set; }
public string Title { get; set; }
public string[] Details { get; set; }
}
public class ReportContractVM
{
public int ContactId { get; set; }
public string ContractName { get; set; }
}
public class SomeController : ControllerBase
{
public ActionResult<IEnumerable<ReportContractVM>> Get()
{
IEnumerable<ReportContract> contracts = CreateConrtacts();
IEnumerable<ReportContractVM> result = contracts.Select(
x => new ReportContractVM
{
ContactId = x.ContractId,
ContractName = x.ContractName
}
);
return Ok(result);
}
private IEnumerable<ReportContract> CreateContracts()
{
List<ReportContract> contracts = new List<ReportContract>()
{
new ReportContract {
ContractId = 1234,
ContractName= "ABCD",
Details= new string[] {
"test", "Best", "rest", "jest"},
Title="First"
},
new ReportContract {
ContractId = 1235,
ContractName= "EFGH",
Details= new string[] {
"fish", "dish", "wish", "pish"},
Title="Second"
},
new ReportContract {
ContractId = 1236,
ContractName= "IJKL",
Details= new string[] {
"hot", "tot", "mot", "jot"},
Title="Third"
},
new ReportContract {
ContractId = 1237,
ContractName= "MNOP",
Details= new string[] {
"ring", "sing", "bing", "ping"},
Title="Fourth"
}
};
return contracts;
}
}
Inspecting the type assigned to 'result' in the debugger yields:
System.Linq.Enumberable.SelectListIterator
<
ComplexIEnumerable.ReportContract,
ComplexIEnumerable.ReportContractVM
>
I am not sure how this relates to
IEnumerable<ReportContractVM>.
How is
IEnumerable<ReportContractVM>
accessed from this result?
Please ignore the rest of this text. The "intelligent" editor is demanding more detail and I think this suffices.
Upvotes: 1
Views: 2987
Reputation: 155370
I recommend reading this article that explains what Enumerator (aka yield return
methods) materialization is - as it's core to how Linq works and how to use IEnumerable<T>
results (note that an IEnumerable<T>
can refer to a both materialized collections like T[]
or List<T>
and also non-materialized enumerators):
As for your case, generally speaking, never return a non-materialized Entity Framework (i.e. database-backed) IEnumerable<T>
or IQueryable<T>
from an ASP.NET Web API Controller or pass a non-materialized IEnumerable<T>
as a member of a ViewModel (this is because the lifetime of the parent DbContext
may be shorter than the lifetime of the IEnumerable<T>
/IQueryable<T>
object). However returning an non-materialized collection using only in-memory objects is fine (such as an enumerator over enum
values to get SelectListItem
objects):
Specifically, change this:
IEnumerable<ReportContractVM> result = contracts.Select(
x => new ReportContractVM
{
ContactId = x.ContractId,
ContractName = x.ContractName
}
);
to this:
List<ReportContractVM> list = contracts
.Select( x => new ReportContractVM { ContractId = x.ContractId, ContractName = x.ContractName } )
.ToList();
Also, change your ActionResult<T>
to use IReadOnlyList<T>
instead of IEnumerable<T>
which will help ensure you always return a materialized list instead of a possibly non-materialized enumerator:
public class SomeController : ControllerBase
{
public ActionResult<IReadOnlyList<ReportContractVM>> Get()
{
}
}
BTW, I've been having problems with ActionResult<T>
not working well with generics and in async Task<ActionResult<T>>
actions so I personally prefer to do this, but YMMV:
public class SomeController : ControllerBase
{
[Produces( typeof( IReadOnlyList<ReportContractVM> ) )
public IActionResult Get()
{
}
// if async:
[Produces( typeof( IReadOnlyList<ReportContractVM> ) )
public async Task<IActionResult> Get()
{
}
}
Upvotes: 3
Reputation: 339
Basically the output is the concrete enumerable type returned from Select with type parameters referencing both the input and output types. It may be easier to see in a simplified example below.
namespace ExampleNamespace
{
class A
{
public A(int i) { }
}
}
Now say I have this code somewhere:
List<int> i = new List<int>() { 1, 2, 3, 4, 5, 6 };
var temp = i.Select(x => new A(x));
Upon inspection the type of temp is:
System.Linq.Enumerable.WhereSelectListIterator<int, ExampleNamespace.A>
So to break this down:
System.Linq.Enumerable.WhereSelectListIterator is the type of iterator returned from Select. This differs from your type slightly probably due to different .NET versions.
int is the the type that your starting IEnumerable held
ExampleNamespace.A is the full name of the output type.
That all being said, you should read and understand what Dai posted in their answer.
Upvotes: 0