I would like to extend an existing datamodel in Microstream with a new data object. E.g. I have Customers, with data records in Microstream, and I would like to add Vendors, with their own datastructure and data. As the database is not empty, I cannot start as if their is no data, however adding a list of Vendor to the dataroot doesn't seem to work. Microstream says the list is null when starting, which is correct, but I cannot add my new object to a null list. Can someone explain me how to add a vendor to my 'database' ?
You just need to add this List and store this object with the existing list again.
I received an answer from fh-ms # Microstream:
Hi, you are right, the vendors list is not present in the storage, so the field will be initialized with its default value (null).
There are several possibilities to introduce initial values to new fields.
One rather complex way would be to implement a Legacy Type Handler.
A far more simple one is just lazy initialization in your Customer type:
public List<Vendor> getVendors()
{
if(this.vendors == null)
{
this.vendors = new ArrayList<>()
}
return this.vendors;
}
And that works !
Related
I have an object, Client, with a navigation property that is a list of Order objects. Whenever I retrieve a Client object, I include the list of Orders, with AsNoTracking().
public new IQueryable<Client> FindByConditionNoTracking(Expression<Func<Client, bool>> expression)
{
return this.ClientContext.Set<Client>().Include(s => s.Orders)
.Where(expression).AsNoTracking();
}
In my UpdateClient repository method, I take in a Client object. I then attempt to retrieve that original client from the database (using Include to get the child Orders), map the Client param to the original, and save to the database. Over here, I do not use AsNoTracking, because I specifically want the changes to be tracked.
public new void Update(Client client)
{
var id = client.ClientId;
var original = this.ClientContext.Clients.Include(s => s.Orders).Where(s => s.ClientId == id)
.FirstOrDefault<Client>();
original = _mapper.Map(client, original);
this.ClientContext.Update(original);
}
The error I am getting is that an instance of Order with the same key value is already being tracked. A few problems with that:
Wherever the Client and the child Orders are retrieved for the purposes of display I use AsNoTracking.
The only place where I retrieve without AsNoTracking is where I get the original within this very method.
The bug isn't with the parent property. If I was improperly retrieving the Client elsewhere, wouldn't I have this error with the Client id itself? But the error seems to be only with the navigation property.
All insight is appreciated!
If anyone else runs into this: Automapper, when mapping collections, apparently recreates the entire collection. I solved the above issue by using Automapper.Collections in my mapping configuration. Thanks to Mat J for the tip!
Right now, if I add a field to a Parse object and then save it, the new column shows up in the Parse dashboard.
For example, after running:
let media = new Parse.Object("Media");
media.set("foo", "bar");
await media.save();
I will have a new column called foo.
Is it possible to prevent this from happening?
Yes. This can be done using class-level permissions, which allow you to prevent fields being added to classes.
Parse lets you specify what operations are allowed per class. This lets you restrict the ways in which clients can access or modify your classes.
...
Add fields: Parse classes have schemas that are inferred when objects are created. While you’re developing your app, this is great, because you can add a new field to your object without having to make any changes on the backend. But once you ship your app, it’s very rare to need to add new fields to your classes automatically. You should pretty much always turn off this permission for all of your classes when you submit your app to the public.
You would have to add a beforeSave trigger for every one of your classes, keep a schema of all your keys, iterate over the request.object's keys, and see if there are any that do not belong in your schema. You can then either un-set them and call response.success(), or you can call response.error() to block the save entirely, preferably with a message indicating the offending field(s).
const approvedFields = ["field1", "field2", "field3"];
Parse.Cloud.beforeSave("MyClass", function(request, response) {
let object = request.object;
for( var key in object.dirtyKeys() ) {
if( approviedFields.indexOf(key) == -1 ) return response.error(`Error: Attempt to save invalid field: ${key});
}
response.success();
});
Edit:
Since this got a little attention, I thought I'd add that you can get the current schema of your class. From the docs: https://docs.parseplatform.org/js/guide/#schema
// create an instance to manage your class
const mySchema = new Parse.Schema('MyClass');
// gets the current schema data
mySchema.get();
It's not clear if that's async or not (you'll have to test yourself, feel free to comment update the answer once you know!)
However, once you have the schema, it has a fields property, which is an object. Check the link for what those look like.
You could validate an object by iterating over it's keys, and seeing if the schema.fields has that property:
Parse.Cloud.beforeSave('MyClass', (request, response) => {
let object = request.object;
for( var key in object.dirtyKeys() ) {
if( !schema.fields.hasOwnProperty(key) ) < Unset or return error >
}
response.success();
}
And an obligatory note for anyone just starting with Parse-Server on the latest version ,the request scheme has changed to no longer use a response object. You just return the result. So, keep that in mind.
im working on an Android App.
I have a custom class which has relations with TWO ParseUsers and other fields. As suggested by the docs, I used an array (with key "usersArray") to store the pointers for the two ParseUsers, because I want to be able to use "include" to include the users when i query my custom class. I can create a new object and save it successfully.
//My custom parse class:
CustomObject customObject = new CustomObject();
ArrayList<ParseUser> users = new ArrayList<ParseUser>();
users.add(ParseUser.getCurrentUser());
users.add(anotherUser);
customObject.put("usersArray", users);
//I also store other variable which i would like to update later
customObject.put("otherVariable",false);
customObject.saveInBackground();
Also, i can query successfully with:
ParseQuery<CustomObject> query = CustomObject.getQuery();
query.whereEqualTo("usersArray", ParseUser.getCurrentUser());
query.whereEqualTo("usersArray", anotherUser);
query.include("usersArray");
query.findInBackground( .... );
My problem is when trying to UPDATE one of those CustomObject.
So after retrieving the CustomObject with the previous query, if I try to change the value of the "otherVariable" to true and save the object, I am getting a UserCannotBeAlteredWithoutSessionError or java.lang.IllegalArgumentException: Cannot save a ParseUser that is not authenticated exceptions.
CustomObject customObject = customObject.get(0); //From the query
customObject.put("otherVariable", true);
customObject.saveInBackground(); // EXCEPTION
I can see this is somehow related to the fact im trying to update an object which contains a pointer to a ParseUser. But im NOT modifying the user, i just want to update one of the fields of the CustomObject.
¿There is any way to solve this problem?
Maybe late but Parse users have ACL of public read and private write so you should do users.isAuthenticated() to check if its true or false.
If false then login with the user and retry. Note: you cannot edit info on two users at the same time without logging out and relogging in.
Another thing you can do is use Roles and define an admin role by using ACL which can write over all users.
Here we have a Manifest class that includes list of students and teachers, both could be null.
class Manifest{
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "MANIFEST_STUDENT")
List<String> students = new ArrayList<String>();
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "MANIFEST_TEACHER")
List<String> teachers = new ArrayList<String>();;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "MANIFEST_OTHERS")
List<String> others = new ArrayList<String>();;
}
on the UI, there are two multiple select, one for student and one for teacher that let user choose for current manifest.
here is the problem:
When user deselect all students or teachers from the list(meaning remove all students or teachers from current manifest) and click save, unfortunately nothing can be saved, from UI and database it shows that the multiselect chosen looks the SAME as before.
from service layer, the code is simply like this.
manifest.merge();
It seems we must keep at least one student or teacher for the collection field to make the change valid. So what's going on here and what is the solution? BTW, we are on Openjpa.
Kind of resolve the issue, more like a work around:
Before calling merge(), place several condition checkers to make sure the collection fields are not null
public void save(Manifest entity) {
if(entity.getStudents()==null){
entity.setStudents(new ArrayList<String>());
}
if(entity.getTeachers()==null){
entity.setTeachers(new ArrayList<String>());
}
if(entity.getOthers()==null){
entity.setOthers(new ArrayList<String>());
}
entity.merge();
}
Simple as it, it seems the UI returns those collection fields as null even we initiate them as with empty String lists.
cheers.
Initializing a value in a JPA managed class, such as class Manifest, has no bearing on what, or how, JPA will create the class as JPA maps extracted rows to the class. In particular, the result of:
List<String> students = new ArrayList<String>();
is likely to be:
On creation (by JPA) of a new instance, assign an ArrayList<String>() to students.
JPA overwrites students with the data it extracts - the empty ArrayList is dereferenced/lost.
If your code is clearing a list, such as students, use obj.getStudents().clear(). More likely to run into problems if you call obj.setStudents(someEmptyList).
The issue here is how the JPA manager handles empty datasets: as null or as an empty list. The JPA spec (old, not sure about the just released update) doesn't take a position on this point. A relevant article here.
From your comments, it's apparent that OpenJPA may not be respecting a null value for a Collection/List, while it happily manages the necessary changes for when the value is set to an empty list instead. Someone knowing more about OpenJPA than I may be able to help at this stage - meanwhile you've got a workaround.
I've been facing a problem that is basically the following:
I have a knockout ViewModel which contains observable arrays of items with observable properties and methods.
I need to pull data from the server. The methods need to exist after data is taken from server. So I create a new ViewModel and then update its value from what comes from server. (THIS DOES NOT WORK, THE RESULTING ARRAY HAS NO ITEMS)
If I create, with mapping, a new object using var newObj = ko.mapping.fromJS(data) the resulting Array has items, but its items have no methods. It spoils my Bindings.
The fiddle of my problem: http://jsfiddle.net/claykaboom/R823a/3/ ( It works util you click in "Load Data From The Server" )
The final question is: What is the best way to have items on the final array without making the loading process too cumbersome, such as iterating through every item and filling item's properties in order to keep the previously declared methods?
Thanks,
I changed your code little bit. Check this version of JSFiddle.
var jsonFromServer = '{"ModuleId":1,"Metadatas":[{"Id":1,"MinValue":null,"MaxValue":null,"FieldName":"Teste","SelectedType":"String","SelectedOptionType":null,"IsRequired":true,"Options":[]}]}';
Your code doesnt work because your jsonFromServer variable does not contain methods we need at binding like you described in your question. ( -- > Metadatas )
So we need to define a custom create function for Metadata objects at the mapping process like this :
var mapping = {
'Metadatas': {
create: function(options) {
var newMetaData = new MetadataViewModel(options.parent);
newMetaData.Id(options.data.id);
newMetaData.FieldName(options.data.FieldName);
newMetaData.SelectedType(options.data.SelectedType);
newMetaData.SelectedOptionType(options.data.SelectedOptionType);
newMetaData.IsRequired(options.data.IsRequired);
newMetaData.Options(options.data.Options);
// You can get current viewModel instance via options.parent
// console.log(options.parent);
return newMetaData;
}
}
}
Then i changed your load function to this :
self.LoadDataFromServer = function() {
var jsonFromServer = '{"ModuleId":1,"Metadatas":[{"Id":1,"MinValue":null,"MaxValue":null,"FieldName":"Teste","SelectedType":"String","SelectedOptionType":null,"IsRequired":true,"Options":[]}]}';
ko.mapping.fromJSON(jsonFromServer, mapping, self);
}
You dont have to declare a new viewModel and call ko.applyBindings again. Assigning the updated mapping to current viewModel is enough. For more information check this link. Look out for customizing object construction part.
The final question is: What is the best way to have items on the final
array without making the loading process too cumbersome, such as
iterating through every item and filling item's properties in order to
keep the previously declared methods?
As far as i know there is no easy way to do this with your object implemantation. Your objects are not simple. They contains both data and functions together. So you need to define custom create function for them. But if you can able to separate this like below then you dont have to customize object construction.
For example seperate the MetadataViewModel to two different object :
--> Metadata : which contains only simple data
--> MetadataViewModel : which contains Metadata observableArray and its Metadata manipulator functions
With this structure you can call ko.mapping.fromJSON(newMetaDataArray , {} , MetadataViewModelInstance.MetadataArray) without defining a custom create function at the mapping process.