List within data record empty - c#

i have this class which contains a list of the object ConversieDetail
public class ConversieRun
{
[Key]
public String Guid { get; set; }
public String Naam { get; set; }
public String Status { get; set; }
public DateTime Start { get; set; }
public DateTime? Einde { get; set; }
public List<ConversieDetails> Details { get; set; }
}
With the following method i need to return a list of ConversieRun including the ConversieDetails
public List<PGData.ConversieRun> GetAll()
{
//var result = _context.CoversieDetails.ToList();
return _context.ConversieRun.ToList();
}
however when i return with above example the conversieDetail List is null.
now when i uncomment the result list, the List of conversieDetails will be filled in the ConversieRun object as expected.
any reason why the list of ConversieDetails is null if i don't get them first in another list?
thanks in advance.

Relationships in entities are not loaded by default and will be null. You can explicitly tell EF to also load the related entities by using the Include like this:
_context.ConversieRun.Include(x => x.Details).ToList();
Now all ConversieRun entities will be loaded including their details.
You can read more about this in the "Loading Related Data" section of the documentation (https://learn.microsoft.com/en-us/ef/core/querying/related-data)
The example here is using the Explicit loading method, you can also choose to use the Lazy loading method where the related entities are loaded when you request them. This can however have a negative impact on the number of database queries as it would run a separate query for every ConversieRun entity to get its details.

Related

What's the correct way to reference tables using Code First with EF.Core for searching efficiently

Fairly new to EF.Core and I'm having some issues as my tables start getting more complex. Here's an example of what I have defined for my classes. Note ... there are many more columns and tables than what I have defined below. I've paired them down for brevity.
public class User
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public bool Active { get; set; }
}
Followed by
public class JournalEntry
{
public int Id { get; set; }
public int UserId { get; set; }
public string Details { get; set; }
public DateTime DateEntered { get; set; }
public virtual User User { get; set; }
}
I want to be able to issue the following query and INCLUDE the User Table so that I can then populate a ViewModel with columns from the User Table without having to do another lookup and also to sort the data while retrieving it:
public IQueryable<JournalEntry> GetByUser(int userId)
{
return _DbContext.JournalEntries.Where(j => j.UserId == userId)
.Include(u => u.User)
.OrderBy(u=> u.User.FirstName)
.ThenBy(j => j.DateEntered);
}
My controller would then have something similar to the following:
public IActionResult List(int userId)
{
var journalEntries = new _dbRepository.GetByUser(userId);
var myViewModel = new MyViewModel();
myViewModel.UserName = ($"{journalEntries.User.FirstName} {journalEntries.User.LastName}");
myViewModel.Entries = journalEntries;
etc ....
return View(myViewModel);
}
I'm loading the user's first and last name in the View Model and whatever other attributes from the various tables that are referenced. The problem that I'm having is that I'm getting errors on the Migration creation "Foreign key constraint may cause cycle or multiple cascade paths." And of course, if I remove the line reading public virtual User User { get; set; } from the JournalEntry class then the problem goes away (as one would expect).
I believe that the way I'm doing the models is incorrect. What would be the recommended way that I should code these models? I've heard of "lazy loading". Is that what I should be moving towards?
Thanks a bunch.
--- Val
Your query returns an IQueryable<JournalEntry> not a JournalEntry.
Change the code to get the user details from the first object:
var myViewModel.UserName = ($"{journalEntries.First().User.FirstName} {journalEntries.First().User.LastName}");
In the line above I'm calling First() on your journal entries collection and that would have a User. Then I can access FirstName and LastName.
Also, don't bother with LazyLoading since you are learning. It could cause select n+1 issues if used incorrectly

Merge properties from mapping table to single class

