RavenDB Includes still round-tripping to load included data - c#

I have a parent/child relationship between a ProductFamily (the parent) and a list of subordinates (SaleItem). I am running the Ravendb Server locally with the server pulled up as a console app. When I query the Family data I am attempting to include the list of SaleItems in the session to avoid extra trips to the server. However on the console I see the subsequent calls to load the individual saleitem list for each family as I step through the foreach loop. I think I am doing something incorrectly and am puzzled as to what it may be. I am on day 2 of using RavenDB, so any handholding is appreciated.
Classes:
public class Family
{
public string Id { get { return string.Format(#"Families/{0}", this.FamilyID); } }
public int FamilyID { get; set; }
public string FamilyNumber { get; set; }
public string Description { get; set; }
public string[] SaleitemIds { get; set; }
public override string ToString()
{
return string.Format("Number:{0} - {1}", FamilyNumber, Description);
}
[JsonIgnore]
public List<SaleItem> SaleItems { get; set; }
}
public class SaleItem
{
public string Id { get { return string.Format(#"SaleItems/{0}", this.SaleItemID); } }
public int SaleItemID { get; set; }
public string Description { get; set; }
public override string ToString()
{
return string.Format("Number:{0} - {1}", SaleItemID.ToString(), Description);
}
}
And the code:
List<SearchTerm> searchterms = new List<SearchTerm>(){ new SearchTerm(){term="1009110922"}
,new SearchTerm(){term="1009112439"}
,new SearchTerm(){term="1009122680"}
,new SearchTerm(){term="1009124177"}
,new SearchTerm(){term="1009133928"}
,new SearchTerm(){term="1009135435"}
,new SearchTerm(){term="1009148000"}};
using (IDocumentSession session = documentStore.OpenSession())
{
var results = session.Query<Family>().Customize(o => o.Include<SaleItem>(s => s.Id)).Where(x => x.FamilyNumber.In(searchterms.Select(t => t.term).ToList()));
foreach (Family fam in results)
{
Console.WriteLine(fam.ToString());
fam.SaleItems = session.Load<SaleItem>(fam.SaleitemIds).ToList();
foreach (SaleItem si in fam.SaleItems)
{
Console.WriteLine(" " + si.ToString());
}
}
}
As I step through the code I see the calls to Get the list of saleitems on the line:
fam.SaleItems = session.Load<SaleItem>(fam.SaleitemIds).ToList();
I believe I have implemented something incorrectly, but I am new enough with this platform to accept that I could have simply misunderstood what the behavior would be. There are definitely cases where I do not want the Saleitem doc to be embedded in the Family doc, so that is not really an option in this case.

Doug_w,
Look at what you are including:
o.Include<SaleItem>(s => s.Id)
You probably want it to be:
o.Include<SaleItem>(s => s.SaleitemIds )

Related

Nested one-to-many tables SQLite-Net Extensions [duplicate]

I am trying to use SQLite-Net Extensions to create a Relational Database. I'm running into an issue when trying to pull the Term object from the database. It successfully pulls over its associated courses, but not the courses associated assessments and notes. I'm not sure if the problem lies in how I insert the objects into the database, how I pull the objects from the database, or how I have the objects attributes listed.
I feel like the SQLite-Net Extensions documentation is extremely limited, so I'm not even sure what's going on. I've tried it many different ways, including adding CascadeOperations, but non of those seemed to help.
Here is the (simplified) code for my objects:
[Table("Terms")]
public class Term
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string Name { get; set; }
[OneToMany]
public List<Course> Courses { get; set; }
public Term() { }
public Term(string name, List<Course> courses)
{
Name = name;
Courses = courses;
}
Courses
[Table("Courses")]
public class Course
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
[ForeignKey(typeof(Term))]
public int TermID { get; set; }
public string Name { get; set; }
[OneToMany]
public List<Assessment> Assessments { get; set; }
[OneToMany]
public List<Note> Notes { get; set; }
public Course() { }
public Course(string name, List<Assessment> assessments, List<Note> notes)
{
Name = name;
Assessments = assessments;
Notes = notes;
}
}
Assessments
[Table("Assessments")]
public class Assessment
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
[ForeignKey(typeof(Course))]
public int CourseID { get; set; }
public string Name { get; set; }
public Assessment() { }
public Assessment(string name)
{
Name = name;
}
}
Notes
[Table("Notes")]
public class Note
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
[ForeignKey(typeof(Course))]
public int CourseID { get; set; }
public string Name { get; set; }
public string Text { get; set; }
public Note() { }
public Note(string name, string note)
{
Name = name;
Text = note;
}
}
And here is the code for inserting and getting objects:
Inserting
public bool SaveTermAsync(Term term)
{
if (term.ID != 0)
{
_database.UpdateWithChildrenAsync(term);
return true;
}
else
{
foreach (var course in term.Courses)
{
foreach (var assessment in course.Assessments)
{
_database.InsertAsync(assessment);
}
foreach (var note in course.Notes)
{
_database.InsertAsync(note);
}
_database.InsertAsync(course);
}
_database.InsertAsync(term);
_database.UpdateWithChildrenAsync(term);
return false;
}
}
Getting
public Task<List<Term>> GetTermsAsync()
{
return _database.GetAllWithChildrenAsync<Term>();
}
I know it's a bit of a code dump, but I have no idea where or what could be going wrong. If anyone could give any information about what is potentially going wrong, that would be awesome. Perhaps I'm simply expecting something to happen that isn't actually how it works. I don't know.
Also, if anyone has any links to some better documentation than https://bitbucket.org/twincoders/sqlite-net-extensions/src/master/ that would be awesome
EDIT
I tried using Cascading Options as well, CascadeRead, CascadeInsert, and CascadeAll. Using CascadeInsert or CascadeAll with _database.InsertWithChildrenAsync(term, true) resulted in a crash. The crash does not provide any error messages, and even wrapping the InsertWithChildren with a try catch block didn't work. Removing the recursive bool caused the program not to crash, and actually get the closest to what I'm looking for. Assessments and Notes are no longer null, but are still empty. Here's my updated code:
Saving and Getting:
public async Task<List<Term>> GetTermsAsync()
{
return await _database.GetAllWithChildrenAsync<Term>(recursive: true);
}
public async void SaveTermAsync(Term term)
{
if (term.ID != 0)
{
await _database.UpdateWithChildrenAsync(term);
}
else
{
//Trying this with recursion results in crash
await _database.InsertWithChildrenAsync(term);
}
}
One-To-Many Relationships:
//In Term
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<Course> Courses { get; set; }
//In Courses
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<Assessment> Assessments { get; set; }
[OneToMany(CascadeOperations = CascadeOperation.All)]
public List<Note> Notes { get; set; }
Also, I forgot to include last time how I'm populating the tables in the first place.
public bool CreateTables()
{
_database.CreateTableAsync<Term>().Wait();
_database.CreateTableAsync<Course>().Wait();
_database.CreateTableAsync<Assessment>().Wait();
_database.CreateTableAsync<Note>().Wait();
return true;
}
public Task<int> ClearTablesTest()
{
_database.DropTableAsync<Term>();
_database.DropTableAsync<Course>();
_database.DropTableAsync<Assessment>();
return _database.DropTableAsync<Note>();
}
async public Task<int> PopulateTestData()
{
await ClearTablesTest();
CreateTables();
Term term = new Term("Test Term", true, DateTime.Now, DateTime.Now.AddDays(10),
new List<Course>
{
new Course("Test Course", CourseStatus.Completed, "Guys Name", "(999)-999-9999", "email#gmail.com", 6, DateTime.Now, DateTime.Now.AddDays(10),
new List<Assessment>
{
new Assessment("Test Assessment", AssessmentType.Objective, false, DateTime.Now, DateTime.Now.AddDays(10))
},
new List<Note>
{
new Note("Test Note", "This is a test note.")
})
});
App.Database.SaveTermAsync(term);
return 0;
}
I finally figured out what was causing the crash as well as causing general confusion within SQLite-Net Extensions.
In my Assessment class, the property
public string BackgroundColor
{
get { return IsComplete ? "#558f45" : "Gray"; }
set { BackgroundColor = value; }
}
was causing the crash when recursion was used. I've been scouring the web for over two weeks looking for solutions to this issue, but haven't found anything similar to this. I submitted a bug report on the SQLite-Net Extensions bitbucket.
If anyone knows why this specific line would cause issues, I'd love to hear your input. Until then I'm going to mark this question as answered and continue work on my app.
Thanks #redent84 for your help thus far on this issue.

Get subcollection of MongoDB collection with C# driver based on search

I have this project with https://github.com/Mech0z/Foosball/blob/master/Models/Old/PlayerRankHistory.cs
I have the following classes where PlayerRankHistory is saved in MongoDB, this contains a list of PlayerRankHistorySeasonEntry which each contains PlayerRankHistoryPlot.
I would then like to provide an email of a player and a seasonname and then only get the list PlayerRankHistoryPlots out as a list, but the code I have written is very slow and not faster than just providing only an email and getting much more data out
And as a side note, not sure how to write it to make it async
The query I have now is
public async Task<List<PlayerRankHistoryPlot>> GetPlayerRankEntries(string email, string seasonName)
{
var query = Collection.AsQueryable().SingleOrDefault(x => x.Email == email)
.PlayerRankHistorySeasonEntries.SingleOrDefault(x => x.SeasonName == seasonName).HistoryPlots;
List<PlayerRankHistoryPlot> result = query.ToList();
return result;
}
public class PlayerRankHistory
{
public PlayerRankHistory(string email)
{
Email = email;
PlayerRankHistorySeasonEntries = new List<PlayerRankHistorySeasonEntry>();
}
public Guid Id { get; set; }
public string Email { get; set; }
public List<PlayerRankHistorySeasonEntry> PlayerRankHistorySeasonEntries { get; set; }
}
public class PlayerRankHistorySeasonEntry
{
public PlayerRankHistorySeasonEntry(string seasonName)
{
SeasonName = seasonName;
HistoryPlots = new List<PlayerRankHistoryPlot>();
}
public string SeasonName { get; set; }
public List<PlayerRankHistoryPlot> HistoryPlots { get; set; }
}
public class PlayerRankHistoryPlot
{
public PlayerRankHistoryPlot(DateTime date, int rank, int eloRating)
{
Date = date;
Rank = rank;
EloRating = eloRating;
}
public DateTime Date { get; set; }
public int Rank { get; set; }
public int EloRating { get; set; }
}
An example of a document
{"_id":"AYU3e3Qgw0Gut1fngze80g==","Email":"someemail#gmail.com","PlayerRankHistorySeasonEntries":[{"SeasonName":"Season 1","HistoryPlots":[{"Date":"2020-01-10T12:24:12.511Z","Rank":11,"EloRating":1488},{"Date":"2020-01-13T12:51:41.597Z","Rank":12,"EloRating":1488},{"Date":"2020-01-15T11:11:43.223Z","Rank":10,"EloRating":1510},{"Date":"2020-01-15T11:11:45.049Z","Rank":8,"EloRating":1530},{"Date":"2020-01-15T12:14:58.042Z","Rank":9,"EloRating":1530},{"Date":"2020-01-15T12:14:59.886Z","Rank":8,"EloRating":1530}]}]}
I believe when you define Collection.AsQueryable().FirstOrDefault(), you are pulling all records in that collection and then filtering through them. You should use the Find() method that is provided by MongoDB C# driver to filter the records which is much faster as well.
Get the PlayerRankHistory objects based on the email address
From on the filtered records, only return the records that have the required season
Get the HostoryPlots for only the first match as list
Collection.Find(Builders<PlayerRankHistory>.Filter.Eq(x => x.Email, email))
.Select(y => y.PlayerRankHistorySeasonEntries.Where(z => z.SeasonName.Equals(seasonName)))
.FirstOrDefault()?.HistoryPlots
.ToList();

Entity Framework core .Include() issue

Been having a play about with ef core and been having an issue with the include statement. For this code I get 2 companies which is what i expected.
public IEnumerable<Company> GetAllCompanies(HsDbContext db)
{
var c = db.Company;
return c;
}
This returns
[
{
"id":1,
"companyName":"new",
"admins":null,
"employees":null,
"courses":null
},
{
"id":2,
"companyName":"Test Company",
"admins":null,
"employees":null,
"courses":null
}
]
As you can see there are 2 companies and all related properties are null as i havnt used any includes, which is what i expected. Now when I update the method to this:
public IEnumerable<Company> GetAllCompanies(HsDbContext db)
{
var c = db.Company
.Include(t => t.Employees)
.Include(t => t.Admins)
.ToList();
return c;
}
this is what it returns:
[
{
"id":1,
"companyName":"new",
"admins":[
{
"id":2,
"forename":"User",
"surname":"1",
"companyId":1
}
]
}
]
It only returns one company and only includes the admins. Why did it not include the 2 companies and their employees?
public class Company
{
public int Id { get; set; }
public string CompanyName { get; set; }
public List<Admin> Admins { get; set; }
public List<Employee> Employees { get; set; }
public List<Course> Courses { get; set; }
public string GetFullName()
{
return CompanyName;
}
}
public class Employee
{
public int Id { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public int CompanyId { get; set; }
[ForeignKey("CompanyId")]
public Company company { get; set; }
public ICollection<EmployeeCourse> Employeecourses { get; set; }
}
public class Admin
{
public int Id { get; set; }
public string Forename { get; set; }
public string Surname { get; set; }
public int CompanyId { get; set; }
[ForeignKey("CompanyId")]
public Company Company { get; set; }
}
I'm not sure if you've seen the accepted answer to this question, but the problem is to do with how the JSON Serializer deals with circular references. Full details and links to more references can be found at the above link, and I'd suggest digging into those, but in short, adding the following to startup.cs will configure the serializer to ignore circular references:
services.AddMvc()
.AddJsonOptions(options => {
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});
Make sure you are using Include from "Microsoft.EntityFrameworkCore" And Not from "System.Data.Entity"
Lazy loading is not yet possible with EF Core. Refer here.
Alternatively you can use eager loading.
Read this article
Below is the extension method i have created to achieve the eager loading.
Extension Method:
public static IQueryable<TEntity> IncludeMultiple<TEntity, TProperty>(
this IQueryable<TEntity> source,
List<Expression<Func<TEntity, TProperty>>> navigationPropertyPath) where TEntity : class
{
foreach (var navExpression in navigationPropertyPath)
{
source= source.Include(navExpression);
}
return source.AsQueryable();
}
Repository Call:
public async Task<TEntity> FindOne(ISpecification<TEntity> spec)
{
return await Task.Run(() => Context.Set<TEntity>().AsQueryable().IncludeMultiple(spec.IncludeExpression()).Where(spec.IsSatisfiedBy).FirstOrDefault());
}
Usage:
List<object> nestedObjects = new List<object> {new Rules()};
ISpecification<Blog> blogSpec = new BlogSpec(blogId, nestedObjects);
var challenge = await this._blogRepository.FindOne(blogSpec);
Dependencies:
public class BlogSpec : SpecificationBase<Blog>
{
readonly int _blogId;
private readonly List<object> _nestedObjects;
public ChallengeSpec(int blogid, List<object> nestedObjects)
{
this._blogId = blogid;
_nestedObjects = nestedObjects;
}
public override Expression<Func<Challenge, bool>> SpecExpression
{
get { return blogSpec => blogSpec.Id == this._blogId; }
}
public override List<Expression<Func<Blog, object>>> IncludeExpression()
{
List<Expression<Func<Blog, object>>> tobeIncluded = new List<Expression<Func<Blog, object>>>();
if (_nestedObjects != null)
foreach (var nestedObject in _nestedObjects)
{
if (nestedObject is Rules)
{
Expression<Func<Blog, object>> expr = blog => blog.Rules;
tobeIncluded.Add(expr);
}
}
return tobeIncluded;
}
}
Will be glad if it helps. Please note this is not a production ready code.
I test your code, this problem exist in my test. in this post LINK Proposed that use data projection. for your problem Something like the following, is work.
[HttpGet]
public dynamic Get()
{
var dbContext = new ApplicationContext();
var result = dbContext.Companies
.Select(e => new { e.CompanyName, e.Id, e.Employees, e.Admins })
.ToList();
return result;
}
I know this is an old issue, but its the top result in google, so im putting my solution i I found here. For a Core 3.1 web project there is a quick fix.
Add nuget package Microsoft.EntityFrameworkCore.Proxies.
Then you simply just need to specify in your options builder when configuring your services. Docs: https://learn.microsoft.com/en-us/ef/core/querying/related-data
public void ConfigureServices(IServiceCollection services) {
services.AddDbContextPool<YourDbContext>(options => {
options.UseLazyLoadingProxies();
options.UseSqlServer(this.Configuration.GetConnectionString("MyCon"));
});
}
Now your lazy loading should work as it did in previous EF versions.
If you not using it for a web project, you can do it right inside of the OnConfiguringMethod inside of your DbContext itself.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.UseLazyLoadingProxies();
}
My EF stuff is kept in a separate class library so i can re-use it through multiple company applications. So having the ability to not lazy load when not needed for a particular application is useful. So i prefer passing in the build options, for reuse-ability purposes. But both will work.

C# Copy List items to Object Arrays

I have a list created from a stored procedure using EF6.0
I have also created 3 classes
public class Resas
{
public string todo{ get; set; }
public string prop { get; set; }
public string Code { get; set; }
public string statusCode { get; set; }
public string checkin { get; set; }
public string checkout { get; set; }
public List<profiles> profiles { get; set; }
}
public class profiles
{
public string action { get; set; }
public string id { get; set; }
public string profileType { get; set; }
public string title { get; set; }
public string firstName { get; set; }
public string middleName { get; set; }
public string lastName { get; set; }
public List<emailAddresses> emailAdresses { get; set; }
}
public class emailAddresses
{
public string emailAddress { get; set; }
public string emailAddress2 { get; set; }
}
I am doing a for-loop in the list and I need to get certain columns and put it in the array (I will put two, to keep it simple)
myEntities db = new myEntities();
List<rev_Result> revList = new List<rev_Result>();
revList.Clear();
revList = db.rev().ToList();
for (int i = 0; i < revList.Count(); i++)
{
Resas resas = new Resas();
profiles[] profiles = new profiles[1];
resas.todo = revList[i].todo;
resas.profiles[0].lastName = revList[i].lastName;
}
I am not familiar with C# as you can see from the psedo-code above.
I cannot figure out how to feed the Resas with data and then its Profile with data and then move to the next Resas entry.
Any help appreciated.
That's fairly simple using Linq:
Resas resas = new Resas();
resas.profiles = revList
.Select(x => new profiles() { action = x.todo, lastName = x.lastName })
.ToList();
What's happening here is: You loop through every entry in revList and get your wanted data structure (that's what Select is doing). x refers to the current entry in the loop, while the stuff to the right side of the arrow is you 'output': a new instance of your profiles class with the members assigned accordingly. The result of all of this is then converted to a list (before ToList(), think of it as a recipe to create the list) and assigned to resas.profiles.
By the way, a word on conventions: Usually, in C#, you would give your classes a name that starts with a capital letter. Also, your profiles class seems to contain data of exactly one profile, so a better name might be Profile. This also makes your data structure more clear, since List<profiles> seems to be a list of lists of profiles - but that's not what it actually is, is it?
Furthermore, Members generally start with a capital letter as well, so instead of action, lastName, you'd have: Action and LastName.
You can try with Linq. This is the code that should solve your issue, but Resas class doesn't have action property:
List<Resas> ls = revList.Select(x => new Resas() {
action = x.todo,
profiles = new List<profiles>() {
new profiles { lastName = x.lastName }
}
).ToList();
If you need to use action property of inprofiles` class:
List<Resas> ls = revList.Select(x => new Resas() {
profiles = new List<profiles>() {
new profiles {
action = x.todo,
lastName = x.lastName
}
}
).ToList();

502 error while converting entity framework data to json. Possible recursion. How to prevent it?

[HttpGet("/api/notes/suggested")]
public JsonResult GetSuggestedNotes(string searchText)
{
//TODO: Podpowiedzi przy wpisywaniu tytułu
JsonResult result = null;
try {
List<Note> n = db.Notes.Include(x => x.NoteTags).ToList();
result = Json(n);
}
catch(Exception e)
{
Console.WriteLine(e);
}
return result;
}
public class Note
{
public Note()
{
CreationDate = DateTime.Now;
NoteTags = new HashSet<NoteTag>();
Parts = new HashSet<Part>();
}
public int ID { get; set; }
public virtual ICollection<NoteTag> NoteTags { get; set; }
public virtual ICollection<Part> Parts { get; set; }
public DateTime? CreationDate { get; set; }
[NotMapped]
public string TagsToAdd { get; set; }
[NotMapped]
public string TagsAsSingleString {
get
{
string result = "";
foreach(var nt in NoteTags)
{
result += nt.Tag.Name + " ";
}
return result;
}
}
}
public class NoteTag
{
public int NoteId { get; set; }
public virtual Note Note { get; set; }
public int TagId { get; set; }
public virtual Tag Tag { get; set; }
}
When I try to get data using this WebAPI controller, I get 502 bad gateway. No errors, everything's fine while debugging server. Data get from database correctly.
I suspect that it could be something similar to "infinite loop" but how to prevent it? (Note class is connected to collection of NoteTag objects that are connected back to Note which probably makes this loop).
And why there are no errors if something went wrong? :/
I don't know if it still relevant but i had the same problem and what worked for me it to Configure Newtonsoft.Json
SerializerSettings.ReferenceLoopHandling = ewtonsoft.Json.ReferenceLoopHandling.Ignore.
If you are using VS2015 MVC you can add the following code:
services.AddMvc().AddJsonOptions(options => options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
in the ConfigureServices method in the Startup class.
I think the problem its recursion, can you try with an Anonymous type
NoteTags has Note , imagine if the Note->NoteTags->Note->NoteTags->Note->NoteTags ...
`List n = db.Notes.Include(x => x.NoteTags).ToList();
var e = n.select(x=> new {property=value});
result = Json(e);`

Categories

Resources