-1

I am trying to populate an instance with LINQ containing an array of nested classes. I have managed to do this with the following LINQ. I have also included the classes that make up the instance in the select.

    select new EditableWarrantBook {
        Id = p1.id,
        Comment = p1.EntryComment,
        WarrantYear1 = new BookYear {
            StatusYear = p2.StatusYear,
            Status = p2.Status,
        },
        WarrantYear2 = new BookYear {
            StatusYear = p3.StatusYear,
            Status = p3.Status,
        },
        WarrantYear3 = new BookYear {
            StatusYear = p4.StatusYear,
            Status = p4.Status,
        }
    }

public class EditableWarrantBook
{
    public int Id { get; set; }
    public string Comment { get; set; }

    public BookYear[] WarrantYear = new BookYear[3];

    public BookYear WarrantYear1
    {
        get { return WarrantYear[0]; }
        set { WarrantYear[0] = value; }
    }
    public BookYear WarrantYear2
    {
        get { return WarrantYear[1]; }
        set { WarrantYear[1] = value; }
    }
    public BookYear WarrantYear3
    {
        get { return WarrantYear[2]; }
        set { WarrantYear[2] = value; }
    }

}

public class BookYear
{
    public int? StatusYear { get; set; } 
    public string Status { get; set; } 
}

This works I can access values with either WarrantYear[0] or WarrantYear1. This can sometimes be useful when designing UI. However, in this case, I don't need the WarrantYear1 property because I am turning this into JSON and I don't need to repeat (or want to send two versions of the same data on the network). My question is, how do I write the select statement to load WarrantYear array. Or how do I write the class so that I can access the array as a property. My solution should not contain the Warrant1, Warrant2, Warrant3 properties in the EditableWarrantBook Class.

ricardo
  • 525
  • 5
  • 12
  • what are you trying to accomplish with all the joins - none of them is actually using any values from the CookSaleStatus object... – Joanna Derks May 18 '12 at 20:12
  • The join isn't part of the problem, but I did simplify a bit based on your comment. I didn't want to confuse things, but sometimes leaving it out is also confusing. – ricardo May 18 '12 at 20:54

2 Answers2

1

Since I couldn't get the select to work without Warrant1, Warrant2 and Warrant3 property I left it as is and rewrote the class so that JSON serializer would create an instance of JSON without Warrant1, Warrant2 and Warrant3 property. I used reflection from this article. Just to clarify the problem, my first attempt was creating a JSON payload that was twice as big as it had to be because the JSON serializer was creating both the Warrant1, Warrant2 and Warrant3 structure and again the Warrant[] array. Rewriting the class with a base class for a JSON result with only the Warrant[] array satisfied the interface requirements for my Webservice.

namespace my.Models
{
public class WarrantBook
{
    public int Id { get; set; } 
    public string Comment { get; set; }

    BookYear[] warrantYear = new BookYear[3];

    public BookYear[] WarrantYear
    {
        get { return warrantYear; }
        set { warrantYear = value; }
    }
}

public class EditableWarrantBook : WarrantBook  
{
    public BookYear WarrantYear1
    {
        get { return WarrantYear[0]; }
        set { WarrantYear[0] = value; }
    }
    public BookYear WarrantYear2
    {
        get { return WarrantYear[1]; }
        set { WarrantYear[1] = value; }
    }
    public BookYear WarrantYear3
    {
        get { return WarrantYear[2]; }
        set { WarrantYear[2] = value; }
    }

}
public class BookYear
{
    public int? StatusYear { get; set; } 
    public string Status { get; set; } 
}

public static class Ext
{
    public static void CopyProperties(this EditableWarrantBook  source, WarrantBook destination)
    {
        // Iterate the Properties of the destination instance and   
        // populate them from their source counterparts   
        PropertyInfo[] destinationProperties = destination.GetType().GetProperties();
        foreach (PropertyInfo destinationPi in destinationProperties)
        {
            PropertyInfo sourcePi = source.GetType().GetProperty(destinationPi.Name);
            destinationPi.SetValue(destination, sourcePi.GetValue(source, null), null);
        }
    }
}

}

