Entity Framework Core - recursive parent/child linking - entity-framework-core

I have an "account" table that includes a string foreign-key ("parent_guid") to its "parent" account (if one exists). I would like to create an entity that knows its parent, as well as all of its children.
Here is my entity:
[Table(name:"accounts")]
public class Account
{
[Key]
public string Guid { get; set; }
public string Name { get; set; }
[Column("guid")]
public string accountGuid { get; set; }
[Column(name: "parent_guid")]
public string parentGuid { get; set; }
[ForeignKey("parentGuid")]
public Account Parent { get; set; }
[InverseProperty("Parent")]
public ICollection<Account> Children { get; set; }
}
Here's my dbContext:
public DbSet<Split> Splits { get; set; }
public DbSet<Account> Accounts { get; set; }
public ReportContext(DbContextOptions<ReportContext> options)
: base(options)
{ }
My query is through the 'splits' context as the source table, but I end up returning Accounts. Maybe there's a better way?
When I query for an Account by Guid, I get a result, but 'Parent' and 'Children' are always null, even though 'parentGuid' contains the correct value, and I have confirmed that there should be child records.
Any idea how to make this work, either through annotations or fluent API?

Yes, EF Core requires explicit inclusion of relational entities.
var accounts = await dbContext.Accounts.Include(account => account.Parent)
.Include(account => account.Children)
.ToListAsync();
##EDIT
As per the edits to the question, this is one way to Eager Load relational entities, but I cannot speak to the efficiency of this query without knowing the relations and indexes.
public IQueryable<Split>
FindAllByAccountGuidsPostedBefore(IEnumerable<string> accounts,
DateTime endDate) {
using (reportContext) {
return reportContext.Splits.Include(s => s.Account)
.ThenInclude(a => a.Parent)
.ThenInclude(a => a.Children)
.Where(s => accounts.Contains(s.Account.Guid)
&& s.Transaction.postDate < endDate);
}
}
One way to obtain that information is by looking at the console when this query is run to find the SQL statement(s) this produces, or by asking someone who is more experienced in Relational Databases :)

Related

how to load several related entities but not with all his fields, with include?

