The object extraMetadata is undefined and throwing an error on line 247 of breeze.labs.dataservice.sharepoint.js
rawEntity.__metadata = { 'type': aspect.extraMetadata.type };
I suspect it is because I have not defined the type found in __metadata object on my entity definitions for breeze. Any suggestions on how to define my type correctly would be very welcome! Here is my type definition for one of the objects.
models.Project = {
name: 'Project',
defaultResourceName: 'getbytitle(\'Projects\')/items',
dataProperties: {
ID: {
type: breeze.DataType.Int32
},
Title: {
nullable: false
},
StatusId: {
type: breeze.DataType.Int32,
nullable: false
},
SelectedApproverId: {
type: breeze.DataType.Int32,
nullable: false
},
Created: {
type: breeze.DataType.DateTime
},
Modified: {
type: breeze.DataType.DateTime
}
},
navigationProperties: {
Status: {
type: "Status",
foreignKeyNames: ['StatusId'],
hasMany: false
},
SelectedApprover: {
type: "User",
foreignKeyNames: ["SelectedApproverId"]
}
}
};
UPDATE: 11/11/2013
If I run the following query:
return breeze.EntityQuery
.from(metadataStore.getEntityType('Project').defaultResourceName)
.orderBy('Created desc')
.using(manager)
.execute()
.then(function (data) {
console.log(data.results);
return data.results;
});
the results are an array of simple JavaScript objects, not Breeze Entities, that lack an __metadata properties. I'm trying to figure out why this is the case.
Update: 11/12/2014
I have confirmed that this issue presents itself when I have multiple entities defined under navigationProperties.
Please be sure you are using BreezeJS v.1.4.12 or later.
To be clear, the code to which you refer is on line 147 (not 247) of the breeze.labs.dataservice.sharepoint.js file in my possession.
It's located within the _createChangeRequest where it is preparing to save a modified entity. I'll assume that you have queried a Product entity, made changes to it, and are saving it back when the error occurs.
I don't believe the problem will be traced to how you defined the metadata for your Product type.
You should NOT define a __metadata property for your type. The __metadata property is something we expect SharePoint (any OData source in fact) to add to the JSON entity data that it sends to the client when you query that OData source for entities.
__metadata wouldn't be defined for results returned by a projection but then your issue concerns a modified entity so I'm assuming that you acquired this entity through a normal query ... one that did not have a select clause.
I'd like to know if you see the __metadata property in the JSON payload of a query that retrieved the entity you were modifying. Please examine the network traffic from the query request. If you don't see it there, we have to find out why the server didn't send it.
Background
The __metadata property on the JSON node is a crucial part of the contract with the SharePoint OData server. That's how the Breeze client learns about the entity's type and its etag.
Look at the jsonResultsAdapter.visitNode and updateEntityNode methods. You'll see how the adapter uses __metadata to determine the EntityType for that data. You'll also see that the adapter moves the __metadata to the adapter result's extraMetadata property. BreezeJS subsequently moves that "extra metadata" from this result object to the entity's entityAspect.extraMetadata property.
Does this seem tortured? It is tortured. OData requires extra information to be carried around with the entity (specifically the etag) without which the server simply will not update or delete the entity. We have to squirrel that info away somewhere, out of your hair, and then bring it back when we make save requests to the server. We put it on the entityAspect in keeping with that property's role as the keeper of the "entity-ness" that has nothing to do with your object's business purpose and everything to do with how it is persisted.
So much for the why. Where is the bug?
The bug
The underlying bug is that this __metadata from the SharePoint OData source has disappeared. We don't know how it disappeared yet. But we're in big trouble without it.
The sharepoint adapter should give a better message when extraMetadata is missing. We actually look for that problem a few lines later; see adjustUpdateDeleteRequest:
var extraMetadata = aspect.extraMetadata;
if (!extraMetadata) {
throw new Error("Missing the extra metadata for an update/delete entity");
}
That test appears too late. I'll make a note to move the test up.
But such a fix will only cause the save to fail with a better message. It won't tell you how to fix it.
So let's work on finding where the __metadata disappeared ... starting with whether it ever arrived in the first place.
I await your report.
Update 17 July 2014
I'm still waiting to hear if you are seeing the __metadata property in the payload of the response to the original entity query.
Meanwhile, I checked the OData specs (plural) for references to the __metadata property. It appears that the __metadata property has always been optional. It follows that an OData provider need not send or honor the etag ... and we know that this is possible because Web API 2 OData didn't support etags ... a defect soon to be corrected.
See the OData v.2 spec where it describes JSON format. Search for the term "__metadata".
The OData v.3 spec also calls for the __metadata property in a JSON response (at least a JSON verbose response).
But ... heavy sigh ... it appears that the __metadata property is gone from the v.4 spec and that the metadata information is supplied entirely through JSON annotations. The DataJS library (used by many but not all BreezeJS OData adapters) may map those annotations into the node's __metadata property but I can't confirm it yet. We have some work to do coping with all of these variations.
In the meanwhile, I think all BreezeJS OData dataservice adapters should take a more defensive position regarding extra metadata and should simply ignore the omission rather than throw exceptions.
We'll make these defensive changes very soon.
Of course the server will reject your update or delete request if the OData service actually requires an etag or other metadata. I don't know what we can do about that just yet.
There hasn't been a post in a while but I am going to share what I found as the problem and how I resolved it for me (because it took me a long time).
Basically the breeze.labs.dataservice.sharepoint adapter has a function serverTypeNameToClientDefault() that expects the SharePoint custom list type as returned by REST/OData in the __metadata "type" field to be in the exact format of:
SP.Data.**mylistname**sListItem** (notice the "sListItem" suffix; ; Ex. SP.Data.CustomersListItem)
This function does a string regex to extract the Breeze entity name from the SharePoint type and uses that name to look up the entity in the metadata store ("Customer" in the above example). If there is no match, Breeze will not find your entity and will return a basic object instead of a Breeze entity. Therefore your REST JSON result returned from SharePoint, even though it does have the __metadata property is not converted into a Breeze entity that contains the property entityAspect.extraMetadata, among other things. This is what leads to the error "Unable to get property 'type' of undefined or null reference"
For my solution, since in my case I don't care as much what the URL of my custom lists are, I just made sure that when my custom list was provisioned by SharePoint that it resulted in a name according to what Breeze expects. You accomplish this by setting the Url attribute of the ListInstance element like this:
<ListInstance
Title="My Customers"
OnQuickLaunch="TRUE"
TemplateType="10000"
Url="Lists/Customers" <!-- List/Customer will not work -->
Description="My List Instance">
...
The better solution would be to make the serverTypeNameToClientDefault() function more robust or fix it to my needs locally but hopefully this can be addressed in a future version of the adapter.
Note that I have tested this solution with the following configurations (not all dependencies listed):
Breeze.Client 1.4.9 with Breeze.DataService.SharePoint 0.2.3
Breeze.Client 1.5.0 with Breeze.DataService.SharePoint 0.3.2
Also note that the 0.3.2 version of the adapter now displays a better error message when this happens as mentioned above -- "Missing the extra metadata for an update/delete entity"; but it doesn't fix the problem.
Hope this helps someone.
For breeze v1.4.14 and breeze labs sharepoint 2013 v0.2.3 i am using small fix in file breeze.labs.dataservice.sharepoint.js.
At the end of function
function visitNode(node, mappingContext, nodeContext)
just before
return result;
i just set property extraMetadata like this:
result.extraMetadata = node.__metadata;
This seems to fix problem when i try to save modified entity back to sharepoint.
Sorry folks for the long overdue aspect of this, but I got the bug with the extra "s" resolved today... FINALLY. You can track the issue here: https://github.com/andrewconnell/breeze.js.labs/issues/6
This all stemmed from a very incorrect assumption I made. It's been fixed in version 0.6.2 of the data service adapter for SharePoint. Note that you MUST use the same name for your entity when creating it in the metadata store as the list where the data is coming from.
I resolved my issue with multiple navigationProperties on an entity by editing line 319 of breeze.labs.dataservice.sharepoint.js v.0.10.0
I changed:
if (entityType._mappedPropertiesCount <= Object.keys(node).length - 1)
to:
if (entityType.dataProperties.length <= Object.keys(node).length - 1)
It looks like the _mappedPropertiesCount includes the navigationProperties count too. e.g. dataProperties.length + navigationProperties.length
The query node was then thought to not contain a full set of properties for the entity (it was assumed to be the result of a partial projection).
It therefore wasn't being treated as an entity, its metadata wasn't being set, and it ultimately wasn't being added to the cache.
It worked with only one navigationProperty as there were two extra items in Object.keys(node), __Metadata and ID. So it would still pass the test with one navigationProperty, but not two or more.
Related
This is from the category "unexpected behavior" - take the following query (you can paste it in Graph Explorer):
https://graph.microsoft.com/v1.0/users?$filter=idc eq 'test'
This returns status code 400 and "Property 'idc' does not exist as a declared property or extension property." Which is a sensible and understandable response.
Now, if try to $select this property:
https://graph.microsoft.com/v1.0/users?$select=idc
I get a result I totally don't expect:
{
"#odata.context": "https://graph.microsoft.com/v1.0/$metadata#users(idc)",
"#odata.nextLink": "https://graph.microsoft.com/v1.0/users?$select=idc&$skiptoken=cut",
"value": [
{},
{},
...
{}
]
}
(a list of empty objects; asking for a single user with that invalid property name returns me an emtpy response).
So my question is - why does $filter error out and $select does not? Would there be a way to force $select to error out too? (eg I am using the /beta endpoint and a property name changes - I want my code to fail to find out)
Sorry for the late answer. We had a discussion on this and would love to get some of your thoughts (and the thoughts of other developers). We don't have a clear answer on this yet.
There are 2 schools of thought here:
Make $select and $filter behave consistently when dealing with properties that don't exist.
It's OK for these to differ in behavior, since the caller’s intent when specifying $select is likely different than that of $filter. The service cannot ignore a property specified in $filter because it completely changes the set of objects that are returned. However, $select does not change the set of objects but only drops the properties that are not available. Hence $select and $filter do not need to be consistent.
Thoughts?
Hope this helps,
I have two Relay mutations that I'm nesting to first add an object then set its name. I believe what I'm passing to the second mutation is in fact data fetched by Relay, but it appears to disagree with me. The code in the React view is as follows:
Relay.Store.update(
new AddCampaignFeatureLabelMutation({
campaign: this.props.campaign
}),
{
onSuccess: (data) => {
Relay.Store.update(
new FeatureLabelNameMutation({
featureLabel: data.addCampaignFeatureLabel.featureLabelEdge.node,
name: this.addLabelInputField.value
})
);
},
onFailure: () => {}
}
);
This does work, but gives me a warning:
Warning: RelayMutation: Expected prop `featureLabel` supplied to `FeatureLabelNameMutation` to be data fetched by Relay. This is likely an error unless you are purposely passing in mock data that conforms to the shape of this mutation's fragment.
Why does Relay think the data isn't fetched? Do I maybe need to explicitly return the new featureLabel in the payload somehow?
I ran into the same problem and it took me some time to figure out what was going on, so this might help others:
As the warning says, you have to provide an entity to the mutation that was fetched by Relay. BUT what the warning does not say is that it has to be fetched with the mutation in mind.
So basically you have to add the mutation you are going to execute on it in the future in the initial query like this:
fragment on Person {
firstname,
lastname,
language,
${UpdatePersonMutation.getFragment('person')}
}
This will add the necessary pieces to the entity in the store which are needed by the mutation.
In you case what you have to do is to add the FeatureLabelNameMutation getFragment to your AddCampaignFeatureLabelMutation query. This will bring back your featureLabel entity with the necessary information for the FeatureLabelNameMutation to succeed without warning.
The Relay documentation is very very poor on this and many other areas.
Relay expects any fragments for your mutation to come from your props. Since you're using data coming from your callback and not something from your container props Relay raises that warning.
Take a look at the source: https://github.com/facebook/relay/blob/master/src/mutation/RelayMutation.js#L289-L307
Hello I am trying to execute a query using breezejs 1.3.4 . My query is the following
function getContacts() {
var query = breeze.EntityQuery
.from("Contacts").where("Desc", "startsWith", "P");
return manager.executeQuery(query)
.then(getSucceeded).fail(getFailed);
}
"Desc" is a string property in my "Contacts" C# backend model. Tha problem is that the Query URL is formatted like this .../api/Application/Contacts?$filter=startswith(Desc%2Ctime'P')%20eq%20true
The word time is added before "P" and I get a this exception in the Response
{"$id":"1","$type":"System.Web.Http.HttpError, System.Web.Http","Message":"The query specified in the URI is not valid.","ExceptionMessage":"Unrecognized 'Edm.Time' literal 'time'P''
If in the comparison I use a lower case "p" then the Url is costructed as it should be like this
"$filter=startswith(Desc%2C'p')%20eq%20true` .
I don't have the same problem when using other UpperCase letters of the english alphabet.
Does anyone have an idea what am I missing, I can't figure out why the word "time" is added in that specific query?
Thank you.
We were able to reproduce the problem.
While the exception message is confusing, I believe you might be getting the error because you have not associated that the resource name 'Contacts' with an EntityType of presumably 'Contact'.
What is happening is that when Breeze tries to build the query it will usually use its metadata to correctly format the url query string to send to the server. The critical part of this process involves determining the EntityType associated with the resourceName given in the 'from' clause of the query ('Contacts' in your case). Breeze uses a resource name to entity type map to do this, but if it cannot find a mapping, it will still continue to build the url because we still need to support ad-hoc requests against endpoints for which we have no metadata.
You can update this map yourself via the MetadataStore.setEntityTypeForResourceName method. If you are using the Entity Framework, this map is initially populated based on the EntityType name/Entity Set name mapping that is part of your EDMX model. So in your case you can either modify your EDMX model or call the setEntityTypeForResourceName method directly.
Unfortunately, without the metadata Breeze has to infer the datatypes in the query. So in your case
"Desc", "startsWith", "P"
since Breeze can't determine that "Desc" is a 'string' property of your Contract EntityType it tries to infer the 'dataType' of "Desc" based on the value 'P'. Unfortunately, 'P' is a valid ISO8601 'duration' value ( a way to express a 'Time' datatype), so Breeze incorrectly tries to construct a query string with 'P' treated as a 'Time' constant. This is why your code works with a lower case 'p' ( not a valid duration value).
This inference logic can certainly be improved and we have a fix that will allow us to do exactly that coming out in the next release. However... the better and more general solution to this type of issue is to get the 'resourceName/entityType' mappings correct in the first place.
Hope this helps.
i´m using the Papa's course CCJS code to investigate Breeze.js and SPA. Using this code i´m trying to manage aditional information that cames from server but that is not an Entity contained in the Metadata that cames from EntityFramework.
So i created a NO-DB class called Esto and a Server method like Lookups:
[HttpGet]
public object Informacion()
{
var a = new Esto(....);
var b = new Esto(.....);
var c = new Esto(......);
return new {a,b,c};
}
then in model.js inside configureMetadataStore i call:
metadataStore.addEntityType({
shortName: "Esto",
namespace:"CodeCamper",
dataProperties:{
id: {dataType: breeze.DataType.Int32,isPartOfKey: true},
name: {dataType: breeze.DataType.String}
}
};
and also define in the model entityNames array: esto:'Esto' as an Entity
now in the context.js i load this creating a server side method like getLookups but called getInformacion:
function getInformacion(){
return EntityQuery.from('Informacion')
.using(manager).execute()
}
and then inside primeData in the success method call this:
datacontext.informacion = {
esto: getLocal('Esto',nombre)};
where getLocal is:
function getLocal(resource, ordering)
{
var query = EntityQuery.from(resource).orderBy(ordering);
return manager.executeQueryLocally(query);
}
I get an error in the query contained in the getLocal that states that Can not find EntityType for either entityTypeName: 'undefined' or resourceName:'Esto'.
What i´m doing wrong?
Thanks
You were almost there! :-) Had you specified the target EntityType in the query I think it would have worked.
Try this:
var query = EntityQuery.from(resource).orderBy(ordering).toType('Esto');
The toType() method tells Breeze that the top-level objects returned by this query will be of type Esto.
Why?
Let's think about how Breeze interprets a query specification.
Notice that you began your query, as we usually do, by naming the resource which will supply the data. This resource is typically a path segment to a remote service endpoint, perhaps the name of a Web API controller method ... a method named "Foos".
It's critical to understand that the query resource name is rarely the same as the EntityType name! They may be similar - "Foos" (plural) is similar to the type name "Foo" (singular). But the resource name could be something else. It could be "GetFoos" or "GreatFoos" or anything at all. What matters is that the service method returns "Foo" entities.
Breeze needs a way to correlate the resource name with the EntityType name. Breeze doesn't know the correlation on its own. The toType() method is one way to tell Breeze about it.
Why do remote queries work without toType()?
You generally don't add toType() to your queries. Why now?
Most of the time [1], Breeze doesn't need to know the EntityType until after the data arrive from the server. When the JSON query results includes the type name (as they do when they come from a Breeze Web API controller for example), Breeze can map the arriving JSON data into entities without our help ... assuming that these type names are in metadata.
Local cache queries are different
When you query the cache ... say with executeQueryLocally ... Breeze must know which cached entity-set to search before it can query locally.
It "knows" if you specify the type with toType(). But if you omit toType(), Breeze has to make do with the query's resource name.
Breeze doesn't guess. Instead, it looks in an EntityType/ResourceName map for the entity-set that matches the query resource name.
The resource name refers to a service endpoint, not a cached entity-set. There is no entity-set named "Informacion", for example. So Breeze uses an EntityType/ResourceName map to find the entity type associated with the query resource name.
EntityType/ResourceName
The EntityType/ResourceName map is one of the items in the Breeze MetadataStore. You've probably never heard of it. That's good; you shouldn't have to think about it ... unless you do something unusual like define your own types.
The map of a new MetadataStore starts empty. Breeze populates it from server metadata if those metadata contain EntityType/Resource mappings.
For example, the Breeze EFContextProvider generates metadata with mappings derived from DbSet names. When you define a Foo class and exposed it from a DbContext as a DbSet named "Foos", the EFContextProvider metadata generator adds a mapping from the "Foos" resource name to the Foo entity type.
Controller developers tend to use DbSet names for method names. The conventional Breeze Web API controller "Foo" query method looks like this:
[Get]
public IQueryable<Foo> Foos() {...}
Now if you take a query such as this:
var query = EntityQuery.from('Foos').where(...);
and apply it to the cache
manager.query.executeLocally(query).then(...);
it just works.
Why? Because
"Foos" is the name of a DbSet on the server
The EFContextProvider generated metadata mapping ["Foos" to Model.Foo]
The Web API Controller offers a Foos action method.
The BreezeJS query specifies "Foos"
The executeLocally method finds the ["Foos"-to-Model.Foo] mapping in metadata and applies the query to the entity-set for Foo.
The end-to-end conventions work silently in your favor.
... until you mention a resource name that is not in the EntityType/ResourceName map!
Register the resource name
No problem!
You can add your own resource-to-entity-type mappings as follows:
var metadataStore = manager.metadataStore;
var typeName = 'some-type-name';
var entityType = metadataStore.getEntityType(typeName);
metadataStore.setEntityTypeForResourceName(resource, entityType);
Breeze is also happy with just the name of the type:
metadataStore.setEntityTypeForResourceName(resource, typeName);
In your case, somewhere near the top of your DataContext, you could write:
var metadataStore = manager.metadataStore;
// map two resource names to Esto
metadataStore.setEntityTypeForResourceName('Esto', 'Esto');
metadataStore.setEntityTypeForResourceName('Informacion', 'Esto');
Don't over-use toType()
The toType() method is a good short-cut solution when you need to map the top-level objects in the query result to an EntityType. You don't have to mess around with registering resource names.
However, you must remember to add toType() to every query that needs it. Configure Breeze metadata with the resource-to-entity-type mapping and you'll get the desired behavior every time.
Notes
[1] "Most of the time, Breeze doesn't need to know the EntityType until after the data arrive from the server." One important exception - out of scope for this discussion - is when the query filter involves a Date/Time.
I think that the problem here is that you are assuming that entity type names and resource names are the same thing. A resource name is what is used to execute a query
var q = EntityQuery.from(resourceName);
In your case the "resourceName" is "Informacion" and the entityType is actually "Esto". Breeze is able to make this connection on a remote query because it can examine the results returned from the server as a result of querying "Informacion" and seeing that they are actually "Esto" instances. This is not possible for a local query because Breeze doesn't know what local collection to start from.
In this case you need to give Breeze a little more information via the MetadataStore.setEntityTypeForResourceName method. Something like this:
var estoType = manager.metadataStore.getEntityType("Esto");
manager.metadataStore.setEntityTypeForResourceName("Informacion", estoType);
Note that this is not actually necessary if the resource was defined via Entity Framework metadata, because Breeze automatically associates all EF EntitySet names to resource names, but this information isn't available for DTO's.
Note also that a single entity type can have as many resourceNames as you like. Just make sure to register the resourceNames before you attempt a local query.
Breeze cannot create entities. It does the query for Metadata (which is in my Controller class) and it returns data with a 200 code. Then it queries the data, and returns with a 200 code. Both sets of return data appear to be json format on the surface.
Looking further into the Metadata, it appears to be json serializing the xml metadata. This does not feel right to me - and on the live example on the breeze site, does not appear to be doing this.
Anyone have any tips on what might be causing this? Here is how the metadata starts:
"{\"?xml\":{\"version\":\"1.0\",\"encoding\":\"utf-8\"},\"schema\":{\"namespace\":
When the service returns data, I get an error, with the full & correct json response. The internalError reports "Unable to get property 'createCtor' of undefined or null reference"
It gets to this line in the mergeEntity function, when I debug breeze.js:
targetEntity = entityType._createEntityCore();
Then in the ctr.prototype.getEntityCtor function - it fails to get the entity's constructor from the metadataStore's _typeRegistry property using this line:
var aCtor = typeRegistry[this.name] || typeRegistry[this.shortName];
after that, the error is thrown and we end up in the catch of the executeQuery function.
The issue I was having should have been obvious. My bundling configuration was not including knockout scripts after the scripts have been updated. Be sure that you have a binding library loaded (and it's loading properly) if you experience this issue.