Spring Data MongoRepository Saving Objects with Differing Numbers of Fields - spring

I am storing game states in a MongoDB database and am using Spring Data to manage my database interactions. I am new to Spring Data and am unsure how to deal with the following scenario.
I have a document of type "Game" with a bunch of properties such as id, timestamp, etc... One of these properties is a list of actions taken by users. These actions are of the form:
{ type: 2 }, {type: 3, value: 4}, {type: 5, id: 1234}, {type 6}, {type: 5, value: 6, id: 56}
In other words, an action can have three properties: type, value, and id. However, not every action requires all three values to be stored. I want to avoid having a bunch of null values in my database, and would like my database to just not include and id or a value if they are not specified.
Using Spring Data's MongoRepository model, I am not sure how to achieve this. I can create a CRUD Game class and have one of its properties be a list of Action (where Action itself is a CRUD class with properties type, value, and id), but won't that end up storing null values in the database if I do not specify the value or id?
In short, how can I use Spring Data's MongoRepository but still maintain the flexibility of being able to store lists of objects with varying parameters or object types in general.

I will explain how varying fields are handled with an example. The following Game.java POJO class represents the object mapping to the game collection document.
public class Game {
String name;
List<Actions> actions;
public Game(String name, List<Actions> actions) {
this.name = name;
this.actions = actions;
}
public String getName() {
return name;
}
public List<Actions> getActions() {
return actions;
}
// other get/set methods, override, etc..
public static class Actions {
Integer id;
String type;
public Actions() {
}
public Actions(Integer id) {
this.id = id;
}
public Actions(Integer id, String type) {
this.id = id;
this.type = type;
}
public Integer getId() {
return id;
}
public String getType() {
return type;
}
// other methods
}
}
For the Actions class you need to provide constructors with the possible combinations. Use the appropriate constructor with id, type, etc. For example, create a Game object and save to the database:
Game.Actions actions= new Game.Actions(new Integer(1000));
Game g1 = new Game("G-1", Arrays.asList(actions));
repo.save(g1);
This is stored in the database collection game as follows (queried from mongo shell):
{
"_id" : ObjectId("5eeafe2043f875621d1e447b"),
"name" : "G-1",
"actions" : [
{
"_id" : 1000
}
],
"_class" : "com.example.demo.Game"
}
Note the actions array. As we had stored only the id field in the Game.Actions object, only that field is stored. Even though you specify all the fields in the class, only those provided with values are persisted.
These are two more documents with Game.Actions created with type only and id + type using the appropriate constructors:
{
"_id" : ObjectId("5eeb02fe5b86147de7dd7484"),
"name" : "G-9",
"actions" : [
{
"type" : "type-x"
}
],
"_class" : "com.example.demo.Game"
}
{
"_id" : ObjectId("5eeb034d70a4b6360d5398cc"),
"name" : "G-11",
"actions" : [
{
"_id" : 2,
"type" : "type-y"
}
],
"_class" : "com.example.demo.Game"
}

Related

update detail object from parent request spring

i have two classes (entity) that reflect a master/detail dependency
public class Invoice {
String id
String total
String number
...
#OneToMany
List<InvoiceDetail> invoiceDetail
}
public class InvoiceDetail {
String id
String code
String price
....
}
for each resource service i have a method (reflection implementation) that does a partial update individually
// detail patch example
request.forEach((key, value) -> {
Field field = ReflectionUtils.findRequiredField(invoiceDetail.class, key.toString());
field.setAccessible(true);
ReflectionUtils.setField(field, invoiceDetail, value);
});
return invoiceDetailRepository.save(invoiceDetail);
but now i want to implement one time shot partial update with master and detail objects as json and use the invoice and invoiceDetail patch methods. My target is only change and update fields that comes from body json.
{
"number": "string", //only change number in invoice
"invoiceDetails": [
{
"id": 1,
"code": "string", //only change code that invoice detail
},
{
"id": 3,
"price": "number" //only change price that invoice detail
......
}
]
}
It's a good idea to receive the json body and send the respective objects (master/detail) to respective patch methods ?

