I have the following Entity:
public class Invoice
{
[Key]
public int Id { get; set; }
public DateTime? ArchiveDate { get; set; }
public DateTime? ClotureDate { get; set; }
...
}
I would like to know whether my invoice is archived or closed by using a kind of flag (boolean). For that purpose I added 2 unmapped properties in my breeze entity like this:
public class Invoice
{
[Key]
public int Id { get; set; }
public DateTime? ArchiveDate { get; set; }
public DateTime? ClotureDate { get; set; }
[NotMapped]
public bool Archived { get { return ArchiveDate.HasValue; } }
[NotMapped]
public bool Clotured { get { return ClotureDate.HasValue; } }
...
}
Now I can query my breeze entity like this:
var query = entityQuery.from("Invoices")
.where('id', '==', id)
.toType('Invoice');
The call above will return all properties of my invoice entity (including archived & clotured). It works well.
But I need only a few specific properties (for performance). Then I try:
var query = entityQuery.from("Invoices")
.where('id', '==', id)
.select("id, archived, clotured")
.toType('Invoice');
I got the error: The specified type member 'Archived' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
Very frustrating. Any idea why do I cannot perform such query?
Or maybe does someone have another solution?
Many thanks.
Short version
What you are seeing is perfectly expected. The ArchivedDate
is both a persisted data property and a serialized property. The Archived
property is not persisted but it is serialized. That's why you see data values for both ArchivedDate
and Archived
. However, your remote query ... the LINQ query executed on the server ... may only refer to the persisted properties such as ArchivedDate
. EF knows nothing about calculated properties such as Archived
; they cannot participate in a LINQ query ... not in a where
, select
, orderBy
or any other query. You can't mention something in a query that EF doesn't know about ... and you told EF (properly) to ignore these Archived
and Clotured
calculated properties.
Long version
The [Unmapped] attribute hides the properties from EF ... as it must because Archived
and Clotured
are calculated properties, not persistable data.
The [Unmapped] attribute also hides these properties from the metadata generated from EF. That too is both expected and good.
But this also means that you cannot construct a LINQ query that references these properties. They aren't data properties. They can't be queried by EF. Only data properties and navigation properties can appear in a LINQ query. It is really that simple.
Perhaps you're wondering why the unmapped calculated property values are actually communicated to the JavaScript client, why those values appear in the JSON payload and would populate the like-named Breeze entity properties if you add such properties to the client metadata for Invoice
as "unmapped properties".
To understand why, you must understand the difference between properties that you query with EF and the properties that you serialize with Json.NET. After the EF query completes, the materialized entities have both the data properties (e.g., ArchivedDate) and the calculated properties (Archived). The [NotMapped] attribute doesn't hide a property from Json.NET. Json.NET serializes ALL properties of the materialized object - both data and calculated properties - unless you tell it not to. For example you could hide the Archived property from Json.NET serialization with the [Ignore] attribute.
The toType
is a red herring and has no bearing on the matter.
Remove the ".toType('Invoice')' line from your query. Just go with:
var query = entityQuery.from("Invoices")
.where('id', '==', id)
.select("id, archived, clotured");
This forces breeze to coerce your projection into an Invoice entity type. If you leave it off you will get a true projection, i.e. a plain javascript object with just the properties you have specified, i.e. not an entity.