MongoDB projection on missing field on Document - mongodb-.net-driver

I am using new Mongodb C# Driver 2.2.4, I have collection where i dont have all the fields in the document. For example
[BsonIgnoreExtraElements]
public class Category : Entity
{
[BsonElement("name")]
public string Name { get; set; }
[BsonElement("title")]
public string Title { get; set; }
[BsonElement("description")]
public string Description { get; set; }
}
Here is my query where i am only projecting name and title.
var category = All().Where(c => c.Name == "test")
.Select(c => new { c.Title, c.Name })
.FirstOrDefault();
My data looks like this
{
"_id" : ObjectId("5575b9351eccba081c144433"),
"name" : "Sample Name",
"title" : "Sample Title",
"description" : "Sample Description"
}
{
"_id" : ObjectId("5575b9351eccba081c144433"),
"name" : "Test",
"description" : "Test Description",
}
Now since there is no Title with the document with Name = "Test" it throws error below.
No matching creator found.

check this link. you should try Query.exists
MongoDB how to check for existence
alternatively
MongoDB C# - Getting BsonDocument for an Element that doesn't exist

Related

What is the best practice to store a specification of an Entity in Spring?

My database contain products in a single table called Product, and each product might have certain fields with their specification, e.g.
//Bicycle product
{
"name" : "Bicycle",
"category" : "Bicycles"
"specification" : {
"color" : "red"
}
}
//Wheel product
{
"name" : "Wheel",
"category" : "Spare Parts",
"specification" : {
"diameter" : "7.5"
}
}
So i've come up with idea of making a field of type Map<String, String> (which creates a another table called specifications) in my Product entity to contain those specifications. But i don't like this approach, because all of the additional fields would be of String type, and because Spring will create a bean out of this field, I wont be able to specify the type of value as an abstract class (like this Map<String, Object>).
I thought of creating additional fields, like this:
#ElementCollection
private Map<String, String> string_features;
#ElementCollection
private Map<String, Double> double_features;
// ...
But it looks kind of ugly and I think there is a better way to do it. And also, if the specification field is of a different Entity type, I will have to create another map for that specific entity, e.g.
//Bicycle product
{
"name" : "Bicycle",
"category" : "Bicycles"
"specification" : {
"color" : "red",
"wheel" : {
"name" : "Wheel",
}
}
}
If the value can be only be numbers and strings, maybe you can save the value as strings and then use a regex to check if the string is a number before returning the value.
Otherwise, you need a way to recognize the type.
I think I would change it to this:
//Bicycle product
{
"name" : "Bicycle",
"category" : "Bicycles"
"specifications" : [
{ name: "color", value: "red", type: "string"},
{ name: "diameter", value: "7.5", type: "double"},
...
]
}
You can map it as:
#ElementCollection
private List<Specification> specifications;
...
#Embaddable
class Specification {
String name;
String value;
String type;
// ... getter/setter and everything else
#Transient
Object getConvertedValue() {
if ("double".equals(type)) {
return Double.parse(value);
}
// String is the default
return value;
}
}
The nice thing is that you can have as many types as you want.

Spring - How to correctly show relationship data in view (table) using REST api

