I'm tearing my hair out trying to get my recursive EFCore objects to load all their grandparents/grandchildren and I can't decide if I'm being rubbish or I'm working around features which aren't working as desired.
A Link has a Category, which exists in a hierarchy of other Categories (the data structure works fine)
Link link = FP.Links.Include(a=>a.Transaction.Account.Home)
.Include(a=>a.Category)
.ThenInclude(b=>b.parent)
.AsEnumerable()
.Where(a=>a.TransId==_transId &&
a.Transaction.Account.Home==_authHome)
.FirstOrDefault();
When I look at the Link.Category.parent.parent it is null. It always seems to load the first couple of levels above, when I pull back a link whose category has more than 2 parents, it eventually gives me a null.
I've tried various SO answers which have guided me to use includes (which feels clunky) and the latest talks about .AsEnumerable(). I've tried FirstOrDefault() and SingleOrDefault().
I've got as far as: Loading in EFCore 2 is far from done, and that 'fixing up' is a thing. So, I tried adding:
List<Category> CL= FP.Categories.Where(a=>a.home==_authHome).ToList();
before the previous query and it works.
I have three questions:
1) Have I correctly interpreted that this forces my DBContext to have already loaded the data I need, and so it has access when I re-query?
2) Am I leaving myself open to this NOT working in some circumstances which testing wouldn't show me?
3) Is there any less inane way to make this same thing work?
(I may want link.Category.parent.parent.parent.(n).parent to all be accessible)
and a question that, knowing the rules of SO, I'll phrase rhetorically:
4) Is there any world in which this is desirable from a debugging/coding point of view????
Classes:
public class Link
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
[ForeignKey("Transaction")]
public int TransId { get; set; }
public DateTime effectiveDt { get; set; }
public bool isRefund { get; set; }
public int transactionRole { get; set; }
public int setBy { get; set; }
public string transactionComment { get; set; }
public virtual Transaction Transaction { get; set; }
public virtual Category Category { get; set; }
}
public partial class Category
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id {get; set;}
public string Name { get; set; }
public int categoryType { get; set; } //0=Normal, 1=Savings, 2=Loan
[JsonIgnore]
public virtual Category parent { get; set; }
[JsonIgnore]
public virtual ICollection<Summary> summaries { get; set; }
public virtual ICollection<Category> children { get; set; }
[JsonIgnore]
public virtual ICollection<Link> Link { get; set; }
[JsonIgnore]
public virtual ICollection<Budget> Budget { get; set; }
[JsonIgnore]
public virtual Home home { get; set; }
}
Related
I have the following code
public class Question
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Required]
public string QuestionText { get; set; }
public virtual ICollection<QuestionOption> QuestionOptions { get; set; }
}
public class QuestionOption
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[Required]
public string OptionText { get; set; }
[ForeignKey("QuestionId")]
public Question Question { get; set; }
public long QuestionId { get; set; }
public virtual ICollection<QuestionOptionAnswer> QuestionOptionAnswers { get; set; }
}
public class QuestionOptionAnswer
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[ForeignKey("QuestionOptionId")]
public QuestionOption QuestionOption { get; set; }
public long QuestionOptionId { get; set; }
}
and the following query to retrieve the question:
var dbResult = await (from question in context.Question.Include(x => x.QuestionOptions).ThenInclude(x => x.QuestionOptionAnswers)
where question.Id == id
select question).FirstOrDefaultAsync();
Everything works fine and the query returns what I really need except from the fact that I can see in a QuestionOption the parent Entity(Question) which I already have. I have cases that I have 10 question options for a question and each one has the Question parent entity returned. This is not desired as I am getting the same thing I already have multiple times. How can I prevent this from happening?
You aren't getting the parent object multiple times, each parent is returned as a single row from the database and one object is created for each Question.
It is referenced in multiple places though, including in your QuestionOption, which as little impact on performance.
I am using Entity Framework Core 2.0.1 and I have the following models
public class Article
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
[Key]
public int Id { get; set; }
[Required]
public string Title { get; set; }
public string Slug { get; set; }
public int Approved { get; set; }
public DateTime ArticleDate { get; set; }
// ... some other fields
public virtual ICollection<ArticleCategoryRelation> ArticleCategoryRelations { get; set; }
}
public class ArticleCategory
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
//... soem other fields
[ForeignKey("ArticleCategoryParent")]
public int? ArticleCategoryParentID { get; set; }
public virtual ArticleCategory ArticleCategoryParent { get; set; }
public virtual ICollection<ArticleCategory> SubCategories { get; set; }
public virtual ICollection<ArticleCategoryRelation> ArticleCategoryRelations { get; set; }
}
public class ArticleCategoryRelation
{
[Column(Order = 0)]
public int ArticleId { get; set; }
public Article Article { get; set; }
[Column(Order = 1)]
public int ArticleCategoryId { get; set; }
public ArticleCategory ArticleCategory {get; set;}
}
Every article belongs to one or more categories. Categories might have parent category.
I want to get from database last two articles (where Approved = 1) with related category details, for each category that belongs to a parent category which id is given as input.
I have tried but with no success. I can't filter results of an .Include() entity. Is it possible... or I don't know how to do it?
All my data are accessed through entity framework with appContext (the context used to get entities from database). Can I achieve what I want through entity framework core (lambda expression is preferred over Linq if possible), or should I use ADO.NET library (which I know how to execute custom queries).
P.S. I want to get data only to show in the view... no edit is needed.
You don't actually need to include here at all, as far as I can tell. Whenever you use data from a nav property, EF will go get the data from that table, as best it can filter it.
var CategoriesUnderParent = AppContext.ArticleCategories
.Where(c => c.ArticleCategoryParent == {parent});
foreach(var category in CategoriesUnderParent)
{
var ArticlesAllowed = category.ArticleCategoryRelations
.Where(acr => acr.Article.Approved == 1).Select(a => a.Article);
var ArticlesPicked = ArticlesAllowed
.OrderByDescending(ar => ar.ArticleDate)
.Take(2);
// Do something with your data
}
I have the following table design.
As can be seen here, there is a one to many relationship, with the many on the EpisodePatient side.
Then, I have the following classes.
public class EpisodeModel
{
public int ID { get; set; }
public virtual EpisodePatientModel EpisodePatient { get; set; }
}
public class EpisodePatientModel
{
public int EpisodePatientID { get; set; }
public virtual EpisodeModel Episode { get; set; }
}
I am setting up the relationship, in Entity Framework, to be a one to 0 or many. The reason for this is, I am selecting all EpisodePatients from a View, and I want the Episode to be Lazy loaded when accessed.
This is how I am setting up my relationship.
modelBuilder.Entity<EpisodePatientModel>().HasRequired(r => r.Episode).WithOptional(o => o.EpisodePatient);
I want this to act as a One to zero or many in my code, as an Episode will always have an EpisodePatient, and vice versa.
The problem I am facing is, when I load the EpisodePatient, and try to access the Episode linked item, it is always null, and Lazy loading does not occur.
What am I doing wrong here?
UPDATE
This is how I load the original EpisodePatient items.
this.DbContext.EpisodePatients.AsNoTracking();
I re-created your model but with data annotations like below and it workes fine:
public class EpisodeModel
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public virtual EpisodePatientModel EpisodePatient { get; set; }
}
public class EpisodePatientModel
{
public string Name { get; set; }
[Key, ForeignKey("Episode")]
public int EpisodeId { get; set; }
public virtual EpisodeModel Episode { get; set; }
}
Try without AsNoTracking(), because if you use it your context is not tracking and you can't include more data if you need.
And try change to relation one to many.
modelBuilder.Entity<EpisodePatientModel>().HasRequired<Episode>(s => s.Episode).WithMany(s => s.EpisodePatient);
I'm not sure the best way to create this kind of relation ship. I have these two entities for this example.
Person & Address
public class Person
{
public string Name { get; set; }
public virtual ICollection<PersonAddressLink> HomeAddresses { get; set; }
public virtual ICollection<PersonAddressLink> WorkAddresses { get; set; }
}
public class Address
{
public string AddressString {get; set; }
public virtual ICollection<Person> People { get; set; }
}
and a link table, needed because it contains other info.
public class PersonAddressLink
{
public Address HomeAddress { get; set; }
public Address WorkAddress { get; set; }
public int SomeOtherInt { get; set; }
public string SomeOtherString { get; set; }
}
The problem is EF doesn't know how to separate the entities on person.HomeAddresses / person.WorkAddresses. I have tried mergin HomeAddress & WorkAddresses into a single collection like this:
public virtual ICollection<PersonAddressLink> WorkAddresses { get; set; }
but it still won't work.
I'm just looking for advice on how to lay something like this out to get it working with EF Code first.
I hope that makes sense.
Thanks
Late reply but I got the mapping correct by creating the table in SQL Management studio and using the reverse engineer functionality which generated the Code First. I need to use two separate entities.
I have two tables and one is self-referencing like this:
Job (id, description)
JobAssembly (id, jobid, parentassemblyid)
I have two domain objects in similar fashion:
public class Job
{
public int Id { get; set; }
public string Description { get; set; }
public virtual List<JobAssembly> Assemblies { get; set; }
}
public class JobAssembly
{
public int Id { get; set; }
public int JobId { get; set; }
public virtual Job { get; set; }
public int? ParentAssemblyId { get; set; }
public virtual JobAssembly ParentAssembly { get; set; }
public virtual List<JobAssembly> SubAssemblies { get; set; }
}
Here is the problem. When I use EF:
using (var db = new JobContext())
{
var job = db.Jobs.Find(1);
}
I get, as expected, the job requested. But it comes with ALL of the assemblies - not just the parents but the sub assemblies. Again this is expected.
My question is: How do I instruct EF to only bring in JobAssemblies which have no subassemblies... as the default behavior? I know how to query EF for said parent assemblies. But is there a way to set the mappings, or some other way, to set the default query behavior to only get the assemblies whose parentassemblyid == null?
Thanks :)
EDIT:
Allow me to illustrate:
I have a Job with id = 1. It has one Assembly with id = 1. Assembly 1 has two sub assemblies with ids = 2 and 3 respectively. When var job = db.Jobs.Find(1) is executed, EF populates the object graph like so:
The job has all three assemblies (because jobid on all three == 1). JobAssembly with id 1 has its subassemblies populated appropriately.
All of this is expected, but it would be nice if I could customize how EF is loading the objects. Job should not have every JobAssembly where JobId == 1, but only where JobId == 1 and ParentAssemblyId == null.
If I understand correctly, you want Job.Assemblies to contain only those Assemblies that have no parent (i.e. those Assemblies that are direct children of the Job, rather than grandchildren etc).
The "normal" way to do this would be to only have the direct children reference the Job via foreign key, and have the grandchildren etc. reference only their parents.
It seems likely to me that the Assemblies table has been created like this in order to optimise data reads (i.e. so you only have to query once on JobId, then you can create the tree structure in memory). I'm going to assume that's the case, rather than telling you to change your database structure. If that's not the case, let me know.
There are a few ways that you can get only the direct children of your Job. The simplest way would be to just have a property of your Job class do the filtering for you:-
public class Job
{
public int Id { get; set; }
public string Description { get; set; }
public virtual List<JobAssembly> Assemblies { get; set; }
public IEnumerable<JobAssembly> DirectChildren
{
get
{
return this.Assemblies == null
? null
: this.Assemblies.Where(x => x.ParentAssemblyId == null);
}
}
}
but if you're going to take this approach you need to be really really careful that you're not lazy loading data in a silly way. Some people, when faced with a problem, think "I know, I'll use an O/RM". Now they have N+1 problems ;)
A more robust solution would be to use a separate ViewModel to encapsulate the tree structure you want in your application tier. This prevents Select N+1 issues because your data tier takes responsibility for pulling the whole list of Assemblies in a single query, then maps them into a tree for your application tier:-
public class JobViewModel
{
public int Id { get; set; }
public string Description { get; set; }
public virtual List<JobAssemblyViewModel> Children { get; set; }
}
public class JobAssemblyViewModel
{
public int Id { get; set; }
public virtual List<JobAssemblyViewModel> Children { get; set; }
}
If you do this a lot, you may want to consider using e.g. AutoMapper to project your queries onto your view models for you.
Here's an idea using inheritance to distinguish between RootAssemblies and SubAssemblies:
public abstract class JobAssembly
{
public int Id { get; set; }
public virtual List<SubAssembly> SubAssemblies { get; set; }
}
public class SubAssembly : JobAssembly
{
public int ParentAssemblyId { get; set; }
public virtual JobAssembly ParentAssembly { get; set; }
}
public class RootAssembly : JobAssembly
{
public int JobId { get; set; }
public virtual Job Job { get; set; }
}
public class Job
{
public int Id { get; set; }
public string Description { get; set; }
public virtual List<RootAssembly> Assemblies { get; set; }
}