Usage WCF REST Webservice

        [WebGet(UriTemplate = "GetWarrant?id={s}&user={user}")]
    public WarrantBook GetWarrant(string s, string user)
    {
        int id;
        if (int.TryParse(s, out id))
        {
            EditableWarrantBook model = SessionWarrantBook.One(p => p.Id == id);
            model.CheckedOutBy = user; // need to add checkout code
            WarrantBook jsonModel = new WarrantBook();
            model.CopyProperties(jsonModel);
            return jsonModel;
        }
        return new WarrantBook();
    }
Community
  • 1
  • 1
ricardo
  • 525
  • 5
  • 12
0

The problem is that you're trying to mix SQL world (building your query) with your CLR world (creating new objects). The way to get around this is to break up your queries into your initial data fetching query (SQL world), and then enumerate over the results to transform into your objects (CLR world):

So after the where p1.SaleYear == 2009, add this:

select new { p1, p2, p3, p4 })
.AsEnumerable()
.Select(tr => new EditableWarrantBook
    {
    Id = tr.p1.id,
    Comment = tr.p1.EntryComment,
    WarrantYear = new[] {
        tr.p2 == null ? (BookYear)null : new BookYear // EDITED LINE
        {
            StatusYear = tr.p2.StatusYear,
            Status = tr.p2.Status,
        },
        tr.p3 == null ? (BookYear)null : new BookYear // EDITED LINE
        {
            StatusYear = tr.p3.StatusYear,
            Status = tr.p3.Status,
        },
        tr.p4 == null ? (BookYear)null : new BookYear // EDITED LINE
        {
            StatusYear = tr.p4.StatusYear,
            Status = tr.p4.Status,
        }}
    }).ToList();

If you want to stay in query syntax, then just change it to:

result = from tr in 
    (/*Select up to the AsEnumerable call above)*/).AsEnumerable()
    select new EditableWarrantBook
        {
            /* Initialization code supplied above */
        }

Edit: So I've modified the example one more time. It looks like one of the values that you're getting back is null, so I've added a null check to make sure you're not accessing properties of null objects (although I didn't check p1 for null).

SPFiredrake
  • 3,852
  • 18
  • 26
  • I tried the above and it compiles, but I get the following run time error. "The array type BookYear[] cannot be initialized in a query result. Consider using 'System.Collecions.Generic.List'1 [blabla.BookYear]' instead." That message doesn't help me. – ricardo May 18 '12 at 21:00
  • @ricardo - Based on this reply http://stackoverflow.com/questions/6435520/not-able-to-return-jsonresult it seems that you can use `.AsEnumerable()` to fix the problem - try `from p4 in list3.DefaultIfEmpty().AsEnumerable()` ? – Joanna Derks May 18 '12 at 21:10
  • Didn't help, same error message. My other option is to leave the select as is and try to create a base class with Warrant1, Warrant2, and Warrant3 as protected. Then use a daughter class that would mask the protected properties and use it to create the JSON. But I can't figure that out either, that's why I am trying to get help. It seems like I am getting deeper in a whole. THank you for you help. – ricardo May 18 '12 at 22:01
  • I tried the above again after the change SPFiredrake suggests. Compiles, but know I get a null reference. Maybe my class is not correct? I am using the version from the answer I gave. Though I now have working code would rather not have to use reflection and do it the way SPFiredrake suggests, but I just can't get it to work. Thank you. – ricardo May 21 '12 at 15:44
  • That did it. I appreciate you taking the time to show me how to do this. I learned more than just how to solve my initial question because of you taking the time. Thank you. – ricardo May 21 '12 at 19:18
  • No problem. Just be sure to check those values for null when working with them, otherwise you'll get more null reference exceptions. One thing I like to do when I have the possibility of nulls is to create a default object, IE: `public class BookYear { ... public static BookYear Default = new BookYear(); } }` and `public class EditableWarrantBook { ... public static EditableWarrantBook Default = new EditableWarrantBook { Id = -1, WarrantYear = new[] { YearBook.Default, YearBook.Default, YearBook.Default }; } }` – SPFiredrake May 21 '12 at 21:07
  • Thank you for the advice. FYI. I did a little testing with 70K records and the .asEnumerable solution took 6 sec versus the reflection solution which took less than 2 sec. I didn't expect this so I thought I would pass it on. – ricardo May 21 '12 at 22:06