I have a User (id, username) entity which has many-to-many relationship with Roles (id, name) entity. I am trying to show the User data in a ajax Datatable. Now, if a User has six roles, it shows six [object Object] for all six roles. I dont know how to correctly show the role name instead of object Object.
This is what I have:
.DataTable(
{
"pagingType" : "full_numbers",
"sAjaxSource" : "/api/AppUser/all",
"sAjaxDataProp" : "",
"aoColumns" : [
{
"data" : "id"
},
{
"data" : "username"
},
{
"data" : "userenabled"
},
{
"data" : "useremail"
},
{
"data" : "userfirstname"
},
{
"data" : "userlastname"
},
{
"data" : "useraddress"
},
{
"data" : "roles"
}
This is how it looks like in Data Table:
Here is my REST Controller piece:
#RestController
#RequestMapping("/api/AppUser")
public class AppUserRestAPIs {
#GetMapping(value = "/all", produces = "application/json")
public List<AppUser> getResource() {
return appUserJPARepository.findAll();
}
}
I know it must be trivial but feeling lost and could not find a single example on how to represent relationship data in view (html) using REST api. Searched almost everywhere. What I am missing here? Will appreciate any pointers here.
Answering my own question:
Found it ! Here - https://editor.datatables.net/examples/advanced/joinArray.html
So instead of:
{
"data" : "roles"
}
I have to use:
{
"data" : "roles",
render : "[, ].name"
}
All worked perfectly but now I am clueless what if I don't use Datatable. Not sure if I have to put another question for it.
Use function to flat roles list:
Instead of
{
"data" : "roles"
}
Try this :
{
"data": null,
"render": function(data, type, row, meta) {
var flatrole = '';
//loop through all the roles to build output string
for (var role in data.roles) {
flatrole = flatrole + role.name + " ";
}
return flatrole;
}
}

Unwrapping a Kotlin hashmap in Thymeleaf inside of Spring Boot 2

I have a Kotlin function which creates a model with a hashmap as shown below
#GetMapping("/")
fun index(model: Model): Mono<String> {
model.addAttribute("images", imageService.findAllImages()?.flatMap { image ->
Mono.just(image)
.zipWith(repository.findByImageId(image?.id!!).collectList())
.map({ imageAndComments: Tuple2<Image?, MutableList<learningspringboot.images.Comment>> ->
hashMapOf<String, Any?>(
"id" to imageAndComments.t1?.id,
"name" to imageAndComments.t1?.name,
"comments" to imageAndComments.t2)
}).log("findAllImages")
})
model.addAttribute("extra", "DevTools can also detech code changes.")
return Mono.just("index")
}
Image.kt
package learningspringboot.images
import org.springframework.data.annotation.Id
data class Image(#Id var id: String? = null, var name: String? = null)
Comment.kt
package learningspringboot.images
import org.springframework.data.annotation.Id
data class Comment #JvmOverloads constructor(#Id private var id: String? = null, private var imageId: String? = null, private var comment: String? = null) {
}
In my Thymeleaf template I have
<ul><li th:each = "Comment :${image.comments}" th:text = "${image.comments}"></li></ul>
Which gives me this lines like
[Comment(id=5a623d5d2298352bc4929866, imageId=0d46b575-b6ce-48e2-988a-ebe62ebc2ceb, comment=test), Comment(id=5a623d8b2298352bc4929867, imageId=0d46b575-b6ce-48e2-988a-ebe62ebc2ceb, comment=test23)]
Which shows the comment record as is with the MongoDB keys/ids and everything else. This is not what I want.
I also have this in my Thymeleaf template
<ul><li th:each = "Comment :${image.comments}" th:text = "${comment == null ? 'empty' : comment.Comment}"></li></ul>
Which shows the word empty for each comment record.
The comment record in the database looks like
{ "_id" : ObjectId("5a623d5d2298352bc4929866"), "imageId" : "0d46b575-b6ce-48e2-988a-ebe62ebc2ceb", "comment" : "test", "_class" : "learningspringboot.comments.Comment" }
The image records in the database looks like
{ "_id" : ObjectId("5a623d5d2298352bc4929866"), "imageId" : "0d46b575-b6ce-48e2-988a-ebe62ebc2ceb", "comment" : "test", "_class" : "learningspringboot.comments.Comment" }
{ "_id" : ObjectId("5a623d8b2298352bc4929867"), "imageId" : "0d46b575-b6ce-48e2-988a-ebe62ebc2ceb", "comment" : "test23", "_class" : "learningspringboot.comments.Comment" }
How can I unwrap the comments records so that I only see the "comment" values and not the "_id" or "imageId" values?
The problem is that I have a hashmap<string>,arraylist<image>.
So I simply need to loop through all the image elements in the array list using thymeleaf. I'm pretty sure that this has been done before, I'll just need to find a good example and do some reading.
I was able to use the following code
<th:block th:each="Comment : ${image.comments}">
<ul th:each="comment : ${Comment}">
<li th:text="${comment.comment}"></li>
</ul>
</th:block>
And now I can move on.

In Nest 1.7.1 Delete or DeleteByQuery nothing works

In Nest 1.7.1 Delete or DeleteByQuery nothing works for me.
I am trying to delete below documents:
Article article1 = new Article()
{
Id = 1111,
Title = "Title - Test Elastic Search",
Summary = "Summary - Test Elastic Search",
Body = "Body - Test Elastic Search",
ArticleDate = _dateToday,
Author = new Author() { Id = 100, Name = "Mikey" },
};
Article article2 = new Article()
{
Id = 2222,
Title = "Title - Test Elastic Search",
Summary = "Summary - Test Elastic Search",
Body = "Body - Test Elastic Search",
ArticleDate = _dateToday,
Author = new Author() { Id = 100, Name = "Mikey" },
Published = true
};
I was expecting below queries would delete single document and all documents in an index but no query is deleting.
_elasticClient.Delete(article).Found;
_elasticClient.DeleteByQuery<Article>(q => q.Query(t => t.Term(m => m.OnField(f => f.Id).Value(articleId))))
.Found;
_elasticClient.DeleteByQuery<Article>(q => q.MatchAll()).IsValid;
Please correct me if i am doing anything wrong.
Here's a working example
void Main()
{
var settings = new ConnectionSettings(new Uri("http://localhost:9200"), "articles");
var client = new ElasticClient(settings);
if (client.IndexExists("articles").Exists)
{
client.DeleteIndex("articles");
}
client.CreateIndex("articles", c => c
.AddMapping<Article>(m => m
.MapFromAttributes()
)
);
var today = DateTime.Now.Date;
var article1 = CreateArticle(1111, today);
var article2 = CreateArticle(2222, today);
var article3 = CreateArticle(3333, today);
var article4 = CreateArticle(4444, today);
var bulkRequest = new BulkDescriptor();
bulkRequest.Index<Article>(i => i.Document(article1));
bulkRequest.Index<Article>(i => i.Document(article2));
bulkRequest.Index<Article>(i => i.Document(article3));
bulkRequest.Index<Article>(i => i.Document(article4));
bulkRequest.Refresh();
client.Bulk(bulkRequest);
var searchResponse = client.Search<Article>(q => q.MatchAll());
Console.WriteLine("Documents from search: {0}. Expect 4", searchResponse.Documents.Count());
client.Delete(article1, d => d.Refresh());
searchResponse = client.Search<Article>(q => q.MatchAll());
Console.WriteLine("Documents from search {0}. Expect 3", searchResponse.Documents.Count());
client.Delete(article2, d => d.Refresh());
searchResponse = client.Search<Article>(q => q.MatchAll());
Console.WriteLine("Documents from search {0}. Expect 2", searchResponse.Documents.Count());
client.DeleteByQuery<Article>(q => q.MatchAll());
searchResponse = client.Search<Article>(q => q.MatchAll());
Console.WriteLine("Documents from search {0}. Expect 0", searchResponse.Documents.Count());
}
private Article CreateArticle(int id, DateTime articleDate)
{
return new Article()
{
Id = id,
Title = "Title - Test Elastic Search",
Summary = "Summary - Test Elastic Search",
Body = "Body - Test Elastic Search",
ArticleDate = articleDate,
Author = new Author() { Id = 100, Name = "Mikey" },
Published = true
};
}
public class Article
{
public int Id { get; set;}
public string Title{ get; set;}
public string Summary { get; set;}
public string Body { get; set;}
public DateTime ArticleDate { get; set; }
public Author Author { get; set; }
public bool Published { get; set;}
}
public class Author
{
public int Id { get; set; }
public string Name { get; set;}
}
results in
Documents from search: 4. Expect 4
Documents from search 3. Expect 3
Documents from search 2. Expect 2
Documents from search 0. Expect 0
as expected.
Something to bear in mind is that Elasticsearch is eventually consistent meaning that a document that is indexed does not appear in search results until after a refresh interval (by default, 1 second); Likewise, with a delete query, a document marked for deletion will appear in search results until the refresh interval has elapsed.
A GET request on a given document with a given id will however return the document before the refresh interval.
If you need documents to be searchable (or to not show in search results after deletion), you can refresh the index after an operation as I did with the bulk and delete calls above, using .Refresh(). You might be tempted to call refresh after every operation, however I would recommend using it only when you really need to as it adds overhead to the cluster and called all the time would likely degrade performance.
I got it working finally.
The delete request which is sent via NEST in fiddler is DELETE /articlestest/article/_query and the query which worked in plugin is this DELETE /articlestest/articles/_query ( the document type name was misspelled in the code).That's the reason ,query was not deleting the documents via NEST.And the bad thing is ,it doesn't even complain about the non-existent document type :( It took me a while to found that issue.

load specific fields in Elasticsearch Nest query

the documentation seems to indicate i can return a subset of fields instead of the entire document. here's my code:
var result = client.Search<MyObject>(s => s
.Fields(f => f.Title)
.Query(q => q
.QueryString(qs => qs
.OnField("title")
.Query("the"))));
i'm searching on the word 'the' on the 'title' field and wanting to just return 'title'. my result.Documents object contains 10 objects that are each null.
i do see the values i want but it's deep in the search response:
result.Hits[0].Fields.FieldValues[0]...
is there a better way to get at the list of 'title' fields returned?
my mapping for the data (truncated) is this ...
{
"myidex": {
"mappings": {
"myobject": {
"properties": {
"title": {
"type": "string"
},
"artists": {
"properties": {
"id": {
"type": "string",
"index": "not_analyzed",
"analyzer": "fullTerm"
},
"name": {
"type": "string",
"index": "not_analyzed",
"analyzer": "fullTerm"
}
}
}
}
}
}
}
}
and my class objects are like this:
[Table("MyTable")]
[Serializable]
[ElasticType(Name="myobject")]
public class MyObject
{
[ElasticProperty]
public string Title { get; set; }
[JsonIgnore]
public string Artistslist { get; set; }
[ElasticProperty(Analyzer = "caseInsensitive")]
public List<Person> Artists { get; set; }
}
[Serializable]
public class Person
{
[ElasticProperty(Analyzer = "fullTerm", Index = FieldIndexOption.not_analyzed)]
public string Name { get; set; }
[ElasticProperty(Analyzer = "fullTerm", Index = FieldIndexOption.not_analyzed)]
public string Id { get; set; }
}
Artistslist comes from my data source (sql) then i parse it out into a new List object before indexing the data.
I think this deeply nested value is do to a change in Elasticsearch 1.0 and how partial fields are now returned as arrays (See 1.0 Breaking Changes - Return Values for details.). This is addressed in the NEST 1.0 Breaking Changes documentation; in the Fields() vs SourceIncludes() section. It shows an example of using a FieldValue helper method to gain access to these values. Based on that, try the following:
For all items:
foreach (var hit in result.Hits)
{
var title = hit.Fields.FieldValue<MyObject, string>(f => f.Title);
}
For a specific item:
var title = result.Hits.ElementAt(0)
.Fields.FieldValue<MyObject, string>(f => f.Title);
I know it is still a bit verbose but it should work for you and will handle the new array return formatting of Elasticsearch 1.0.
I found the solution in Nest's Github repo. They have created an issue about this problem. You should use FielddataFields instead of Fields.
https://github.com/elastic/elasticsearch-net/issues/1551
var result = client.Search<MyObject>(s => s
.FielddataFields(f => f.Title)
.Query(q => q
.QueryString(qs => qs
.OnField("title")
.Query("the"))));
and in response you see FieldSelections. You get the fields that you wanted.

Resources