views:

987

answers:

3

The basic problem...

I have a method which executes the following code:

IList<Gig> gigs = GetGigs().WithArtist(artistId).ToList();

The GetGigs() method gets Gigs from my database via LinqToSql...

So, when GetGigs().WithArtist(artistId).ToList() is executed I get the following exception:

Member access 'ListenTo.Shared.DO.Artist Artist' of 'ListenTo.Shared.DO.Act' not legal on type 'System.Collections.Generic.List`1[ListenTo.Shared.DO.Act]

Note that the extension function "WithArtist" looks like this:

    public static IQueryable<Gig> WithArtist(this IQueryable<Gig> qry, Guid artistId)
    {
        return from gig in qry
               where gig.Acts.Any(act => (null != act.Artist) && (act.Artist.ID == artistId))
               orderby gig.StartDate
               select gig;
    }

If I replace the GetGigs() method with a method that constructs a collection of gigs in code (rather than from the DB via LinqToSQL) I do NOT get the exception.

So I'm fairly sure the problem is with my LinqToSQl code rather than the object structure.

However, I have NO IDEA why the LinqToSQl version isnt working, so I've included all the associated code below. Any help would be VERY gratefully receivced!!

The LinqToSQL code....

    public IQueryable<ListenTo.Shared.DO.Gig> GetGigs()
    {
        return from g in DBContext.Gigs
               let acts = GetActs(g.ID)
               join venue in DBContext.Venues on g.VenueID equals venue.ID
               select new ListenTo.Shared.DO.Gig
               {
                   ID = g.ID,
                   Name = g.Name,
                   Acts = new List<ListenTo.Shared.DO.Act>(acts),
                   Description  = g.Description,
                   StartDate    = g.Date,
                   EndDate      = g.EndDate,
                   IsDeleted    = g.IsDeleted,
                   Created      = g.Created,
                   TicketPrice  = g.TicketPrice,
                   Venue        =  new ListenTo.Shared.DO.Venue { 
                                    ID = venue.ID, 
                                    Name = venue.Name, 
                                    Address = venue.Address,
                                    Telephone = venue.Telephone,
                                    URL = venue.Website 
                   }

               };
    }



    IQueryable<ListenTo.Shared.DO.Act> GetActs()
    {
        return from a in DBContext.Acts

               join artist in DBContext.Artists on a.ArtistID equals artist.ID into art
               from artist in art.DefaultIfEmpty()

               select new ListenTo.Shared.DO.Act
               {
                    ID = a.ID,
                    Name = a.Name,
                    Artist = artist == null ? null : new Shared.DO.Artist
                    {
                       ID =  artist.ID,
                       Name = artist.Name
                    },
                    GigId = a.GigID

               };
    }

    IQueryable<ListenTo.Shared.DO.Act> GetActs(Guid gigId)
    {
        return GetActs().WithGigID(gigId);
    }

I have included the code for the Act, Artist and Gig objects below:

public class Gig : BaseDO
{

    #region Accessors

    public Venue Venue
    {
        get;
        set; 
    }

    public System.Nullable<DateTime> EndDate
    {
        get;
        set;
    }

    public DateTime StartDate
    {
        get;
        set;
    }

    public string Name
    {
        get;
        set;
    }

    public string Description
    {
        get;
        set;
    }

    public string TicketPrice
    {
        get;
        set;
    }

    /// <summary>
    /// The Act object does not exist outside the context of the Gig, therefore,
    /// the full act object is loaded here.
    /// </summary>
    public IList<Act> Acts
    {
        get;
        set;
    }

    #endregion
}

public class Act : BaseDO
{
    public Guid GigId { get; set; }
    public string Name { get; set; }
    public Artist Artist { get; set; }
}

public class Artist : BaseDO
{
    public string Name { get; set; }
    public string Profile { get; set; }
    public DateTime Formed { get; set; }
    public Style Style { get; set; }
    public Town Town { get; set; }
    public string OfficalWebsiteURL { get; set; }
    public string ProfileAddress { get; set; }
    public string Email { get; set; }
    public ImageMetaData ProfileImage { get; set; }

}

public class BaseDO: IDO
{
    #region Properties

    private Guid _id;

    #endregion

    #region IDO Members

    public Guid ID
    {
        get
        {
            return this._id;
        }
        set
        {
            this._id = value;
        }
    }




}

}

+1  A: 

I don't see anything in your classes to indicate how LINQ to SQL is meant to work out which column is which, etc.

Were you expecting the WithArtist method to be executed in .NET, or converted into SQL? If you expect it to be converted into SQL, you'll need to decorate your Gig class with appropriate LINQ to SQL attributes (or configure your data context some other way). If you want it to be executed in code, just change the first parameter type from IQueryable<Gig> to IEnumerable<Gig>.

Jon Skeet
There are auto generated LinqToSQl gig classes. The gig class defined above are schema driven objects which I am adapting the to LinqToSQl Data into...
ListenToRick
So is the Gig type you're returning from GetGigs() one which LINQ to SQL knows about or not?
Jon Skeet
@Jon : I know you're busy (and this question is rather old, now), but is there any chance you could elaborate on your answer with some code .. with an emphasis on the *to be converted to SQL*, angle. I've got the exact same problem here. I'm using the Repository pattern (eg. GetActs()) + Pipes and Filters (eg. With Artist()) and my repository doesn't handle other *children* properties (eg. Gigs is parent, Acts property is child), so I was trying to manipulate the query (eg. WithActs()) to dynamically return an assosiated child, if the consumer codes for that. This problem is killing me :(
Pure.Krome
@Pure Krome: I suggest you ask as a separate question. It'll be easier to write a proper answer, and there may well be subtle differences. It'll also get the attention of more people :)
Jon Skeet
+2  A: 

I think the problem is the 'let' statement in GetGigs. Using 'let' means that you define a part of the final query separately from the main set to fetch. the problem is that 'let', if it's not a scalar, results in a nested query. Nested queries are not really Linq to sql's strongest point as they're executed deferred as well. In your query, you place the results of the nested query into the projection of the main set to return which is then further appended with linq operators.

When THAT happens, the nested query is buried deeper into the query which will be executed, and this leads to a situation where the nested query isn't in the outer projection of the query to execute and thus has to be merged into the SQL query ran onto the DB. This is not doable, as it's a nested query in a projection nested inside the main sql query and SQL doesn't have a concept like 'nested query in a projection', as you can't fetch a set of elements inside a projection in SQL, only scalars.

Frans Bouma
So how do I load a collection of acts without using let?
ListenToRick
Many thanks for you help by the way!!
ListenToRick
You can use it the way you do it, but then you can't append query operators to that query: the nested query has to be on the outside projection. So the query of GetGigs itself will run with the nested query.
Frans Bouma
A: 

I had the same issue and what seemed to do the trick for me was separating out an inline static method call that returned IQueryable<> so that I stored this deferred query into a variable and referenced that.

I think this is a bug in Linq to SQL but at least there is a reasonable workaround. I haven't tested this out yet but my assumption is that this problem may arise only when referencing static methods of a different class within a query expression regardless of whether the return type of that function is IQueryable<>. So maybe it's the class that holds the method that is at the root of the problem. Like I said, I haven't been able to confirm this but it may be worth investigating.

jpierson