GraphQl Union input types

In HotChocolate I have a query type like so
// QueryType.cs
public Task<ContentCategory> FilterContentAsync(string categoryCode, ICollection<Filter> filters, CancellationToken cancellationToken = default)
=> ...;
// Filter.cs
public abstract class Filter
{
public string Type { get;set; }
}
// BooleanFilter.cs
public class BooleanFilter : Filter
{
public bool IsTrue { get; set; }
}
// NumberRangeFilter.cs
public class NumberRangeFilter : Filter
{
public int Min { get; set; }
public int Max { get; set; }
}
when I run the application I get the following error
1. Unable to infer or resolve a schema type from the type reference `Input: Filter`.
Is the following query supported:
filterContent (categoryCode: "All", filters: [{type: "boolean", isTrue: true}, {type: "numberrange", min: 10, max: 60}]) {
id
}
Seems that this is not implemented currently.
Here is an issue in hotchocolate repository. Currently (Jul 2021) it is open and have "backlog" label, so I can assume that input unions are not implemented yet.
And here is a tweet of one of developers of hotChocolate, where he says
We are implementing the #graphql oneof spec proposal for Hot Chocolate
12... let the input unions come :)
By the way there is another link to discussion about possible implementations for input union problem. So according to this, there is 7 various ideas how to change the spec, 1 winner (the 7th option) and no one single link to possible implementations.

web api : checking odata query

I have a web api exposing a list of sessions. This is my code :
[RoutePrefix("api/data")]
public class SessionController : ApiController
{
[HttpGet]
[Route("sessions")]
[Queryable]
public IQueryable<Session> Get()
{
List<Session> list = new List<Session>();
list.Add(new Session { Id = 1, Name = "name 1", Place = "place 1", SessionOn = Convert.ToDateTime("1/1/2014") });
list.Add(new Session { Id = 2, Name = "name 2", Place = "place 2", SessionOn = Convert.ToDateTime("2/1/2014") });
list.Add(new Session { Id = 3, Name = "name 3", Place = "place 3", SessionOn = Convert.ToDateTime("3/1/2014") });
return list.AsQueryable();
}
}
public class Session
{
public int Id { get; set; }
public string Name { get; set; }
public string Place { get; set; }
public DateTime SessionOn { get; set; }
}
An user can request this api to see all sessions like this :
mydomain/api/data/sessions
I have added the oData to allow user querying and filtering those data like this :
mydomain/api/data/sessions?$filter=Name eq 'name1'
mydomain/api/data/sessions?$filter=Place eq 'place 1'
Everything is working well, the only problem remaining is that I would like to check the query given by the user to tell him that a value is not valid for example :
In my list of sessions, the possible values for the field 'Place' are :
place 1
place 2
place 3
place 4
If the user do the following request :
mydomain/api/data/sessions?$filter=Place eq 'placezzzzz 1'
He will just get an empty sets of Session. What I would like to do, is check in my backend code api the value given (that is to say 'placezzzzz 1') and returns a response to the user telling that this value is invalid.
You could change your method signature to include a ODataQueryOptions<T> parameter:
[EnableQuery]
public IQueryable<Session> Get(ODataQueryOptions<Session> options) {
//Do something fun with the filter:
var filter = options.Filter;
}
The options argument can be added to your Get method without changing the routing. It contains all kinds of info about the request, including the filter that was used. You can still return your IQueryable, just like you are used to.
I use the [System.Web.OData.EnableQuery] attribute instead of [Queryable] because QueryableAttribute is obsolete. (source msdn) I'm not sure this solution will work with the older version.

Automatic id generation and mapping _id NEST

