Content-ID references in ASP.NET OData batch requests - asp.net

I have an ASP.NET WebApi OData service, and I am trying to consume it from JavaScript using JayData.
My model is as follows:
class Parent {
ICollection<Child> Children;
}
class Child {
ICollection<Parent> Parents;
}
I'm trying to add a new parent entity with a new child entity using JayData context, something like:
var child = new db.Children.Child({Parents: []});
var parent = new db.Parents.Parent({Children: [child]});
db.Parents.add(parent);
db.saveChanges();
As a result, JayData sends a batch request to the OData service containing 2 post requests: one for the child and another for the parent, where the child is given Content-ID = 1,
and the parent entity is serialized to JSON as:
{'Children': [{'__metadata': {'uri': '$1'}}]}
On the server side, the CreateEntity method of both Parent and Child EntitySetControllers gets executed, but the Parent argument has an empty collection of Children, i.e. it ignores the Content-ID reference to the new Child entity from the request.
Is there a convenient way to make it work in ASP.NET OData, or do I have to parse the Parent's JSON from the request and check whether such references exist by hand?

Related

Use PathElement in objectify query as ancestor

I am migrating from google datastore api to objectify(i have used datastore at compute engine and migrating to 6.0a1 objectify). In previous version i used this code to query users:
KeyFactory userKeyFactory = datastore.newKeyFactory()
.addAncestors(PathElement.of("UserList", "default"))
.setKind(USER_KIND);
//...save entity
Query<Entity> query = Query.newEntityQueryBuilder()
.setKind(USER_KIND)
.setFilter(PropertyFilter.hasAncestor(datastore.newKeyFactory().setKind("UserList").newKey("default")))
.build();
QueryResults<Entity> queryResults = datastore.run(query);
List<User> result = new ArrayList<>();
queryResults.forEachRemaining(entity -> result.add(transformUser(entity)));
return result;
Now i am trying to make same query with ancestor. However objectify don't work with PathElement. So following code:
ofy().load().type(User.class).ancestor(PathElement.of("UserList", "default")).list()
fails with
java.lang.IllegalArgumentException: No class 'com.google.cloud.datastore.PathElement' was registered
at com.googlecode.objectify.impl.Registrar.getMetadataSafe(Registrar.java:115)
at com.googlecode.objectify.impl.Keys.getMetadataSafe(Keys.java:56)
at com.googlecode.objectify.impl.Keys.getMetadataSafe(Keys.java:65)
at com.googlecode.objectify.impl.Keys.rawKeyOf(Keys.java:47)
at com.googlecode.objectify.impl.Keys.anythingToRawKey(Keys.java:117)
at com.googlecode.objectify.impl.QueryImpl.setAncestor(QueryImpl.java:203)
at com.googlecode.objectify.impl.SimpleQueryImpl.ancestor(SimpleQueryImpl.java:69)
at com.googlecode.objectify.impl.LoadTypeImpl.ancestor(LoadTypeImpl.java:23)
What is proper way to use PathElement with objectify? I see i can create com.googlecode.objectify.Key and pass it as ancestor but it require class, but i don't have UserList class (it is small application, all entities related to single group).
I tried to use this code:
ofy().load().type(User.class).ancestor(datastore.newKeyFactory().setKind("UserList").newKey("default")).list()
Now it fails that User don't have field with #Parent annotation. Here is stacktrace:
com.googlecode.objectify.LoadException: Error loading Key{projectId=projectId, namespace=, path=[PathElement{kind=UserList, id=null, name=default}, PathElement{kind=User, id=1, name=null}]}: Loaded Entity has parent but com.package.model.User has no #Parent
at com.googlecode.objectify.impl.EntityMetadata.load(EntityMetadata.java:84)
at com.googlecode.objectify.impl.LoadEngine.load(LoadEngine.java:187)
at com.googlecode.objectify.impl.LoadEngine$1.nowUncached(LoadEngine.java:145)
at com.googlecode.objectify.impl.LoadEngine$1.nowUncached(LoadEngine.java:131)
at com.googlecode.objectify.util.ResultCache.now(ResultCache.java:30)
at com.googlecode.objectify.impl.Round$1.nowUncached(Round.java:66)
at com.googlecode.objectify.util.ResultCache.now(ResultCache.java:30)
at com.googlecode.objectify.impl.HybridQueryResults.lambda$load$1(HybridQueryResults.java:87)
at com.google.common.collect.Iterators$5.transform(Iterators.java:757)
at com.google.common.collect.TransformedIterator.next(TransformedIterator.java:48)
at com.google.common.collect.MultitransformedIterator.next(MultitransformedIterator.java:66)
at com.google.common.collect.Iterators$4.computeNext(Iterators.java:623)
at com.google.common.collect.AbstractIterator.tryToComputeNext(AbstractIterator.java:145)
at com.google.common.collect.AbstractIterator.hasNext(AbstractIterator.java:140)
at com.googlecode.objectify.impl.HybridQueryResults.hasNext(HybridQueryResults.java:92)
at com.googlecode.objectify.util.IteratorFirstResult.nowUncached(IteratorFirstResult.java:31)
at com.googlecode.objectify.util.ResultCache.now(ResultCache.java:30)
at com.googlecode.objectify.LoadResult.now(LoadResult.java:25)
at com.package.UserObjectifyRepository.getUserByEmail(UserObjectifyRepository.java:43)
...
Caused by: java.lang.IllegalStateException: Loaded Entity has parent but com.package.model.User has no #Parent
at com.googlecode.objectify.impl.KeyMetadata.setKey(KeyMetadata.java:142)
at com.googlecode.objectify.impl.KeyMetadata.setKey(KeyMetadata.java:122)
at com.googlecode.objectify.impl.KeyPopulator.load(KeyPopulator.java:24)
at com.googlecode.objectify.impl.translate.ClassPopulator.load(ClassPopulator.java:118)
at com.googlecode.objectify.impl.translate.ClassTranslator.loadSafe(ClassTranslator.java:109)
at com.googlecode.objectify.impl.translate.NullSafeTranslator.load(NullSafeTranslator.java:21)
at com.googlecode.objectify.impl.EntityMetadata.load(EntityMetadata.java:80)
.. 125 more
I suppose correct way to fix this is to have parent field in my User entity, something like this:
#Parent
private UserList userList;
But i haven't such entity "UserList", i need ancestor just to make query with strong consistency.
UPD: Error is gone if i adding this code:
import com.google.cloud.datastore.Key;
#Parent
private Key userList;
Is it proper way to make consistent query?
The #Parent annotation requires a key.
You just have to create a key, so it can understand that Parent/Child relationship.

