RavenDB array search returns random results - asp.net

I'm trying to perform a search on top of a dictionary using the Search method from RavenDB 4. Strangely, if the search term is the word in or it I get random results back. I'm absolutely sure that none of the records contains those words. It also happens when executing the equivalent lucene query on the studio. It works as expected when I enter a valid search term like the employee's name, number, etc.
I've managed to create this simple scenario based on the real one.
Here's the index:
public class Search : AbstractIndexCreationTask<Employee, Page>
{
public Search()
{
Map = employees => from employee in employees
select new
{
Id = employee.Id,
Details = employee.Details
};
Reduce = results => from result in results
group result by new
{
result.Id,
result.Details
}
into g
select new
{
g.Key.Id,
g.Key.Details
};
Index("Details", FieldIndexing.Search);
}
}
Employee class:
public class Employee
{
public string Id { get; set; }
public Dictionary<string, object> Details { get; set; }
}
Adding employees:
details = new Dictionary<string, object>();
details.Add("EmployeeNo", 25);
details.Add("FirstNames", "Yuri");
details.Add("Surname", "Cardoso");
details.Add("PositionCode", "XYZ");
details.Add("PositionTitle", "Developer");
employee = new Employee
{
Details = details
};
session.Store(employee);
session.SaveChanges();
Search method:
var searchTerm = "in";
var result = session
.Query<Page, Search>()
.Search(i => i.Details, $"EmployeeNo:({searchTerm})")
.Search(i => i.Details, $"FirstNames:({searchTerm})", options: SearchOptions.Or)
.Search(i => i.Details, $"Surname:({searchTerm})", options: SearchOptions.Or)
.Search(i => i.Details, $"PositionCode:({searchTerm})", options: SearchOptions.Or)
.Search(i => i.Details, $"PositionTitle:({searchTerm})", options: SearchOptions.Or)
.ToList();
Lucene query outputed:
from index 'Search' where search(Details, "EmployeeNo:(it)")
or search(Details, "FirstNames:(it)")
or search(Details, "Surname:(it)")
or search(Details, "PositionCode:(it)")
or search(Details, "PositionTitle:(it)")
Any idea why random results are returned when those specific words are enterered?

The issue is stop words. Certain terms are so common, that they are meaningless for searching using full text search.
is, it, they, are, etc.
They are erased by the query analyzer.
See the discussion here: https://ravendb.net/docs/article-page/4.2/Csharp/indexes/using-analyzers
You can use a whitespace analyzer, instead of the Standard Analyzer, since the former doesn't eliminate stop words.

After getting help from the RavenDB group guys, we've managed to find a solution for my scenario.
Employee:
public class Employee
{
public string Id { get; set; }
public string DepartmentId { get; set; }
public Dictionary<string, object> Details { get; set; }
}
Department:
public class Department
{
public string Id { get; set; }
public string Name { get; set; }
}
Page:
public class Page
{
public string Id { get; set; }
public string Department { get; set; }
public Dictionary<string, object> Details { get; set; }
}
Index (with dynamic fields):
public class Search : AbstractIndexCreationTask<Employee, Page>
{
public Search()
{
Map = employees => from employee in employees
let dept = LoadDocument<Department>(employee.DepartmentId)
select new
{
employee.Id,
Department = dept.Name,
_ = employee.Details.Select(x => CreateField(x.Key, x.Value))
};
Store(x => x.Department, FieldStorage.Yes);
Index(Constants.Documents.Indexing.Fields.AllFields, FieldIndexing.Search);
}
}
Query:
using (var session = DocumentStoreHolder.Store.OpenAsyncSession())
{
var searchTearm = "*yu* *dev*";
var result = await session
.Advanced
.AsyncDocumentQuery<Page, Search>()
.Search("Department", searchTearm)
.Search("EmployeeNo", searchTearm)
.Search("FirstNames", searchTearm)
.Search("Surname", searchTearm)
.Search("PositionCode", searchTearm)
.Search("PositionTitle", searchTearm)
.SelectFields<Page>()
.ToListAsync();
}
Everything seems to be working fine this way, no more random results.
Big thanks to Ayende and Egor.

Related

Modeling mongodb subobjects in ASP.NET MVC application

