Search code examples
vb.netjoinasp.net-mvc-5entity-framework-6.1

Unable to cast the type 'System.String' to type 'System.Collections.Generic.IEnumerable


My query below works fine when there is only category to return but as soon as as there is more than one I get Sequence contains more than one element error message. I would like to return all the relevant categories. So I changed the PostCategory in the DTO from string to a List and that is when I get the casting error. I also tried changing it to a IList(of String) and IList(of be_Category) and adding ToList to ca.CategoryName. That didn't work.

My query with the joins:

Public Function SelectByID(id As Integer) As PostDTO Implements IPostRepository.SelectByID
    Dim post As New PostDTO
    Using db As Ctx = New Ctx

        post = From ca In db.be_Categories 
               Join c In db.be_PostCategory On ca.CategoryID Equals (c.CategoryID)
               Join p In db.be_Posts On c.PostID Equals (p.PostID)
               Where p.PostRowID = id
               Select New PostDTO With {
                  .PostCategory = ca.CategoryName, 
                  .PostDateCreated = p.DateCreated, 
                  .PostGuid = p.PostID, 
                  .PostId = p.PostRowID, 
                  .PostText = p.PostContent,
                  .PostTitle = p.Title}).Single
    End Using
    Return post
End Function

So is it possible to project the sequence of Category Names into a new DTO or something else or is there another way to return all the categories? I guess since CategoryName is a string, L2E cannot project the strings into the list. Do I need a GroupBy to project category strings into a new form? I also tried AsEnumerable and I tried String.Join - neither worked.

The DTO is below - If PostCategory is a string then I can get a single category back to the view. I hope I have explained it clearly.

 Public Class PostDTO
    Public PostId As Integer
    Public PostGuid As Guid
    Public PostTitle As String
    Public PostSummary As String
    Public PostText As String
    Public PostDateCreated As DateTime
    Public PostIsPublished As Boolean
    Public PostCategory As IList(Of be_PostCategory)
End Class

EDIT: Updated SelectById:

Public Function SelectByID(id As Integer) As IEnumerable(Of PostDTO) Implements IPostRepository.SelectByID

 Dim post As IEnumerable(Of PostDTO)
 Using db As Ctx = New Ctx
 post = From ca In db.be_Categories 
               Join c In db.be_PostCategory On ca.CategoryID Equals (c.CategoryID)
               Join p In db.be_Posts On c.PostID Equals (p.PostID)
               Where p.PostRowID = id
               Select New PostDTO With {
                  .PostCategory = ca.CategoryName, 
                  .PostDateCreated = p.DateCreated, 
                  .PostGuid = p.PostID, 
                  .PostId = p.PostRowID, 
                  .PostText = p.PostContent,
                  .PostTitle = p.Title}).ToList
    End Using
    End Function

Solution

  • The Single method throws an exception when the number of elements returned by the query is not exactly 1. Try removing .Single() from the end of your query.

    Also, we don't see what variable it's being assigned to. An anonymous type works well here, though if you don't use one, make sure it's correct i.e.

    Dim result As IEnumerable(Of PostDTO) = From ca In db.be_Categories ...
    

    Edit #1

    I should add some clarification. When running a LINQ query, expect for the query to return any number of results, just like you would expect SQL or similar to do. In the case where you only expect one result however (i.e. Select Top 1 ...) then you can use .Single(). Getting back to your case, your query is against an Entity Framework data source I can only imagine (Ctx is that, correct?). As indicated by the MSDN documentation, you will return a DbQuery(Of PostDTO), which is the data type returned by a LINQ to Entities query against a DbContext. This type, depending on what you want to do with it, can be cast to several interfaces. See its definition

    Public Class DbQuery(Of TResult) _
        Implements IOrderedQueryable(Of TResult), IQueryable(Of TResult),  _
        IEnumerable(Of TResult), IOrderedQueryable, IQueryable, IEnumerable,  _
        IListSource, IDbAsyncEnumerable(Of TResult), IDbAsyncEnumerable
    

    The IEnumerable(Of TResult), with TResult being a PostDTO in your case, is what you can cast to so you can enumerate the results, providing a lot of functionality like further queries, sorting, getting average, max, min, etc. Hope this clears it up.

    Edit #2

    Finally getting to the bottom of the problem. The first part gives you a single Post, but with nothing in the PostCategory. The second part puts the list of categories into that single post.

    post = From ca In db.be_Categories 
           Join c In db.be_PostCategory On ca.CategoryID Equals (c.CategoryID)
           Join p In db.be_Posts On c.PostID Equals (p.PostID)
           Where p.PostRowID = id
           Select New PostDTO With {
              .PostCategory = Nothing, 
              .PostDateCreated = p.DateCreated, 
              .PostGuid = p.PostID, 
              .PostId = p.PostRowID, 
              .PostText = p.PostContent,
              .PostTitle = p.Title}).FirstOrDefault()
    
    post.PostCategory = (From ca In db.be_Categories
                         Join c In db.be_PostCategory On ca.CategoryID Equals (c.CategoryID)
                         Where p.PostRowID = id
                         Select ca.CategoryName).ToList()