Parent - child relationships in Realm

I am trying to move my Core Data object graph to Realm.
Currently I have an Entity called DBNode, which has
#NSManaged var children: NSSet
#NSManaged var parentNode: DBNode
where I can store the parent node and all the children of the node.
When I have a Realm object called RLMNode: RLMObject with
dynamic var children = RLMArray(objectClassName: RLMNode.className())
dynamic var parent = RLMNode()
it crashes when first trying to add an object.
Can I do this hierarchical structure in Realm?
Edit:
It seems I can do this, and just have one node in the array:
dynamic var parent = RLMArray(objectClassName:RLMNode.className())
Would this be the recommended way? Is it as quick as the object graph in Core Data?
The reason for the crash is probably that initialisation becomes recursive, when you create a node it creates a node for its parent, which in turn needs a node etc. You can check the stack trace to see if that is the case.
Realm in Swift supports optional object properties, and they are set to nil by default, so you can do something like this:
class DBNode: RLMObject {
dynamic var name = ""
dynamic var parent: DBNode?
dynamic var children = RLMArray(objectClassName: DBNode.className())
}
Arrays can in fact not be nil, and they do have to be initialised, but they can be empty.
Beware that you might get an exception thrown if you add both an object and its parent (or children) to the Realm explicitly.s They will be added automatically since you cannot link to objects that are not persisted.

how to hide metadata in web api 2, odata