I have a website that is using EF Core 3.1 to access its data. The primary table it uses is [Story] Each user can store some metadata about each story [StoryUserMapping]. What I would like to do is when I read in a Story object, for EF to automatically load in the metadata (if it exists) for that story.
Classes:
public class Story
{
[Key]
public int StoryId { get; set; }
public long Words { get; set; }
...
}
public class StoryUserMapping
{
public string UserId { get; set; }
public int StoryId { get; set; }
public bool ToRead { get; set; }
public bool Read { get; set; }
public bool WontRead { get; set; }
public bool NotInterested { get; set; }
public byte Rating { get; set; }
}
public class User
{
[Key]
public string UserId { get; set; }
...
}
StoryUserMapping has composite key ([UserId], [StoryId]).
What I would like to see is:
public class Story
{
[Key]
public int StoryId { get; set; }
public bool ToRead { get; set; } //From user mapping table for currently logged in user
public bool Read { get; set; } //From user mapping table for currently logged in user
public bool WontRead { get; set; } //From user mapping table for currently logged in user
public bool NotInterested { get; set; } //From user mapping table for currently logged in user
public byte Rating { get; set; } //From user mapping table for currently logged in user
...
}
Is there a way to do this in EF Core? My current system is to load the StoryUserMapping object as a property of the Story object, then have Non-Mapped property accessors on the Story object that read into the StoryUserMapping object if it exists. This generally feels like something EF probably handles more elegantly.
Use Cases
Setup: I have 1 million stories, 1000 users, Worst-case scenario I have a StoryUserMapping for each: 1 billion records.
Use case 1: I want to see all of the stories that I (logged in user) have marked as "to read" with more than 100,000 words
Use case 2: I want to see all stories where I have NOT marked them NotInterested or WontRead
I am not concerned with querying multiple StoryUserMappings per story, e.g. I will not be asking the question: What stories have been marked as read by more than n users. I would rather not restrict against this if that changes in future, but if I need to that would be fine.
Create yourself an aggregate view model object that you can use to display the data in your view, similar to what you've ended up with under the Story entity at the moment:
public class UserStoryViewModel
{
public int StoryId { get; set; }
public bool ToRead { get; set; }
public bool Read { get; set; }
public bool WontRead { get; set; }
public bool NotInterested { get; set; }
public byte Rating { get; set; }
...
}
This view model is concerned only about aggregating the data to display in the view. This way, you don't need to skew your existing entities to fit how you would display the data elsewhere.
Your database entity models should be as close to "dumb" objects as possible (apart from navigation properties) - they look very sensible as they are the moment.
In this case, remove the unnecessary [NotMapped] properties from your existing Story that you'd added previously.
In your controller/service, you can then query your data as per your use cases you mentioned. Once you've got the results of the query, you can then map your result(s) to your aggregate view model to use in the view.
Here's an example for the use case of getting all Storys for the current user:
public class UserStoryService
{
private readonly YourDbContext _dbContext;
public UserStoryService(YourDbContext dbContext)
{
_dbContext = dbContext;
}
public Task<IEnumerable<UserStoryViewModel>> GetAllForUser(string currentUserId)
{
// at this point you're not executing any queries, you're just creating a query to execute later
var allUserStoriesForUser = _dbContext.StoryUserMappings
.Where(mapping => mapping.UserId == currentUserId)
.Select(mapping => new
{
story = _dbContext.Stories.Single(story => story.StoryId == mapping.StoryId),
mapping
})
.Select(x => new UserStoryViewModel
{
// use the projected properties from previous to map to your UserStoryViewModel aggregate
...
});
// calling .ToList()/.ToListAsync() will then execute the query and return the results
return allUserStoriesForUser.ToListAsync();
}
}
You can then create a similar method to get only the current user's Storys that aren't marked NotInterested or WontRead.
It's virtually the same as before, but with the filter in the Where to ensure you don't retrieve the ones that are NotInterested or WontRead:
public Task<IEnumerable<UserStoryViewModel>> GetForUserThatMightRead(string currentUserId)
{
var storiesUserMightRead = _dbContext.StoryUserMappings
.Where(mapping => mapping.UserId == currentUserId && !mapping.NotInterested && !mapping.WontRead)
.Select(mapping => new
{
story = _dbContext.Stories.Single(story => story.StoryId == mapping.StoryId),
mapping
})
.Select(x => new UserStoryViewModel
{
// use the projected properties from previous to map to your UserStoryViewModel aggregate
...
});
return storiesUserMightRead.ToListAsync();
}
Then all you will need to do is to update your View's #model to use your new aggregate UserStoryViewModel instead of your entity.
It's always good practice to keep a good level of separation between what is "domain" or database code/entities from what will be used in your view.
I would recommend on having a good read up on this and keep practicing so you can get into the right habits and thinking as you go forward.
NOTE:
Whilst the above suggestions should work absolutely fine (I haven't tested locally, so you may need to improvise/fix, but you get the general gist) - I would also recommend a couple of other things to supplement the approach above.
I would look at introducing a navigation property on the UserStoryMapping entity (unless you already have this in; can't tell from your question's code). This will eliminate the step from above where we're .Selecting into an anonymous object and adding to the query to get the Storys from the database, by the mapping's StoryId. You'd be able to reference the stories belonging to the mapping simply by it being a child navigation property.
Then, you should also be able to look into some kind of mapping library, rather than mapping each individual property yourself for every call. Something like AutoMapper will do the trick (I'm sure other mappers are available). You could set up the mappings to do all the heavy lifting between your database entities and view models. There's a nifty .ProjectTo<T>() which will project your queried results to the desired type using those mappings you've specified.

Multiple relations to the same object. Lists are empty

I have two tables in my database that are linked with 2x 1 to many relations to the same object.
Since we added the second DBLot2 to the database the list in DBLot is not filled with objects anymore.
Is there something we did wrong here?
public class DBNesting
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long DBNestingID { get; set; }
public DBLot DBLot { get; set; }
[ForeignKey("DBLot")]
public long DBLotID { get; set; }
public DBLot DBLot2 { get; set; }
[ForeignKey("DBLot2")]
public long? DBLot2ID { get; set; }
}
public class DBLot
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long DBLotID { get; set; }
public List<DBNesting> Nestingen { get; set; }
}
This is how we get the objects:
DatabaseContext dc = new DatabaseContext();
dc.DBNesting
.include("DBLot")
.include("DBLot2")
.where(...)
.ToList();
However the other side is not working:
dc.DBLot
.include("Nestingen")
.where(...)
.ToList()
I would expect that all the DBNesting where we used a DBLot in property
DBLot ore DBLot2 shoud be in Nestingen. But the collections are empty.
dc.DBLot
.include("Nestingen")
.where(...)
.ToList()
will not include the DBLot on the Nestingen only the direct object.
So it will have DBLot and a list of Nestingen but that list will not have the DBLot of each of the Nestingen in the list.
So basically you should be able to see... that you have recursion here, object has reference to object which reference itself.
dc.DBLot.include("Nestingen")
.include("Nestingen.DBLot")
.include("Nestingen.DBLot2")
.where(...)
.ToList()
may work, again will only now bring one level deeper, but if that all you need then awesome.
you could enable lazy loading... but also come with "responsibility" wouldn't recommend
ef 6 is not very efficient with include. also there is an extension which allows you to use typed version so include(x=>x.Nestingen), just take the string names out.
is the goal to have nested object relations.. nth levels. something like
Tree data structure in C#

