RavenDb-Get list from a list of ids - nosql

I'm developing an events manager with RavenDb. The logged user can write a post in a place that follow and then see all posts in the user wall (home page). Every post can be mark as "Like" and "Favourite". A small structure like twitter or facebook.
Wich is the best way to get the posts by the places that I follow? I need to get the count of likes and favourites and if I already mark the post on viewmodel to send to the view and display the list. I'm trying like this, but throw the max requests exception.
var myPlaces = this.SessionDb.Query<EventFollow>()
.Where(ls => ls.UserId == User.Identity.Name)
.Select(l => l.PlaceId);
var listPosts = this.SessionDb
.Advanced.LuceneQuery<Post>().WhereIn("PlaceId", myPlaces)
.Skip(skip)
.Take(20)
.ToList();
List<PostViewModel> posts = new List<SpottedViewModel>();
foreach (var p in listPosts)
{
PostViewModel vm = new PostViewModel();
vm.Creator = p.UserCreatorId == User.Identity.Name;
vm.Like = this.SessionDb.Query<Lol>().Where(lol => lol.SpottedId == p.Id).Count();
vm.Favourites = this.SessionDb.Query<Favourite>().Where(fa => fa.SpottedId == p.Id).Count();
vm.Post= s;
vm.IsLike = this.SessionDb.Query<Like>().FirstOrDefault(l => l.PostId == p.Id) != null;
vm.IsFavourite = this.SessionDb.Query<Favourite>().FirstOrDefault(f => f.SpottedId == s.Id) != null;
posts.Add(vm);
}
I have these models:
public class Post
{
public string Id { get; set; }
public string UtenteCreatorId { get; set; }
public string PlaceId { get; set; }
public string Body{ get; set; }
}
public class Lol
{
public string Id { get; set; }
public string PostId { get; set; }
public string UserId { get; set; }
}
public class Place
{
public string Id { get; set; }
public string UserCreatorId { get; set; }
public string Name{ get; set; }
}
public class Favourite
{
public string Id { get; set; }
public string PostId { get; set; }
public string UserId { get; set; }
}
public class EventFollow
{
public string Id { get; set; }
public string PlaceId { get; set; }
public string UserId { get; set; }
}
Thank you and sorry for my english! ;D