I want the id to be automatically generated when I index a document into elastic search. This works fine when I don't specify an Id property in my poco.
What I would like to do is map the underlying _id field onto my poco class when getting and use auto generated id when indexing. It looks like I can either specify the id or not at all. Are their any nest api options that I am missing?
EDIT
Example gist
https://gist.github.com/antonydenyer/9074159
As #Duc.Duong said in comments you can access Id using DocumentWithMeta.
In current version of NEST DocumentsWithMetaData is replaced with Hits and also you should cast IHit<DataForGet>.Id from string to int.
this is my code:
public class DataForIndex
{
public string Name { get; set; }
// some other fields...
}
public class DataForGet : DataForIndex
{
public int Id { get; set; }
}
var result = client.Search<DataForGet>(x => x.Index("index").MatchAll());
var list = results.Hits.Select(h =>
{
h.Source.Id = Convert.ToInt32(h.Id);
return h.Source;
}).ToList();
Automatic ID Generation
The index operation can be executed without specifying the id. In such a case, an id will be generated automatically. In addition, the op_type will automatically be set to create. Here is an example (note the POST used instead of PUT):
$ curl -XPOST 'http://localhost:9200/twitter/tweet/' -d '{
"user" : "kimchy",
"post_date" : "2009-11-15T14:12:12",
"message" : "trying out Elasticsearch"
}'
Result:
{
"_index" : "twitter",
"_type" : "tweet",
"_id" : "6a8ca01c-7896-48e9-81cc-9f70661fcb32",
"_version" : 1,
"created" : true
}
Seem that NEST auto detect "Id" field in class and map it to "_id" in ES. Your requirement look strange but why not create 2 classes for it ? One for indexing (without Id field), one for getting (inherited from indexing class and declare new field "Id") ?
e.g.:
public class DataForIndex
{
public string Name { get; set; }
// some other fields...
}
public class DataForGet : DataForIndex
{
public int Id { get; set; }
}

Kendo UI Grid - How to Bind to Child Properties

How to bind a column/field to a child property of the json result in the model settings of the Kendo grid (in javascript)? For example, I want the grid to contain columns: FName, LName, Street and Address. Basically I want to flatten the hierarchical structure returned by the web service.
Kendo Settings
fields: {
FName: { type: "string" },
LName: { type: "string" },
// How to map to child properties below?
Street: { field: "Address.Street" }, // this is wrong
City: { field: "Address.City" } // this is wrong
}
JSON
{
"FName": "William",
"LName ": "Shakespeare",
"Address":
{
"Address": "123 Street Ln",
"City": "Philadelphia"
}
}
You don't do it like that. You need to create a class 'Model' that flattens the data graph. You will be able to use lazy loading during the construction of the Model. Either send this Model to the View via the controller or attach it to a larger ViewModel (just a Model of Models not MVVM) that is sent to the View. Then bind this to the Grid.
But, you will be happier to use Ajax loading of the same Model as JSON, which is what I think you are trying to do.
Model
public class ContactModel
{
public string FName { get; set; }
public string LName { get; set; }
public string Address { get; set; }
public string City { get; set; }
public ContactModel()
{}
public ContactModel(Contact contact) // IContact is better if you have Interfaces
{
FName = contact.FName;
LName = contact.LName;
Address = contact.Address.Address;
City = contact.Address.City;
}
// Neat Linq trick to convert database query results directly to Model
public static IList<ContactModel> FlattenToThis(IList<Contact> contacts)
{
return contacts.Select(contact => new ContactModel(contact)).ToList();
}
}
Controller
public JsonResult ReadContacts([DataSourceRequest]DataSourceRequest request)
{
var contacts = _contactsDataProvider.Read(); // Your database call, etc.
DataSourceResult result = ContactModel.FlattenToThis(contacts).ToDataSourceResult(request);
return Json(result, JsonRequestBehavior.AllowGet);
}
But I don't think Will ever made it to Philly. ;)

Resources