I have a method that loads many related entities using include clauses, but this to create a query is too big. I need to load many related entities, but I want to load only the fields that are important to me.
public RECEIPT_REMITS GetByIDWithIncludes(string UUID)
{
return Context.RECEIPT_REMITS.Where(h => h.UUID == UUID) .
Include(r => r.DEPOSITE)
.Include(r => r.PROVIDER)
.Include(r => r.RECEIPT_KINDS)
.Include(r => r.RECEIPT_REMITS_DETAIL.Select(d =>
d.RECEIPT_REMITS_SERIES)).FirstOrDefault();
}
Now the sentence loads the data correctly, but only slowly and also bringing fields that I do not need. How can I do this?
Entities represent data records. Complete data records. This makes them poor choices to be used for other purposes such as models for views. Instead you should adopt view models then map entities to view models either via .Select() or using Automapper with it's .ProjectTo<T>() method which integrates with EF's IQueryable implementation. Even if some of the view models end up being identical to the EF model, they serve separate purposes. An entity should always represent the row it is associated to, so you can't expect to tell EF to return a partially filled entity.
For instance if I have a table ReceiptRemit with 10 columns that I care about, and I also want to include related Deposits, but I only care about the ID and amount from the Deposit table:
Entities:
[Table("RECEIPT_REMIT")]
public class ReceiptRemit
{
[Key]
public string UUID { get; set; }
public string Field1 { get; set; }
public string Field2 { get; set; }
// etc. etc.
public virtual ICollection<Demerit> Demerits { get; set; } = new List<Demerit>();
}
[Table("DEMERITE")]
public class Demerit
{
[Key]
[Column("DEMERITE_ID")]
public int DemeritId { get; set; }
public decimal Amount { get; set; }
public string Field1 { get; set; }
public string Field2 { get; set; }
// etc. etc. to match the table, but stuff I don't care about...
}
View Models:
[Serializable]
public class ReceiptRemitViewModel
{
public string UUID { get; set; }
public string Field1 { get; set; }
public string Field2 { get ; set; }
// etc. etc.
public IEnumerable<DemeritSummaryViewModel> Demerits { get; set; } = new List<DemeritSummaryViewModel>();
}
[Serializable]
public class DemeritSummaryViewModel
{
public int DemeritId { get; set;}
public decimal Amount { get; set; }
}
Then to read: (.Select())
public ReceiptRemitViewModel GetByID(string UUID)
{
return Context.ReceiptRemits.Where(h => h.UUID == UUID)
.Select(x => new ReceiptRemitViewModel
{
UUID = x.UUID.
Field1 = x.Field1,
Field2 = x.Field2,
Demerits = x.Demerits.Select(d => new DemeritSummaryViewModel
{
DemeritId = d.DemeritId,
Amount = d.Amount
}).ToList(),
}.Single();
}
Which can be a bit of a pain with several related summary details to load, but this can be simplified by using Automapper. Automapper can figure out most common mapping details by convention or be configured for anything specific that doesn't work. Once set up, the above becomes:
return Context.ReceiptRemits.Where(h => h.UUID == UUID)
.ProjectTo<ReceiptRemitViewModel>()
.Single();
Alternatively for things like bulk operations you can define a different entity definition for the related data and register these alternative entities to a new DbContext definition. It has to be a separate DbContext declaration because a DbContext cannot have 2 entities mapped to the same table. This works well for situations where you might need to load relatively large #s of records to inspect and potentially update only a subset of related entities and fields.

EF 6 Code First storing an entity Reference to specific child in a one of the collections on the entity

I have a domain model that has a collection of entities configured in the normal 1 to many relationship, however I want to store a reference to a specific item in that collection using a FK in this model
The list as defined in the model
public ICollection<SLWOUpdate> Updates { get; set; }
The reference to the specific item in the list
public int? SLWOUpdateId { get; set; }
[ForeignKey("SLWOUpdateId")]
public virtual SLWOUpdate LastUpdate { get; set; }
Of course the code is responsible for updating the specific item as opposed to having EF do it.
Is this kind of relationship configurable in EF?
The reason I want to do this is for querying filtering purposes as part of complex query that must execute as one statement
Ended up adding a new domain model to represent the LastUpdate which simply holds a primary key to this entity and a FK to the LastUpdate
New Domain Model to represent the Last Update
public virtual SLCurrentWOUpdate LastUpdate { get; set; }
public class SLCurrentWOUpdate
{
[Key]
public int SLWorkOrder_Id { get; set; }
public SLWorkOrder SLWorkOrder { get; set; }
public int? SLWOUpdateId { get; set; }
[ForeignKey("SLWOUpdateId")]
public SLWOUpdate SLWOUpdate { get; set; }
}
I can query this as part of a larger more complex set of predicates... I just have to reach into the model one reference deeper:
db.SLWorkOrders
.Where(t => t.TAutoDeclined != null && t.TClosedPendingPayment != null)
.Where(t => t.LastUpdate.SLWOUpdate.UpdateStatusType.SystemName == "CHANGE_PRIORITY");
Feels kind of hackish.. but it works..

One to many relationship: why does ID field go in other entity?

In Entity Framework when I want to specify that an entity has many of another type of entity it seems to do things backwards to me.
For instance let's say I have a keyword entity that is used in several places throughout my app. All keywords are unique but on my other entities I want to have multiple keywords attached them so to me this would make sense:
class Page
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Keyword> Keywords { get; set; }
}
class Search
{
public int ID { get; set; }
public DateTime Date { get; set; }
public virtual ICollection<Keyword> Keywords { get; set; }
}
class Keyword
{
public int ID { get; set; }
public string Name { get; set; }
}
However when I do this the foreign key is added to the Keyword table whereas I want it to be on the actual entity so I can look at it in database and see small list of keywords instead of looking at keyword and seeing a ridiculous large number of page results.
So instead to get Entity Framework to put the Keyword_IDs on Page and Search entities I am doing this:
class Page
{
public int ID { get; set; }
public string Name { get; set; }
}
class Search
{
public int ID { get; set; }
public DateTime Date { get; set; }
}
class Keyword
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Page> Pages { get; set; }
public virtual ICollection<Search> Searches { get; set; }
}
This feels backwards as I am specifying the relationship on the entity that doesn't get the foreign ID field in the database table.
I feel like I am doing something wrong as I should be able to see the relationship by looking at my search & page class.
I am sorry for the basic question but for some reason I have read documentation and I am not fully understanding it.
In a one-to-many association it's always the many side that refers to the one side. How else would you implement it? If a Page would have a KeywordId as FK, it could only have one keyword, ever.
Also, even when a Keyword would belong to a myriad of pages, that doesn't mean you always have to access all of these pages through one keyword. You'd only do that if you'd do a search for pages in which specific keywords are used.
But now back to your model. You can't have one-to-many associations here. It would mean that any keyword can only belong to one Page or one Search. And if you invert the relationship, as you proposed, a Page or Search can only ever have one keyword (the one that Keyword_ID refers to).
In reality, you're dealing with many-to-many associations and the good news is, it leaves your Keyword intact.
Modelling it as many-to-many doesn't change the way your model looks (the first version), but the mapping is different:
modelBuilder.Entity<Page>().HasMany(p => p.Keywords)
.WithMany()
.Map(m =>
{
m.ToTable("PageKeyword");
m.MapLeftKey("PageID");
m.MapRightKey("KeywordID");
});
modelBuilder.Entity<Search>().HasMany(s => s.Keywords)
.WithMany()
.Map(m =>
{
m.ToTable("SearchKeyword");
m.MapLeftKey("SearchID");
m.MapRightKey("KeywordID");
});
This will generate two junction tables in your database, PageKeyword and SearchKeyword that record the many-to-many associations.

