How to make working my structure with EF Core using Fluent Api? - entity-framework

I'm trying to represent some kind of tree using ef core and postgre sql. I have two classes:
public class ProtocolNode
{
public Guid Id { get; set; }
public string Name { get; set; }
public ICollection<ProtocolCriteria> Criterias { get; set; }
public ProtocolNode()
{
Criterias = new List<ProtocolCriteria>();
}
}
public class ProtocolCriteria
{
public Guid Id { get; set; }
public Guid? ParentId { get; set; }
public ProtocolNode Parent { get; set; }
public Guid? ChildrenId { get; set; }
public ProtocolNode Children { get; set; }
}
After that, I'm trying to run migrations for creating a database, but getting the error:
Unable to determine the relationship represented by navigation property 'ProtocolCriteria.Parent' of type 'ProtocolNode'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
I assume that I need to make some rules using Fluent API, but after some attempts I'm stuck...
I would be thankful for any help.

In general, a top-down tree structure consists of equal nodes which have two major properties, parent & siblings
public class node {
public int Id {get; set;}
public int? ParentId {get; set;} // top node has no parent
public virtual Node Parent {get; set;}
public virtual ICollection<Node> Children {get; set;}
}
builder.Entity<Node>() // ModelBuilder
.HasOptional(c => c.Parent)
.WithMany()
.HasForeignKey(c => c.ParentId);
EF makes parsing the children much easier than maintaining Linked-Lists. This assumes you are NOT talking about Binary trees which are a special case.
Note: EF Core has not implemented the HasOptional() functionality yet.

Related

EF Core 3.1.7 Data annotations for multiple 1:1 relationships in table