Your problem here isn't really getting a list of documents from a list of IDs, you can do that by either using the IDocumentSession.Load method and sending in a list of document IDs, or by using WhereIn like you do. The problem is your model design.
It seems to me you have some serious re-design of your data models to do. It's strongly relational as it is now, it seems, and RavenDB isn't relational (or optimized towards relational models, anyway). It's hard for me to see exactly what your use cases are, but from what I can see I would probably save 'favourites' and 'likes' in the 'post' model, and put 'EventFollow' in the 'user' model.
Also, by RavenDB's "safe by default" principles, you're limited to 30 requests per session (per default, it's configurable). You have some select N+1 going on here so you'll most likely exceed those 30 requests, by far. That's why you get the exception.

Related

Entity Framework and RESTful WebAPI - possible circular reference

Here is a simplified version of my model:
public class User {
public int UserID { get; set; }
public string FirstName { get; set; }
public virtual ICollection<Recipe> Recipes { get; set; }
}
public class Recipe {
public int RecipeID { get; set; }
public string RecipeName { get; set; }
public int UserID { get; set; }
public virtual User User { get; set; }
}
I have a controller that I'd like to return a User as well as some summary information about their recipes. The scaffolded controller code looks like this:
var user = await _context.Users.SingleOrDefaultAsync(m => m.UserID == id);
It works fine. Now I try to add the Recipes, and it breaks:
var user = await _context.Users.Include(u => u.Recipes).SingleOrDefaultAsync(m => m.UserID == id);
My web browser starts to render the JSON, and it flickers and I get a message in the browser saying the connection has been reset.
My Theory - I believe that the parent (User) renders, which exposes the child (Recipe) which contains a reference to the parent (User), which contains a collection of the child (Recipe) and so on which is causing an infinite loop. Here's why I think this is happening:
The Visual Studio debugger allows me to navigate the properties in that way infinitely.
If I comment out the Recipe.User property, it works fine.
What I've tried
I tried to just include the data from Recipe that I need using Entity Framework projection (I'm attempting to not include Recipe.User). I tried to only include Recipe.RecipeName... but when I try to use projection to create an anonymous type like this:
var user = await _context.Users.Include(u => u.Recipes.Select(r => new { r.RecipeName })).SingleOrDefaultAsync(m => m.UserID == id);
I receive this error:
InvalidOperationException: The property expression 'u => {from Recipe r in u.Recipes select new <>f__AnonymousType1`1(RecipeName = [r].RecipeName)}' is not valid. The expression should represent a property access: 't => t.MyProperty'.
What is the solution? Can I project with different syntax? Am I going about this all wrong?
Consider using POCOs for serialization rather than doubly-linked entity classes:
public class UserPOCO {
public int UserID { get; set; }
public string FirstName { get; set; }
public ICollection<RecipePOCO> Recipes { get; set; }
}
public class RecipePOCO {
public int RecipeID { get; set; }
public string RecipeName { get; set; }
public int UserID { get; set; }
}
Copy the entity contents to the corresponding POCO and then return those POCO objects as the JSON result. The removal of the User property via usage of the RecipePOCO class will remove the circular reference.
I can propose you 3 options.
U sing [JsonIgnore] on property, but it will work on every use of Recipe class, so when you would like to just return Recipe class you won't have User in it.
public class Recipe {
public int RecipeID { get; set; }
public string RecipeName { get; set; }
public int UserID { get; set; }
[JsonIgnore]
public virtual User User { get; set; }
}
You can this solution to stop reference loop in all jsons https://stackoverflow.com/a/42522643/3355459
Last option is to create class (ViewModel) that will only have properties that you want send to the browser, and map your result to it. It is propably best from security reason.

how to Pull data with paramter using Azure Offline Sync?

I have schema as below and I would like to know if I can get all the TaskCategoryMappings by CategoryId when I have to pulll all data. I went through the documentation here but I cant figure out how to do it ? All the examples are like this one based on UserId. but userid is used for also authentication and on the server side I already handle it fine to return only mappings belong to relevant user and i want in adition to filter by CategoryId?
another SO sample is here also using userId Parameter Passing with Azure Get Service
public class TaskCategoryMapping : TableData
{
public string TaskId { get; set; }
public string CategoryId { get; set; }
public string UserId { get; set; }
}
According to your description, I checked this issue on my side and found that it could work as expected, you could follow the details below to check your code:
Backend models:
public class Tag : EntityData
{
public string TagName { get; set; }
public bool Status { get; set; }
}
public class Message : EntityData
{
public string UserId { get; set; }
public string Text { get; set; }
public virtual Tag Tag { get; set; }
[ForeignKey("Tag")]
public string Tag_Id { get; set; }
}
GetAllMessage action:
// GET tables/Message
public IQueryable<Message> GetAllMessage()
{
return Query();
}
For the client, I just invoke the online table for retrieving the message entities as follows:
Model on client-side:
public class Message
{
public string Id { get; set; }
public string UserId { get; set; }
public string Text { get; set; }
public string Tag_Id { get; set; }
}
var result=await mobileServiceClient.GetTable<Message>().Where(msg => msg.Tag_Id == "c3cd4cf8-7af0-4267-817e-f84c6f0e1733").ToListAsync();
For offline table, the pull operation query would
await messageSyncTable.PullAsync($"messages_{userid}", messageSyncTable.Where(m => m.Tag_Id == "<Tag_Id>"));
Use fiddler, you could find that the request would look like this:
https://{your-app-name}.azurewebsites.net/tables/Message?$filter=Tag_Id eq 'c3cd4cf8-7af0-4267-817e-f84c6f0e1733'

MVC EF: Adding one to many component by ID

I am working with two classes Company and Visitor that have a one-to-many relationship.
public class Company
{
public int CompanyID { get; set; }
public string CompanyName { get; set; }
public virtual ICollection<Visitor> Visitors { get; set; }
}
public class Visitor
{
public int VisitorID { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int CompanyID { get; set; }
public bool SendNewsletter { get; set; }
public virtual Company Company { get; set; }
}
I have a page where a Visitor can fill out information about themselves. The idea is that if a Company is not in the db, it will get added to the list. If the CompanyName the Visitor enters matches a name in the db, the Company is associated with the Visitor, rounding out its required info, which is then added to its own db.
var companyExists = db.Companies.Any(c => c.CompanyName == visitor.Company.CompanyName);
if(companyExists)
{
var existingCompany = db.Companies.SingleOrDefault(c => c.CompanyName == visitor.Company.CompanyName);
visitor.CompanyID = existingCompany.CompanyID;
visitor.Company = existingCompany;
}
db.Visitors.Add(visitor);
db.SaveChanges();
This code works but it seems redundant. I am associating the Visitor's CompanyID with the existing Company and then doing the same for the Company. Everything I read suggests that updating the Visitor's CompanyID should be sufficient but if I don't map the existingCompany to the Visitor's Company parameter, a second CompanyID is created. I feel like I'm missing some crucial point and am hoping someone here can point me in the right direction.
You don not need set visitor.Company. You can get Company information by CompanyID and you can use Where and FirstOrDefault to avoid redundant like this:
var companyExists = db.Companies.Where(c => c.CompanyName == visitor.Company.CompanyName).FirstOrDefault();
if (companyExists)
{
visitor.CompanyID = companyExists.CompanyID;
// visitor.Company = companyExists;
}
Notice that public virtual Company Company { get; set; } allows the Entity Framework to create a proxy around the virtual property so that the property can support lazy loading and more efficient change tracking. Please see this post for more information about virtual property in EF.

LINQ to Entities does not recognize the method ... get_Item(Int32)

(Please read before marking as duplicate as my particular scenario is unique)
I have the following code:
// Get each treasure hunt
var treasureHunts = dbContext.TreasureHunts.Where(i => i.UserName == User.Identity.Name).ToList();
// Populate each treasure hunt with the list of leaderboard entries
for (int i = 0; i <= treasureHunts.Count; i++)
{
treasureHunts[i].Leaderboard = dbContext.Leaderboard.Where(
leaderboard => leaderboard.TreasureHuntId == treasureHunts[i].TreasureHuntId).ToList();
}
On running the program, I get the following error from the second database query (dbContext.Leaderboard.Where...):
LINQ to Entities does not recognize the method
'QrCodeTreasureHunter.Models.TreasureHuntDetails get_Item(Int32)'
method, and this method cannot be translated into a store expression.
In the first query, I'm getting each of the treasure hunts associated with a particular user.
In the second part, I'm attempting to iterate through each of the treasure hunts, and populate the treasure hunt's Leaderboard List property with the associated leaderboard entries from my Leaderboard table.
From what I understand from reading around is that this query isn't possible in its current form with Entity Framework.
What workarounds or solutions would you be able to recommend to solve this problem? The ideal solution would involve no changes to the data models.
If it's relevant, here is the TreasureHunt model:
public class TreasureHuntDetails
{
public TreasureHuntDetails()
{
Clues = new List<Clue>();
Leaderboard = new List<Leaderboard>();
var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof (TreasureHuntDetails), null, int.MaxValue,
false, true, null);
xml.SetSerializer<TreasureHuntDetails>(dcs);
}
[Key]
public int TreasureHuntId { get; set; }
[Required]
public virtual string UserName { get; set; }
[Required]
public String Name { get; set; }
public String Description { get; set; }
[Required]
public String Password { get; set; }
public String CompletionMessage { get; set; }
public String State { get; set; }
public List<Clue> Clues { get; set; }
public List<Leaderboard> Leaderboard { get; set; }
}
And here is the Leaderboard model:
public class Leaderboard
{
[Key]
public int Id { get; set; }
public int TreasureHuntId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int Completion { get; set; }
public DateTime? StartTime { get; set; }
public DateTime? EndTime { get; set; }
public Int64 TimeTaken { get; set; }
public TreasureHuntDetails TreasureHuntDetails { get; set; }
}
Good luck!
I'm not able to test it right now but it could be the indexer, try this:
foreach (var treauserHunt in treasureHunts)
{
treasureHunt.Leaderboard = dbContext.Leaderboard.Where(leaderboard =>
leaderboard.TreasureHuntId == treasureHunt.TreasureHuntId).ToList();
}
I'm not sure this is the problem, but I remember having some issues with indexing in arrays in LINQ queries, just can't remember if it was with the LINQ method syntax (the one you are using) or the other (the SQL-like);
Thanks for the answer; it solved my problem. I still wanted to use a for loop so I did something like this:
for (int i = 0; i <= treasureHunts.Count; i++)
{
var thisTreasureHunt = treasureHunts[i];
treasureHunts[i].Leaderboard = dbContext.Leaderboard.Where(
leaderboard => leaderboard.TreasureHuntId == thisTreasureHunt.TreasureHuntId).ToList);
}

Asp.net MVC 4 Code First Return Specific Fields from Navigation Property

Been stuck on this for a while so i thought i would ask. I am sure there is something simple i am missing here. Trying to learn Asp.net mvc 4 on my own by building a simple app.
Here is the model:
public class Category
{
public int Id { get; set; }
[Required]
[StringLength(32)]
public string Name { get; set; }
//public virtual ICollection<Note> Notes { get; set; }
private ICollection<Note> notes;
public ICollection<Note> Notes
{
get
{
return this.notes ?? (this.notes = new List<Note>());
}
}
}
public class Note
{
public int Id { get; set; }
[Required]
public string Content { get; set; }
[Required]
[StringLength(128)]
public string Topic { get; set; }
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public virtual IEnumerable<Comment> Comments { get; set; }
public virtual ICollection<Tag> Tags {get; set;}
public Note()
{
Tags = new HashSet<Tag>();
}
}
public class Tag
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Note> Notes { get; set; }
public Tag()
{
Notes = new HashSet<Note>();
}
}
I call this method in a repository from the controller:
public IQueryable<Note> GetAll()
{
var query = _db.Notes.Include(x => x.Category).Include(y => y.Tags);
return query;
}
On the home controller i am trying to return a list of all the notes and wanted to include the category name that it belongs to as well as the tags that go with the note. At first the did not show up so i read some tutorials about eager loading and figured out how to get them to show.
However, my method is not that efficient. The mini-profiler is barking at me for duplicate queries because the navigation properties for category and tags are sending queries for the notes again. IS there any way to just return the properties i need for the category and tag objects?
I have tried several methods with no luck. I was hoping i could do something like this:
var query = _db.Notes.Include(x => x.Category.Name).Include(y => y.Tags.Name);
But i get an error: Cannot convert lambda expression to type 'string' because it is not a delegate type
I have seen that error before that was caused by some missing using statements so i already double checked that.
Any suggestions? Thanks for the help