Problems initializing a type within a LINQ query

Asked

Viewed 144 times

4

I am going through the following problem, I am assembling an API for a forum application and this API needs a endpoint returning a collection of Forum who own the property ParentId nulla. Then I mounted the following endpoint:

[AllowAnonymous]
[HttpGet]
[Route("toplevel")]
public async Task<IHttpActionResult> GetAllTopLevel(int page = 1, int pageSize = 15)
{
    var data = Db.Forums
        .Where(f => !f.ParentId.HasValue)
        .OrderByDescending(f => f.Name)
        .Skip((page - 1) * pageSize)
        .Take(pageSize)
        .Select(f => new ForumDTO
        {
            Id = f.Id,
            Name = f.Name,
            SubForums = f.SubForums.Select(sf => new ForumDTO
            {
                Id = sf.Id,
                Name = sf.Name
            }).ToList()
        }).ToList();

    return Ok(data);
}

But I’m having trouble Select of this LINQ, which is causing a NotSupportedException with the following error message:

The type 'Zigforum.Models.Dtos.Forumdto' appears in two structurally incompatible initializations Within a single LINQ to Entities query. A type can be initialized in two Places in the same query, but only if the same properties are set in Both Places and those properties are set in the same order.

Class Forum:

public class Forum
{
    public Forum()
    {
        SubForums = new List<Forum>();
        Posts = new List<Post>();
    }

    public int Id { get; set; }
    public int? ParentId { get; set; }
    public string Name { get; set; }
    public DateTime Created { get; set; }

    public virtual Forum Parent { get; set; }
    public virtual ICollection<Forum> SubForums { get; set; }
    public virtual ICollection<Post> Posts { get; set; }
}

Class ForumDTO:

public class ForumDTO
{
    [JsonProperty("id")]
    public int Id { get; set; }

    [JsonProperty("parent_id")]
    public int? ParentId { get; set; }

    [JsonProperty("name")]
    public string Name { get; set; }

    [JsonProperty("created")]
    public DateTime? Created { get; set; }

    [JsonProperty("parent")]
    public ForumDTO Parent { get; set; }

    [JsonProperty("subforums")]
    public List<ForumDTO> SubForums { get; set; }

    [JsonProperty("posts")]
    public List<PostDTO> Posts { get; set; }
}

I’ve tried some things too, like initializing the property SubForums with null, since in the previous error message it says that a type can only be initialized in two places if in both places values are assigned to the same properties and in the same order. To do so seemed to me to compress with the demands:

[AllowAnonymous]
[HttpGet]
[Route("toplevel")]
public async Task<IHttpActionResult> GetAllTopLevel(int page = 1, int pageSize = 15)
{
    var data = Db.Forums
        .Where(f => !f.ParentId.HasValue)
        .OrderByDescending(f => f.Name)
        .Skip((page - 1) * pageSize)
        .Take(pageSize)
        .Select(f => new ForumDTO
        {
            Id = f.Id,
            Name = f.Name,
            SubForums = f.SubForums.Select(sf => new ForumDTO
            {
                Id = sf.Id,
                Name = sf.Name,
                SubForums = null // Note que fiz a alteração aqui
            }).ToList()
        }).ToList();

    return Ok(data);
}

But it resulted in another mistake:

Unable to create a null Constant value of type 'System.Collections.Generic.List`1[[Zigforum.Models.Viewmodels.Forumdto, Zigforum, Version=1.0.0.0, Culture=neutral, Publickeytoken=null]]'. Only Entity types, enumeration types or Primitive types are supported in this context.

Could someone please help me? I don’t know what else to do.

  • have tried SubForums = new List<ForumDTO>()?

  • @Tobymosque also does not work, it is a restriction of LINQ to Entities

  • usually I use the AutoMapper to fill in my DTO/Contracts, posted an answer with an example.

4 answers

1

You cannot initialize an object within a Linq expression. Simple like this.

But there’s a way around:

public class ForumDTO
{
    // Propriedades

    public static ToForumDTO(Forum f)
    {
        return new ForumDTO 
        {
            Id = f.Id,
            Name = f.Name,
            SubForums = f.SubForums
        }
    }
}

And use on your Linq like this:

 .Select(ForumDTO.ToSubForum).ToList();

1

I see that your Entity and your DTO have the same properties with compatible types and the same name.

In this case, you can try using the Automapper to do the mapping for you.