Use JSON data set as seed for EntityFramework database

I'm trying to expose static objects using Microsoft.EntityFrameworkCore in a .netcoreapp 2.1.
The data I want to expose is in a .json file, and I can deserialize it without any issue into its corresponding c# classes.
Here's a sample of the structure : https://pastebin.com/SKCKsDJi
For the sake of clarity i suggest you read it using your favourite json reader
And here are the c# version of those objets :
public class FoodItem
{
public int Id { get; set; }
public string Name { get; set; }
public FoodType Type { get; set; }
public string Picture { get; set; }
public float Price { get; set; }
public string Currency { get; set; }
public IEnumerable<Ingredient> Ingredients { get; set; }
public bool IsVegetarian { get; set; }
}
public class FoodType
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Ingredient
{
public int Id { get; set; }
public string Name { get; set; }
}
Lets keep it simple though, there are ingredients, types and items, items are basically sandwiches, which are of a certain type and contain a list of ingredients. All 3 have id's to match them. This is where my problem lies, or so I think.
Everything works fine if I'm just using "Types" for example, in my dbcontext. As soon as I try to add either ingredients or items, or all 3 (which I need, but baby steps), I have the following error.
InvalidOperationException: The instance of entity type 'Ingredient' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
This is caused when :
public EatupController(EatupContext context)
{
_context = context;
var completeModel = JsonConvert.DeserializeObject<EatUpDataModel>(EatUpDataSet.Complete);
_context.Types.AddRange(completeModel.Types); //Works
_context.Ingredients.AddRange(completeModel.Ingredients); //Crashes here.
//If removed, all is fine but data is incomplete
//_context.Types.AddRange(completeModel.Types); //Unused
//_context.Items.AddRange(completeModel.Items); //Unused
_context.SaveChanges();
}
I don't understand why it's complaining about duplicate ID's, because they're all identical. Except, when I'm referencing the ingredient X in an item, obviously some items will use ingredients used by other items (many sandwiches have tomatoes). But surely that type of relation is allowed.
At first I had id's starting at 0 for all different types of objets, so ingredients ranged from 0 to about 100, items from 0 to 60, and types from 0 to 7. But since I had that error I edited all Id's and I still have the error, which is very confusing.
From what I read, this might also be due to using the context in different threads but this is not the case. If I remove the line that crashes, it stops crashing and I can see the data in the context correctly. In this case, only the types. If I add only the items or the ingredients in the context, it crashes for the same reason, just in another object (ingredient or item).
Where should I go from here? I don't even have a bad solution I could try to implement. My worst idea was to manually change the Id's (which is silly to me, it should work with the older ones), but even that failed.

Searching if all properties exist in a list of entities using Linq

I have the following entities:
[Table("Entities")]
public abstract class Entity {
public int EntityID { get; set; }
public string Description { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
And the Tag Entity:
public class Tag {
public int TagID { get; set; }
public int EntityID { get; set; }
public string TagValue { get; set; }
}
As the above makes sort of clear Tags are not reused just stored as strings. This has made determining whether Entities share tags slightly more difficult (and slow).
I have a working search to return a list of entities in which the entities contain any of the tags:
List<string> searchTags = new List<String> {"test1", "test2"};
entities = (_entityRepository.Entities
.Where(o=>o.Tags.Any(f=>searchTags.Contains(f.TagValue)));
Now I also need to return a list of Entities which contain all of the tags in the list. As a non-property variable cannot be passed into a Contains method I cannot just reverse the order of the call with an all, but this is basically what I am trying to achieve:
entities = (_entityRepository.Entities
.Where(o=>o.Tags.All(f=>f.TagValue.Contains(searchTags)));
I think I have just hit the point where I need to correct the DB schema to re-use Tags which should provide a general performance benefit when filtering my entities on lists of Tags, but I still wanted to ask the following questions:
Am I over complicating this and is there a simple Linq statement which accomplishes this or,
Is this something for which I would should use predicate builder to set my criteria?
This can be done like this:
var query = _entityRepository.Entities.Select(e => e);
query = searchTags.Aggregate(query, (current, tag) => current.Where(e => e.Tags.Any(t => t.TagValue == tag)));
var result = query.ToList();

Categories