I am running into issues after adding a sub-object to my mongo documents. The query no longer returns results, even though I've added an object to my model to store the new sub-object.
I believe the issue is in adding the class for the sub-object to the object model. I can't seem to find any references anywhere online, so perhaps I'm searching for the wrong thing?
Mongo elements look as so:
{
_id: [id],
Name: "Paul",
Phone1: {
Name: "Work",
Number: "15551234567"
},
Phone2: {
Name: "Work",
Number: "15551234567"
}
}
In C# my model looks as so:
public class PersonModel {
[BsonId]
public ObjectId _Id { get; set; }
public string Name { get; set; }
public Phone Phone1 { get; set; }
public Phone Phone2 { get; set; }
}
public class Phone {
public string Name { get; set; }
public string Number { get; set; }
}
My query looks as so:
public async Task<List<PersonModel>> GetPerson(string name)
{
var people = new List<PersonModel>();
var allDocuments = await PersonCollection.FindAsync(
ds => ds.Name == name);
await allDocuments.ForEachAsync(doc => people.Add(doc));
return people;
}
Any references to a working example would be appreciated.
Thank you for looking.
The above implementation is correct. After many hours of trouble shooting it turned out I didn't have the datapoint in my database that I was querying against. Unbelievable.
If anyone else is struggling, I also found this guide that confirmed I was dealing with the subobject correctly: https://www.codementor.io/pmbanugo/working-with-mongodb-in-net-1-basics-g4frivcvz

A circular reference was detected while serializing entities with one to many relationship

How to solve one to many relational issue in asp.net?
I have Topic which contain many playlists.
My code:
public class Topic
{
public int Id { get; set; }
public String Name { get; set; }
public String Image { get; set; }
---> public virtual List<Playlist> Playlist { get; set; }
}
and
public class Playlist
{
public int Id { get; set; }
public String Title { get; set; }
public int TopicId { get; set; }
---> public virtual Topic Topic { get; set; }
}
My controller function
[Route("data/binding/search")]
public JsonResult Search()
{
var search = Request["term"];
var result= from m in _context.Topics where m.Name.Contains(search) select m;
return Json(result, JsonRequestBehavior.AllowGet);
}
When I debug my code I will see an infinite data because Topics will call playlist then playlist will call Topics , again the last called Topic will recall playlist and etc ... !
In general when I just use this relation to print my data in view I got no error and ASP.NET MVC 5 handle the problem .
The problem happens when I tried to print the data as Json I got
Is there any way to prevent an infinite data loop in JSON? I only need the first time of data without call of reference again and again
You are getting the error because your entity classes has circular property references.
To resolve the issue, you should do a projection in your LINQ query to get only the data needed (Topic entity data).
Here is how you project it to an anonymous object with Id, Name and Image properties.
public JsonResult Search(string term)
{
var result = _context.Topics
.Where(a => a.Name.Contains(term))
.Select(x => new
{
Id = x.Id,
Name = x.Name,
Image = x.Image
});
return Json(result, JsonRequestBehavior.AllowGet);
}
If you have a view model to represent the Topic entity data, you can use that in the projection part instead of the anonymous object
public class TopicVm
{
public int Id { set;get;}
public string Name { set;get;}
public string Image { set;get;}
}
public JsonResult Search(string term)
{
var result = _context.Topics
.Where(a => a.Name.Contains(term))
.Select(x => new TopicVm
{
Id = x.Id,
Name = x.Name,
Image = x.Image
});
return Json(result, JsonRequestBehavior.AllowGet);
}
If you want to include the Playlist property data as well, you can do that in your projection part.
public JsonResult Search(string term)
{
var result = _context.Topics
.Where(a => a.Name.Contains(term))
.Select(x => new
{
Id = x.Id,
Name = x.Name,
Image = x.Image,
Playlist = x.Playlist
.Select(p=>new
{
Id = p.Id,
Title = p.Title
})
});
return Json(result, JsonRequestBehavior.AllowGet);
}

show data from another model in view