I am having problems figuring out the data annotations to map more than one 1:1 relationships so that EF Core 3.11.7 understands it and can build a migration.
I have a Person table and a Notes table.
There is a 0:M Notes relationship in Person. A person record can have 0 or more notes.
In the notes table is a CreatedBy field which is a Person. It also has a LastEditedBy field which is also a person. EF keeps bombing on how to construct the relationship for Note.CreatedBy. If this were non EF, both fields would be ints with the PersonID of the proper person record. How do it, preferabbly with Data Annotations, explain this to EF Core?
When I try to create a migration it fails and says:
System.InvalidOperationException: Unable to determine the relationship represented by navigation property 'Note.CreatedBy' of type 'Person'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;
namespace VetReg.Domain.Model
{
public class Family
{
public int FamilyID { get; set; } = -1;
public string FamilyName { get; set; }
public List<Pet> Pets { get; set; } = new List<Pet>();
public List<PersonFamily> People { get; set; }
public int AddressID { get; set; } = -1;
public List<Note> Notes { get; set; }
}
public class Person
{
public int PersonID { get; set; }
public string LastName { get; set; }
public string FirstName { get; set; }
public DateTime? Birthdate { get; set; }
public string Password { get; set; }
public List<PersonFamily> Families { get; set; }
public List<Note> Notes { get; set; }
} // class People
public class Note
{
public int NoteID { get; set; }
public int CreatedByID { get; set; }
[ForeignKey("CreatedByID")]
public Person CreatedBy { get; set; }
public DateTime DateCreated { get; set; }
public int LastEditByID { get; set; }
[ForeignKey("LastEditByID")]
public Person LastEditBy { get; set; }
public DateTime? LastEditDate { get; set; }
public string NoteText { get; set; }
}
public class PersonFamily
{
public int PersonID { get; set; }
public int FamilyID { get; set; }
public Person Person { get; set; }
public Family Family { get; set; }
}
}
The question is (and this is what makes impossible to EF to automatically determine the relationships) what is the relation between Person.Notes and Note.CreatedBy / Note.LastEditBy - none probably? You've said there is 0:M relationship between Person and Note, but note that there are potentially 3 one-to-many relationships there - notes associated with person, notes created by person and notes edited by person, which potentially leads to 3 FKs to Person in Note.
Also note that none of the navigation properties is required, but when present they must be paired.
Assuming you want 3 relationships, i.e. there is no relation between Note.CreatedBy / Note.LastEditBy and Person.Notes, you need to tell EF that Note.CreatedBy and Note.LastEditBy do not have corresponding (a.k.a. inverse) navigation property in Person. This is not possible with data annotations. The only available data annotation for that purpose [InverseProperty(...)] does not accept empty/null string name, hence cannot be used for what is needed here.
Also there is another problem here which you will encounter after resolving the current, which also cannot be resolved with data annotations. Since you have multiple required (thus cascade delete by default) relationships from Person to Note, it creates the famous "cycles or multiple cascade paths" problem with SqlServer, and requires turning off at least one of the cascade delete.
With that being said, the model in question needs the following minimal fluent configuration:
modelBuilder.Entity<Note>()
.HasOne(e => e.CreatedBy)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Note>()
.HasOne(e => e.LastEditBy)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
The essential for the original issue are the HasOne / WithMany pairs. Once you do that, EF Core will automatically map the unmapped Person.Notes collection to a third optional relationship with no inverse navigation property and shadow FP property (and column) called "PersonId", i.e. the equivalent of
modelBuilder.Entity<Person>()
.HasMany(e => e.Notes)
.WithOne()
.HasForeignKey("PersonId");
Regarding the second issue with multiple cascade paths, instead of Restrict you can use any non cascading option or the newer ClientCascade. And it could be for just one of the relationships, as soon as it breaks the "cascade path" (apparently you can't break the cycle because it is demanded by the model).

Entity Framework Core - retrieve entire DbSet without using include()

Let's say I have the following 2 classes that will represent a relationship between 2 DB Tables (created using EF Core Migrations):
public class Author
{
public int Id { get; set; }
public string Name { get; set; }
public Book Book { get; set; }
}
public class Book
{
public int Id { get; set; }
public String name { get; set; }
public string Genre { get; set; }
}
These 2 classes are added in as a DbSet in my ApplicationDbContext class.
When I retrieve the Author record, I want the Book object to also be populated info. It seems I have to retrieve it in the following way:
return _context.Author.Include(x => x.Book).ToList();
If I had a dozen of different objects in the Author class does that mean I have to chain the .Include() method calls for each object? Is there a catch-all method that will tell me to populate all of the objects inside the Author class? Something like .IncludeAll() perhaps?
You can use lazy loading enabled on your EF core. This way you don't need to include it every time.
Basically you need to add virtual keyword to your book entity:
public class Author{
public int Id {get; set;}
public string Name {get; set; }
public virtual Book Book {get; set; }
}
public class Book {
public int Id {get; set;}
public String name {get; set;}
public string Genre {get; set; }
}
Then you need to enable lazy loading by installing the Microsoft.EntityFrameworkCore.Proxies package and enabling it with a call to UseLazyLoadingProxies. For example:
In you startup.cs class modify the dbcontext as below
services.AddDbContext<BloggingContext>(
b => b.UseLazyLoadingProxies()
.UseSqlServer(myConnectionString));
Please refer the following link to understand how lazy loading works
https://learn.microsoft.com/en-us/ef/core/querying/related-data

EF5, Inherited FK and cardinality

I have this class structure:
public class Activity
{
[Key]
public long ActivityId { get; set; }
public string ActivityName { get; set; }
public virtual HashSet<ActivityLogMessage> ActivityLogMessages { get; set; }
public virtual HashSet<FileImportLogMessage> FileImportLogMessages { get; set; }
public virtual HashSet<RowImportLogMessage> RowImportLogMessages { get; set; }
}
public abstract class LogMessage
{
[Required]
public string Message { get; set; }
public DateTimeOffset CreateDate { get; set; }
[Required]
public long ActivityId { get; set; }
public virtual Activity Activity { get; set; }
}
public class ActivityLogMessage : LogMessage
{
public long ActivityLogMessageId { get; set; }
}
public class FileImportLogMessage : ActivityLogMessage
{
public long? StageFileId { get; set; }
}
public class RowImportLogMessage : FileImportLogMessage
{
public long? StageFileRowId { get; set; }
}
Which gives me this, model
Each Message (Activity, File or Row) must have be associated with an Activity. Why does the 2nd and 3rd level not have the same cardinality as ActivityLogMessage ? My attempts at describing the foreign key relationship (fluent via modelbuilder) have also failed.
This is really an academic exercise for me to really understand how EF is mapping to relational, and this confuses me.
Regards,
Richard
EF infers a pair of navigation properties Activity.ActivityLogMessages and ActivityLogMessage.Activity with a foreign key property ActivityLogMessage.ActivityId which is not nullable, hence the relationships is defined as required.
The other two relationships are infered from the collections Activity.FileImportLogMessages and Activity.RowImportLogMessages. They neither have an inverse navigation property on the other side nor a foreign key property which will - by default - lead to optional relationships.
You possibly expect that LogMessage.Activity and LogMessage.ActivityId is used as inverse property for all three collections. But it does not work this way. EF cannot use the same navigation property in multiple relationships. Also your current model means that RowImportLogMessage for example has three relationships to Activity, not only one.
I believe you would be closer to what you want if you remove the collections:
public virtual HashSet<FileImportLogMessage> FileImportLogMessages { get; set; }
public virtual HashSet<RowImportLogMessage> RowImportLogMessages { get; set; }
You can still filter the remaining ActivityLogMessages by the derived types (for example in not mapped properties that have only a getter):
var fileImportLogMessages = ActivityLogMessages.OfType<FileImportLogMessage>();
// fileImportLogMessages will also contain entities of type RowImportLogMessage
var rowImportLogMessage = ActivityLogMessages.OfType<RowImportLogMessage>();

Entity framework two-way relationship not loading includes

This is making me feel like an idiot. Entity Framework is supposed to be fairly simple, yet I can't sort this out myself and clearly I've got a fundamental misunderstanding. I hope it doesn't turn out to be an idiot-question - sorry if it is.
Three code-first objects, related to one another.
public class Schedule
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public DateTime Start { get; set; }
public DateTime End { get; set; }
public virtual ICollection<Charge> Charges { get; set; }
}
public class Charge
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public decimal Rate { get; set; }
public Type Type { get; set; }
}
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
}
When I query this, I want all related types, so:
Schedule currentSchedule = _Context.Schedules
.Include("Charges.Type")
.Where(cs => cs.Start < dateWindow && cs.End > dateWindow)
.First();
In C#, this has been working fine.
The problem arises because we're not stopping at C#, but passing the data onto a javascript library called Breeze with smooths out data operations at the client end. Breeze has a bug/feature which demands that EF relationships between objects be specified at BOTH ENDS. So when I do my query above, I don't end up with any Types, because their relationship with Charge isn't directly specified.
So I change it to this:
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual Charge Charge { get; set; }
}
Because virtual is a navigation property, so that should enable Breeze should now to go both ways across the relationship without changing the data structure. But EF doesn't like this. It tells me:
Unable to determine the principal end of an association between the
types 'Core.Charge' and 'Core.Type'. The principal end of this
association must be explicitly configured using either the
relationship fluent API or data annotations
Fair enough. I can see how this could be confusing. Now, my understanding is that if you define a foreign key in a dependent class, it has to be that classes' primary key. So we change it to:
public class Type
{
[Key, ForeignKey("Charge"), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual Charge Charge { get; set; }
}
And that seems to work but ... it's stopped loading any Type information when you ask for a schedule. Messing around with the includes doesn't seem to do anything at all.
What's going on, and what have I done wrong?
You haven't only added a navigation property (Type.Charge) to an existing model/relationship. Instead you have changed the relationship completely from a one-to-many to a one-to-one relationship because by default if a relationship has only one navigation property EF assumes a one-to-many relationship. With your change you have configured a one-to-one relationship.
Those relationships have different foreign keys: The original one-to-many relationship has a separate foreign key in the Charge table (probably named Type_RowId or similar). Your new relationship has the foreign key at the other side in table Type and it is the primary key RowId. The Charges you are loading together with the Schedule probably don't have any related Type with the same primary key, hence no Type is loaded.
If you actually want to reproduce the old (one-to-many) relationship with just a navigation property at the other side you must add a collection to Type instead of a single reference:
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public virtual ICollection<Charge> Charges { get; set; }
}
Are you sure that you want to put ForeignKey on RowId, I think you may want to define some relationship like this
public class Type
{
[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid RowId { get; set; }
public string TypeName { get; set; }
public int ChargeId { get; set; }
[ForeignKey("ChargeId")]
public virtual Charge Charge { get; set; }
}

Table Per Hierarchy & Inherited Relationships

I'm using Entity Framework 5, targeting .Net 4.5. For the life of me I can't figure out what I'm doing wrong that's causing the following error while trying to work with Table Per Hierarchy and Navigation columns:
Invalid column name 'Game_Category'.
Invalid column name 'Game_Value'.
Invalid column name 'Type_Category'.
Invalid column name 'Type_Value'.
Here's the abstract base class (note the composite PK on Category and Value):
[Table("Dictionary")]
public abstract class Lookup
{
[Key, Column(Order = 0)]
[StringLength(50)]
public string Category { get; set; }
[StringLength(100)]
public string ExtendedValue { get; set; }
[Required]
public bool IsActive { get; set; }
[Required]
[StringLength(50)]
public string Key { get; set; }
[Key, Column(Order = 1)]
public int Value { get; set; }
}
Followed by two subclasses that add no additional columns...
public class Game : Lookup {}
public class SetType : Lookup {}
Here's the class with Navigation properties to Game and SetType...
public class CardSet
{
[Required]
[StringLength(10)]
public string Abbreviation { get; set; }
public virtual Game Game { get; set; }
[Required]
public int GameId { get; set; }
[Key]
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Name { get; set; }
[Required]
public DateTime ReleaseDate { get; set; }
public virtual Lookup Type { get; set; }
[Required]
public int TypeId { get; set; }
}
From my data context...
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Lookup>()
.Map<Game>(l => l.Requires("LookupType").HasValue("Game"))
.Map<SetType>(l => l.Requires("LookupType").HasValue("Set Type"));
base.OnModelCreating(modelBuilder);
}
The lookup table has a discriminator column named LookupType. I've read through several tutorials on table/inheritance. The other two - TPT and TPC using similarly built objects were a cinch. While I understand the errors above - that it's looking for FK columns by convention, I don't understand what I'm doing wrong or missing that's causing it to look for those columns. I've tried placing ForeignKey attributes over the GameId and TypeId properties, but then I get errors about dependent/principal relationship constraints and I'm not sure how to specify the category as an additional foreign key.
I have yet to find a tutorial on table/inheritance that goes over navigation properties as I'm using them. Any help would be greatly appreciated, this has been driving me nuts for over an hour.
Update:
I believe the problem lies in the use of Category as part of the key. The CardSet doesn't have two properties for the category of "Game" for that lookup or the category for "Set Type" for that lookup. I tried creating these properties but that didn't work. Is it possible to set those using the Fluent API? I've made about a dozen attempts so far without any luck.
I think that EF does not "like" the construct modelBuilder.Entity<Lookup>() to map the two sub classes. This should help:
modelBuilder.Entity<Game>()
.Map(l => l.Requires("LookupType").HasValue("Game"));
modelBuilder.Entity<SetType>()
.Map(l => l.Requires("LookupType").HasValue("Set Type"));