below follows an example using LINQ for Objects, but should work smoothly with LINQ to Entities.

Entity

public class Forum
{
    public Forum()
    {
        this.SubForums = new List<Forum>();
    }

    public Guid ID { get; set; }        
    public string Nome { get; set; }
    public Guid? ParentID { get; set; }

    public virtual Forum Parent { get; set; }
    public virtual ICollection<Forum> SubForums { get; set; }
}

DTO

[DataContract]
public class ForumDTO
{
    [DataMember]
    public Guid ID { get; set; }
    [DataMember]
    public string Nome { get; set; }
    [DataMember]
    public Guid? ParentID { get; set; }

    [DataMember]
    public ForumDTO Parent { get; set; }
    [DataMember]
    public IList<ForumDTO> SubForums { get; set; }
}

Global.asax - Registering Maps

protected void Application_Start()
{
    Mapper.CreateMap<Forum, ForumDTO>();
}

Consultation

var forums = new List<Forum>();
PopularForums(ref forums);

var data = (
    from forum in forums
    where !forum.ParentID.HasValue
    select Mapper.Map<ForumDTO>(forum)
).ToList();

If it still doesn’t work out, you can make a ToList() before making the map.

var forums = new List<Forum>();
PopularForums(ref forums);

var lista = (
    from forum in forums
    where !forum.ParentID.HasValue
    select forum
).ToList();

var data = Mapper.Map<List<ForumDTO>>(lista);

You can see the full example on Dotnetfiddle, note that it serializes the full tree of the forums, regardless of the number of relatives and subtrees.

  • Interesting, I will try to do this way using Automapper, later I give you a feedback, because I will not be able to test it now.

1

Create an explicit static converter in your Forumdto class that converts the Forum to Forumdto and in your select statement use it. It would also be interesting to replace a good part of the methods with LINQ statements that mimic SQL.

-1

You can initialize an object within select. Take a look at this code, it works perfectly.

using System;
using System.Collections.Generic;
using System.Linq;

public class Program
{
    public static void Main()
    {
        const int page = 1;
        const int pageSize = 15;

        var forums = new List<Forum>
        {
            new Forum
            {
                Id = 1,
                Name = "Forum 1",
                Created = DateTime.Now,
                SubForums = new List<Forum>
                {
                    new Forum {Id = 2, ParentId = 1, Name = "Forum 2"},
                        new Forum {Id = 3, ParentId = 1, Name = "Forum 3"}
                }
            }
        };

        var data = forums
            .Where(f => !f.ParentId.HasValue)
            .OrderByDescending(f => f.Name)
            .Skip((page - 1)*pageSize)
            .Take(pageSize)
            .Select(f => new ForumDTO
                    {
                        Id = f.Id,
                        Name = f.Name,
                        SubForums = f.SubForums.Select(sf => new ForumDTO
                                                       {
                                                           Id = sf.Id,
                                                           Name = sf.Name,
                                                           SubForums = null
                                                       }).ToList()
                    }).ToList();

        foreach (var d in data)
        {
            Console.WriteLine(d.Name);

            if (d.SubForums.Count <= 0) continue;

            foreach (var subForum in d.SubForums)
            {
                Console.WriteLine("\t"+ subForum.Name);
            }
        }       
    }
}

public class Forum
{
    public Forum()
    {
        SubForums = new List<Forum>();
    }

    public int Id { get; set; }
    public int? ParentId { get; set; }
    public string Name { get; set; }
    public DateTime Created { get; set; }
    public virtual Forum Parent { get; set; }
    public virtual ICollection<Forum> SubForums { get; set; }
}

public class ForumDTO
{
    public int Id { get; set; }
    public int? ParentId { get; set; }
    public string Name { get; set; }
    public DateTime? Created { get; set; }
    public ForumDTO Parent { get; set; }
    public List<ForumDTO> SubForums { get; set; }
}

https://dotnetfiddle.net/071eSl

  • 1

    Dude, you’re using Linq on a normal Colection, his problem is because he’s using Linq to Ntity where "Line becomes an SQL query", namely the preview You’re not getting the hang of Lily.

  • Try this: https://dotnetfiddle.net/ULn8ny Only passing Db remains. I can’t test here, but it might work.

  • Lol, eh different, he’s using another Precedent for Linnus, EPH

  • His problem is in ToList() inside the Linnum,

Browser other questions tagged

You are not signed in. Login or sign up in order to post.