I have this model `
public int Id { get; set; }
public string Title { get; set; }
public int CopiesNum { get; set; }
public int CurrentCopiesNum { get; set; )
public Author author { get; set; }
public int AuthorId { get; set; }
i want throug this action to show up the name of author which i made it to search `
public ActionResult SearchForBook(string BookName)
{
var book1 = from s in db.Books
select s;
if (!string.IsNullOrEmpty(BookName))
{
book1 = book1.Where(c => c.Title.Contains(BookName));
}
ViewBag.AuthorId = new SelectList(db.Authors, "AuthorId", "Name").ToList();
// var book = db.Books.Include(b => b.author).ToList();
return View(book1);
}
when i try to show the name of author
<td>
#Html.DisplayFor(modelItem => item.author.Name)
</td>
it shows nothing
As you are using Eager loading, you need to include Author. Try something like this:
In your Controller-
var book1 = db.Books.Include(a => a.Author).ToList();
If you want to use lazy loading then in the model
public virtual Author author { get; set; }
Then your controller should work.
Modification
As you said about your requirement in the comment section, this can also be achieved like below:
In you Controller:
var book1 = new Book();
if (!string.IsNullOrEmpty(BookName))
{
book1 = _context.Books.Include(c => c.Author).SingleOrDefault(c => c.Title == "BookName");
}
You can simply send book1 to the view (You should check if book1 contains anything and if not then handle it properly).
And if you really don't want to change the way you coded, you can edit the Controller like below:
var book1 = from s in _context.Books.Include(c => c.Author)
select s;
And keep the rest of the code as it is. It should also work (though I do not recommend this.)

#Html.DisplayNameFor not showing data MVC5

I have searched around and not had much luck finding a solution to my exact problem.
Model
public class PageDetailsViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Image { get; set; }
}
Controller
public ActionResult Search(int SysID)
{
var query = from r in _db.Auctions
from d in _db.Product_Details
where SysID == d.Id && r.BidStatus == "Open" && d.Id == r.Product_DetailsId
select new PageDetailsViewModel
{
Name = d.Name,
Description = d.Description,
Image = d.Image
};
return View(query);
}
View
#model IEnumerable<ProjectT.Models.PageDetailsViewModel>
#Html.DisplayNameFor(x => x.Name)
This fails to bring the name through. However, if I use a foreach
#foreach (var item in Model)
{
#item.Name
}
It brings through the name no problem.
Any help is much appreciated.
This extension method shows the value of the DisplayNameAttribute from DataAnnotations namespace. Consider this a label. Typically it is used like this:
[DisplayName("The Name")]
public string Name { get; set; }
And in the view:
#Html.DisplayNameFor(x => x.Name) <-- displays The Name
The code above will work only if the model is a single item. For the list case, as you have, you need to do some tricks, say a for loop, so you could do something like:
#Html.DisplayNameFor(x => x[i].Name): #Model[i].Name <-- displays The Name: Bill

Entity Framework : adding record with related data

I have a very simple Situation with 2 tables
public class Movie
{
[Key]
public Guid ID { get; set; }
public string Name { get; set; }
public byte[] Hash { get; set; }
public int GenreID{ get; set; }
[ForeignKey("GenreID")]
public virtual Genre genre{ get; set; }
}
and
public class Genre
{
public int ID { get; set; }
public string Name { get; set; }
}
Now, in an import sequence I want to create new movies and link the Genre with the existing entries in the Genre table or create new Genre entries if they don't exist.
Movie m = new Movie();
m.ID = Guid.NewGuid();
IndexerContext db = new IndexerContext();
var genre = db.Genre.Where(g => g.Name== genreValue).FirstOrDefault();
if(genre!= null)
{
m.GenreID= genre.GenreID;
}
else
{
genre= new Genre();
genre.Name = genreValue;
db.Genres.Add(genre);
var genreCreated= db.Genre.Where(g => g.Name== genreValue).FirstOrDefault();
m.GenreID= genreCreated.GenreID;
}
Now the problem is, it doesn't work. The last line fails because genreCreated is null.
Plus I think I must doing it wrong - it can't be that difficult in Entity Framework.
can anyone help me?
db.Genres.Add(genre);
This does not send insert statement to database - this instructs entity framework that new record should be inserted when saving changes. Genre will be saved (and created id available) after you call db.SaveChanges(); As for now, you do not have save call, so genreCreated is null.
In your situation - fix is simple, you do not need to select genreCreated from db. Just setting m.Genre to new value should do the job
Movie m = new Movie();
m.ID = Guid.NewGuid();
IndexerContext db = new IndexerContext();
var genre = db.Genre.Where(g => g.Name== genreValue).FirstOrDefault();
if(genre! = null)
{
m.GenreID = genre.GenreID;
}
else
{
genre = new Genre();
genre.Name = genreValue;
m.Genre = genre;
}
db.SaveChanges(); //m.GenreID will automatically be set to newly inserted genre
After the add statement you need to save it:
Try
genre= new Genre();
genre.Name = genreValue;
db.Genres.Add(genre);
db.SaveChanges();

Resources