EF 6 optional FK , delete still produces error

So i have these two simple models
public class Blog
{
public int BlogId { get; set; }
private string _Name;
[Column("sNameColumn")]
public string Name { get { return _Name; } set { _Name = value; } }
public virtual List<Post> Posts { get; set; }
}
public class Post
{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public int? blog_id { get; set; }
[ForeignKey("blog_id")]
public virtual Blog Blog { get; set; }
}
I haven't done anything unusual at the dbContext definition. Then , i try to do something like this.
db.Blogs.Remove(db.Blogs.Find(2));
db.SaveChanges();
And i get a FK violation error.Notice that the FK blog_id is null-able, so i thought that EF whould handle the delete, and make the corresponding FK Null.
Can you tell me what i am missing?
The entities have to be loaded for EF to be able to handle setting their foreign key to null.
var b = db.Blogs.Find(2);
db.Entry(b).Collection(b => b.Posts).Load();
db.Blogs.Remove(b);
db.SaveChanges();
Keep in mind that Entity Framework can only update entities it has loaded.
Of course there are ways to update database records by raw SQL statements that you can also execute through EF, but that's not the core of EF as ORM.
So if you want to use EF only, you have no choice. You have to load collections in Blogs explicitly for them to get dissociated from their parent. For instance by Include:
var b = db.Blogs.Include(b => b.Posts).Include(b => b.Comments)
.Single(b => b.BlogId == 2);
db.Blogs.Remove(b);
db.SaveChanges();
Or by Load as in the other answer.
Another option could be to use Entity Framework Extented. One of its features is batch updates, which allows one-shot update statements of records in an IQueryable given a template record. This would look like this:
using EntityFramework.Extensions;
...
db.Posts.Where(p => p.BlogId == 2)
.Update(p => new Post { BlogId = default(int?) });
db.Blogs.Remove(b);
db.SaveChanges();
Only the properties that are set in the template record are modified. To make this transactional, you should wrap all statements in a TransactionScope.

Entity Framework TPH Inheritance Data Modeling Issues

I'm new to Entity Framework and C#/.Net and trying to create a TPH inheritance model, I'm not sure if I should be or not, so if not, please advise,
Here's the model:
public abstract class Vote
{
public int VoteID { get; set; }
public int UserID { get; set; }
public bool Value { get; set; }
public DateTime DateCreated { get; set; }
public User User { get; set; }
}
public class ProjectVote_ : Vote
{
public int ProjectID { get; set; }
public virtual Project Project { get; set; }
}
public class CommentVote_ : Vote //There are three more like this, votes for different hings
{
public int CommentID { get; set; }
public virtual Comment Comment { get; set; }
}
Now the Project model (comment and model is similar)
public class Project
{
public int ProjectID { get; set; }
public string Title { get; set; }
public virtual ICollection<Vote> Vote { get; set; }
}
What happens is that ICollection creates a database column Project_ProjectID as the foreign key in the Vote table (I think) instead of using the ProjectID I defined. How do I fix it or should I model it differently. If the fluent API is the way to fix it, I don't know how to do that.
In the end I want to be able to use one table to store 5 different types of votes.
When you have related entities you don't need to have a property to store the FK in your model. Entity framework knows that it needs to make a FK to the Project table in ProjectVote when it detects Project in your ProjectVote_ model. Same thing with User and UserId and Comment and CommentId. You don't need to have a property that stores the FK in your model.
You are getting the FK column with the name you don't like "Project_ProjectID" because Entity framework is detecting that it needs to create a FK for your navigation property "Project". It's using it's own naming convention to create the column hence "Project_ProjectID".
If you want to provide your own name for the column override OnModelCreating in your DBContext class and add this fluent mapping.
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Project>()
.HasMany(p => p.Vote)
.HasRequired(v => v.Project) //or .WithOptional(v => v.Project)
.Map(m => m.MapKey("ProjectId")); //or any other name you want.
}
And for the future this is a helpful reference for how to use the Fluent API. For example here is some documentation on how to custimize TPH with fluent.
Hope that helps!