I have defined odata route using MapODataServiceRoute in my WebApiConfig.
config.Routes.MapODataServiceRoute("CompanyoOdata", "odata", GetImplicitEdm(config));
private static IEdmModel GetImplicitEdm(HttpConfiguration config)
{
ODataModelBuilder builder = new ODataConventionModelBuilder(config, true);
builder.EntitySet<Company>("Company");
builder.EntitySet<Photo>("Photos");
builder.EntitySet<Country>("Country");
return builder.GetEdmModel();
}
The data service works just fine. But I want to achieve few things.
I don't want to expose my metadata or associations because i'm using it internally and will not need metadata. How can I restrict access to these information (i.e restrict access to http://www.sample.com/odata/#metadata or http://www.sample.com/odata/$metadata)
secondly, I want to ignore some properties from getting serialized. I found two ways of doing this.
Using data contracts and marking properties with [DataMember] attribute or [IgnoreDataMember] attribute
Using Ignore method on EntitySet when building the model
I can't use the first method as I'm using Database first approach for entity framework hence can't decorate the entity with attributes. I thought I can achieve this by using MetaDataType, but it seems it only works for DataAnnotations.
I used second method with success, but you can't pass more than one property in the ignore method. Has to do it to individual property that I need to ignore, which is a bit tedious. Is there another way to do this?
any help really appreciated.
If want to hide metadata (/$metadata) or service document (/), can remove the the MetadataRoutingConvention from existing routing conventions, e.g.:
var defaultConventions = ODataRoutingConventions.CreateDefault();
var conventions = defaultConventions.Except(
defaultConventions.OfType<MetadataRoutingConvention>());
var route = config.MapODataServiceRoute(
"odata",
"odata",
model,
pathHandler: new DefaultODataPathHandler(),
routingConventions: conventions);
If only expose a few properties per type, can use ODataModelBuilder instead of ODataConventionModelBuilder. E.g., some example:
ODataModelBuilder builder = new ODataModelBuilder();
EntityTypeConfiguration<Customer> customer = builder.EntitySet<Customer>("Customers").EntityType;
customer.HasKey(c => c.Id);
customer.Property(c => c.Name);

Retrieving the parent URI of a BluePrinted component based on the URI of a child component

Does anyone know how can I find the URI of a parent Component based on the URI of a shared or localized component in a child publication in SDL Tridion using the Core Service?
You can use ComponentData.BluePrintInfo.OwningRepository.IdRef to get the TcmUri of the publication that "owns" that component. This is the first publication "going up" where the component is either created or localized.
Then you can use something like this to get you the component Uri in the right context:
internal string GetUriInBlueprintContext(string itemId, string publicationId)
{
if (TcmUri.UriNull == itemId)
return null;
TcmUri itemUri = new TcmUri(itemId);
TcmUri publicationUri = new TcmUri(publicationId);
TcmUri inContext = new TcmUri(itemUri.ItemId, itemUri.ItemType, publicationUri.ItemId);
return inContext.ToString();
}
The TcmUri class is part of the Tridion.Common.dll which you can reference from your project too.
Here's is simpler approach than the one that Nuno is proposing and you don't need to reference any dlls
var parentComponentid = ClientAdmin.GetTcmUri(component.Id, component.BluePrintInfo.OwningRepository.IdRef, null);
GetTcmUri method is good for getting any TcmUris - just pass publication id you want your item in and Id of item in current publication. This way you can also find ID of a given item in particular child publication

Problem with Adding multiple child objects to an existing entity in EF

I need to add multiple child objects to an existing parent Object. I am instantiating my parent object and sets it Key/Id in my UI processing layer(to which my child objects will be added).
Parent parenttoModify = new Parent();
parenttoModify.Parent_Id = 5; //this comes from some Index of a dropdown or a key column of a grid, i Have put a dummy value here for example
parenttoModify.Children.Add(child);
parenttoModify.Children.Add(child2);
DataAccess.ModifyParent(parenttoModify);
In my data access layer I have a method like :
public static bool ModifyParent(Parent parent)
{
int recordsAffected=0;
using (TestEntities testContext = new TestEntities())
{
testContext.Parents.Attach(parent);
var parentEntry = testContext.ObjectStateManager.GetObjectStateEntry(parent);
parentEntry.ChangeState(System.Data.EntityState.Modified);
recordsAffected = testContext.SaveChanges();
}
return recordsAffected > 0 ? true : false;
}
I get an error when testContext.Parent.Attach(parent) is called. It says:
Object with same key already exist.
I am not sure why is this happening since i am not adding a parent object, I am just attaching it and adding child objects within it.
Any idea where I am going wrong?
Where do you add childs? I guess you are not showing all code. When you call Attach or AddObject EF always attaches or adds all entities from object graph which are not known (tracked) to context at the moment. The exception says that some entity - probably parent - is already tracked by the context. So you have either:
Used shared context (you are creating a new instance in ModifyParent so it should not be a case)
Load parent from the context first in ModifyParent
Called Attach or AddObject on any child before attaching parent.
All these operations lead to the exception you are receiving.

Resources