Tree structure with reference to root in Entity Framework - asp.net

I'm trying to model a tree structure for orders in Entity Framework. Right now I've go the following:
public class ProjectModel
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
[Required]
public int CustomerId { get; set; }
public virtual List<ProjectNode> Nodes { get; set; }
}
public class ProjectNode
{
[Key]
public int Id { get; set; }
[Required]
public string Name { get; set; }
public string Path { get; set; }
public int? ParentId { get; set; }
[ForeignKey("ParentId")]
public virtual List<ProjectNode> Children { get; set; }
}
What I need to be able to do is get a reference to the root ProjectModel at any level of ProjectNode in order to authorize a given user actually having permission to view and change the project which contains the ProjectNode.
public class ProjectNode {
public int ProjectId { get; set; } //<-- this
...
public class ProjectModel {
[Key]
public int Id { get; set; } //<-- containing the value of this
}
My question is whether its possible to have a theoretical ProjectId property populated at every level of the tree structure, or if I need to set it manually.
I had something working that at first blush appeared to allow this functionality, but upon further investigation only populated the ProjectId for ProjectNodes contained in the ProjectModel's Nodes property.
It seems to me like it would be super inefficient to recurse backwards through the structure to get to the root.

Credit due to #TestWell for this answer -
Apparently, all I needed to do for EF to automatically populate the ProjectId property on the ProjectNode was to change the name of the Id property in ProjectModel to ProjectId.
Unfortunately, this doesn't appear to work if I add a CustomerId property to the ProjectNode that I would like automatically populated from the property of the same name on the node's root ProjectModel, which I realized is the more efficient solution to what I'm trying to do.

Related

Entity Framework Core multiple table relationship

I am trying to create relationship where parent has many children and also includes the oldest child. How to correctly configure that relationship and how to insert the data into the tables?
public class Parent
{
public int Id { get; set; }
public int Name { get; set; }
public int ChildId { get; set; }
public virtual Child Child { get; set; }
public virtual ICollection<Child> Childs { get; set; }
}
public class Child
{
public int Id { get; set; }
public int Age { get; set; }
public int ParentId { get; set; }
public virtual Parent Parent { get; set; }
}
I would recommend against trying to store it as a separate property. Instead, calculate it from existing data with a get-only property something like:
public Child OldestChild => Childs.OrderByDescending(c => c.Age).FirstOrDefault();
If you are concerned about the cost of repeated access, you can look into wrapping the access logic using the Lazy<> class or some other caching methods. If you do, be aware that changes to the Childs collection would then not be reflected in the cached OldestChild result.
FOLLOW-UP: The above is still incomplete as it doesn't resolve cases of children of the same age - close birthdates, twins, adoptees, etc. That also raises the question: Why are are you modeling age (which changes over time vs Birthdate, which is fixed.

.NET EF Inheritance without keys or constraints

It is possible to inherit from a type in dotnet EF without inherit the keys, indexes, etc?
I have these types:
public class Product : IEntity<long>
{
public long Id { get; set; }
public string Name { get; set; }
}
public class ProductVersion : Product
{
[Key]
public int ProductVersionId { get; set; }
public DateTime CreatedAt { get; set; }
}
I want ProductVersion to inherit all the properties from Product without creating any keys, constraints or navigation properties from the parent, just have the same properties and if are required or not, basically create a copy of the table columns.
The way you have it set up should remove the key constraint:
https://www.tektutorialshub.com/entity-framework-core/data-annotations-key-attribute-in-ef-core/
Adding [NotInherritedAttribute] should get rid of any of restraints you add:
https://learn.microsoft.com/en-us/dotnet/api/system.attributeusageattribute.inherited?view=net-6.0
[NotInheritedAttribute]
public class ProductVersion : Product{
[Key]
public int ProductVersionId { get; set; }
public DateTime CreatedAt { get; set; }
}

EF-Code First navigation property foreign key in complex type

I have complex type for Audit fields
My complex type:
[ComplexType]
public class AuditData {
[Column("CreatorUserId")]
public int? CreatorUserId { get; set; }
public DateTime? CreationTime { get; set; }
[Column("ModifierUserId")]
public int? ModifierUserId { get; set; }
public DateTime? ModificationTime { get; set; }
}
My base Entity (all other inherti this one) has AuditData property:
public abstract class Entity : IEntity, IAuditedEntity, INotifiedDbContextBeforeSave
{
// Summary:
// Unique identifier for this entity.
public int Id { get; set; }
public bool IsActive { get; set; }
public int Old_Id { get; set; }
public string Old_TableName { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
public AuditData AuditData { get; set; }
// can this 2 lines below work as navigation property with foreign key in complex type
public virtual User CreatorUser { get; set; }
public virtual User ModifierUser { get; set; }
//... other fields
}
I have 2 navigation properties CreatorUser and ModifierUser.
I know you cant have navigation property in ComplexType but can my navigation property on entity be mapped with foreign key in complexType
something like:
[ForeignKey("CreatorUserId")] // --> should point to AuditData.CreatorUserId
public virtual User CreatorUser { get; set; }
becouse CreatorUserId will be property in every entity but EF is not aware of it.
Mybe there is solution in fluent API ?
The official documentation says:
Complex types are non-scalar properties of entity types that enable scalar properties to be organized within entities. Like entities, complex types consist of scalar properties or other complex type properties. Because complex types do not have keys, complex type objects cannot be managed by the Entity Framework apart from the parent object.
It follows that that complex types can not participate in any relations among entities, so they can't contain foreign keys

Lazy loading not working in Entity Framework

I have two classes that are connected by using the virtual keyword:
Student:
public class Student
{
public int StudentId{get; set;}
public string LastName { get; set; }
public string FirstName { get; set; }
public DateTime EnrollmentDate { get; set; }
public virtual IEnumerable<Enrollment> Enrollments { get; set; }
}
Enrollment:
public class Enrollment
{
public int EnrollmentId { get; set; }
public int CourseId { get; set; }
public int StudentId { get; set; }
public decimal? Grade { get; set; }
public virtual Course course { get; set; }
public virtual Student student { get; set; }
}
Both tables are populated, and have corresponding records (for instance, there is a student with id 1 and enrollments for student with id 1).
I'm pulling up a student by it's id and sending it to a view
Student student = db.Students.Find(id);
return View(student);
In the view I can display the details for that student. The #Model does contain an Enrollment property (at least it comes up in intellisense and doesn't red-line), but it is Null.
There is also a course class:
{
public int CourseId { get; set; }
public String CourseName { get; set; }
public int TotalCredits { get; set; }
}
Since #Model.Enrollments is Null, I can't access #Model.Enrollment.CourseNamae.
Edit:
I just tried a hack workaround:
IEnumerable<Student> temp = db.Students.Include(s => s.Enrollments);
Student student = temp.FirstOrDefault(s => s.StudentId.Equals(id));
return View(student);
This is giving me the error on the second line:
System.InvalidOperationException: A specified Include path is not valid. The EntityType 'MyFirstProject2.Models.Student' does not declare a navigation property with the name 'Enrollments'.
Does that offer any clues?
A little bit late but here some explanation between Lazy Loading vs Eager Loading
And also the Rules for lazy loading:
context.Configuration.ProxyCreationEnabled should be true.
context.Configuration.LazyLoadingEnabled should be true.
Navigation property should be defined as public, virtual. Context will NOT do lazy loading if the property is not define as
virtual.
EDIT:
One last thing for relationships use:
public virtual ICollection<Enrollment> Enrollments { get; set; }
Other Links:
Lazy Loading
Eager Loading
Explicit Loading

Is it OK to declare a DBSet in the context for both a base table and a derived table?

I have a SalesOrder table which inherits from a SalesDocument table using Table Per Type Inheritance
The ( simplified) table classes are;
[Table("SalesDocumentHeaders")]
public abstract class SalesDocumentHeader
{
[ForeignKey("CreatedByUserId")]
public virtual User CreatedBy { get; set; }
[Required]
public int CreatedByUserId { get; set; }
[Required]
public virtual DateTime? DocumentDate { get; set; }
[Required]
public String ReferenceNumber { get; set; }
}
[Table("SalesOrders")]
public class SalesOrder : SalesDocumentHeader
{
[Required]
public String CustomerOrderNumber { get; set; }
public DateTime? DeliverBy { get; set; }
public virtual SortableBindingList<SalesOrderLine> Lines { get; set; }
}
The context contains
public DbSet<SalesOrder> SalesOrders { get; set; }
public DbSet<SalesDocumentHeader> SalesDocumentHeaders { get; set; }
It doesn't strictly need the SalesOrders DBSet, since SalesOrder inherits from SalesDocumentHeader however I find it convenient.
It seems to work OK, but I am worried that there are 2 ways of reaching the same record , am I doing something wrong?
Usually you only need to keep the DBSet for the base table. This helps when you have multiple derived tables (call them A and B) and you need to decide the actual type dynamically.
For example if you have another entity which references type A or B (like a user can have different types of contact information), you can reference the base table and EF will resolve the correct concrete type at runtime. Though of course this adds some extra casting code.

Resources