This thread looks to be a little on the old side and therefore may no longer be relevant. Please see if there is a newer thread on the subject and ensure you're using the most recent build of any software if your question regards a particular product.
This thread has been locked and is no longer accepting new posts, if you have a question regarding this topic please email us at support@mindscape.co.nz
|
Hi All, I have an interface defined that is used when filling combo boxes as below : public interface ILookup this then gets implemeted in partial classes for objects that I want to display in a combo box, e.g. Location partial class Location : ILookup In the repositorry then there is a simple query to get all Locations : public IList<Location> GetAllLocations() and this is called to get the list (which is linked to a BindingSource on the combo box) similar to below : Repository.GetAllLocations().Cast<ILookup>().ToList(); Okaay, so I've got this for various types of entities, but often, that's *all* I want - I don't need all the other fields in that entity. Not having that much experience in C# or Lightspeed, I'm not sure of the best way to accomplish just getting the few fields I want. I thought of something like below but I get a Lightspeed error 'Could not find field [LookupID] on model [Location]' at run time (it builds OK) when it gets to the cast, so I'm doing something wrong somewhere. Any ideas / better way of doing things ?? public List<ILookup> GetLocationsSummary() Normal 0 false false false EN-NZ X-NONE X-NONE MicrosoftInternetExplorer4 /* Style Definitions */ table.MsoNormalTable {mso-style-name:"Table Normal"; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-priority:99; mso-style-qformat:yes; mso-style-parent:""; mso-padding-alt:0cm 5.4pt 0cm 5.4pt; mso-para-margin:0cm; mso-para-margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:11.0pt; font-family:"Calibri","sans-serif"; mso-ascii-font-family:Calibri; mso-ascii-theme-font:minor-latin; mso-fareast-font-family:"Times New Roman"; mso-fareast-theme-font:minor-fareast; mso-hansi-font-family:Calibri; mso-hansi-theme-font:minor-latin;}
|
|
|
You're asking LightSpeed to load LookupID, LookupDescription and Enabled from the database. But they're not in the database: they're properties you've declared. LightSpeed doesn't know that they back onto Id, LocationName and a constant. So it dumbly goes off to the database and asks for them, and the database laughs and flings exceptions. So you've got two choices: either use LINQ to Objects to query against the materialised entities *after* they've been pulled back from the database, or reference the LightSpeed properties in order to get a projection. You've said you want the latter, and you can do this as follows: from loc in Instance.Locations select new { LookupId = loc.Id, LookupDescription = loc.LocationName, Enabled = true }; The bad news is that this will now fail on the Cast<ILookup> because the anonymous type doesn't implement ILookup. (Yeah, the names are the same, but that doesn't suffice. I could create an IHasLength interface with a Length property, but that wouldn't mean that String implemented IHasLength.) Therefore to get this to work you need to create a LocationSummary type which *does* implement ILookup, and select into *that*: from loc in Instance.Locations select new LocationSummary { LookupId = loc.Id, LookupDescription = loc.LocationName, Enabled = true }; // can also do using constructor syntax LocationSummary doesn't have to be an Entity -- it's just a Plain Old Data type whose sole job is to translate the projection into the compile-time ILookup type. (Another alternative, by the way, is to lazy-load the LightSpeed fields, but I wouldn't recommend that in this case.) |
|
|
Ahh, yes, many thanks Ivan. I originally tried projecting into a Location type, and thought about a LocationSummary type, but was thinking it was an entity, not just a data type and then meandered off elsewhere.
|
|
|
I've run into a similar problem, but was pretty convinced it would work. In my case, the interface only specifies properties that the Entity descendant already implements. So for a CarEntity with a string property called 'Color', I have an IHasColor interface that has an abstract version of that same property. Since my own partial CarEntity descends from IHasColor and the model partial actually implements it, it surprised me that CarEntities.Cast throws an exception when I actually use the returned IQueryable. Shouldn't I be able to cast a 'collection' of one type to a 'collection' of an interface that type implements? I think that is what Cast<> allows, but it seems to break in the nightly build from today.
Thanks!
James
|
|
|
Yes, that is what Cast<> does, but remember that operators on an IQueryable aren't executed as you'd expect, but get translated to their database equivalent. So I think that is happening is that when you write UnitOfWork.CarEntities.Cast<IHasColor>(), LightSpeed tries to figure out how to translate Cast<IHasColor> to a SQL function. (I haven't tested this specifically with Cast<>, but that's my expectation.) And we don't know how to translate Cast<> to SQL, so this fails. The solution is to perform the casting client-side by forcing the query to enumerate, and performing the cast on the results: query.AsEnumerable().Cast<IHasColor>(); Thinking about this further, I guess the main reason for having a Cast<> on an IQueryable is to enable LINQ to access properties not available on the castee type and have them translated to SQL -- e.g. unitOfWork.People.Cast<Employee>().Where(e => e.EmployeeNumber < 10) (where an Employee has an EmployeeNumber but a base Person doesn't) -- the Cast operator itself would probably be a no-op. (I haven't verified this guess!) But this won't be the case in your scenario (as Car must implement everything in IHasColor), so the client-side cast should suffice for you. If access to derived class properties does become a requirement for you then let us know. |
|
|
Interesting, that certainly does make sense. In my case, the cast is just to hide away implementation details from the layers that use the interface, but it would be really cool to delay materialization (?) so that the receiver of the interface can do additional filtering, sorting on the collection before materialization. I wonder if there is an operator that changes the return type of operations like ToList and such, but retained the underlying type CarEntity for the purposes of translating to SQL. When I do the Cast<> now, it works fine but the ElementType of the LinqQuery becomes the interface and so further SQL calls attempt to find a table/view with the interface name and that's where things go wrong. Maybe in 3.0? Interfaces aren't quite "POCO", but they are as close as I need to get to allow other layers to remain unaware of the specific data persistence details.
Thanks!
James
|
|
|
I'm not sure why you would need the cast for filtering. It makes sense in the "cast to derived type" scenario, but when you're casting to an interface, doesn't the entity already statically have that member? E.g. if uow.CarEntities.Cast<IHasColor>().Where(ihc => ihc.Color == "HotPink"); works, isn't uow.CarEntities.Where(c => c.Color == "HotPink"); also going to work? (Ditto for OrderBy.) You can then apply .AsEnumerable().Cast<IHasColor>() or .ToList().Cast<IHasColor>() after the Where clause. Ah... I guess the other scenario is that you are exposing the IQueryable from a repository object, so your application can specify queries against it, but you want the application to consume only the interface not the entity: public class CarRepository { // app code Is that your scenario? If so let us know and we can look into adding support for this (no promises...!). |
|
|
Yup, that's exactly it. I'm shooting for the current "ideal" (always seems like more work than it's worth) which is interfaces hiding the storage mechanism. It also is an easy way for similar results (two different views with some common fields) to be passed to functions that work on both. Seems worth the trouble if it isn't a pain.
|
|
|
Right, it appears I was talking through my hat about the issue being the translation of the Cast operator. We were already ignoring it in the translation stage, only to blow up somewhere else. Anyway, I have now fixed it, and the fix will be included in nightly builds dated 28 Aug 2009 and above, available from about 1430 GMT. Please let us know if you still see problems. |
|