Entity-Framework-Core: Mapping 1 to 1 Relationships using Composite Keys - entity-framework-core

I'm having difficult configuring a 1:1 mapping in EntityFrameworkCore using FluentAPI. The navigational reference is always NULL. The only obvious difference between my code and countless others I have examined is I am trying to map via composite keys.
I've played around using annotations instead of Fluent API but encounter the same issue described in my summary.
Class Definitions
[Table("SomeTable")]
public class Defect
{
[Column("Record")]
public int DefectId { get; set; }
[Column("insp_id")]
public int InspId { get; set; }
[Column("defectnum")]
public int Number { get; set; }
public virtual Simulation Simulation { get; set; }
}
[Table("SomeSimulationTable")]
public class Simulation
{
[Column("Record"), Key]
public int SimTableId { get; set; }
[Column("insp_id")]
public int InspId { get; set; }
[Column("DefectNumber")]
public int Number { get; set; }
[Column("SimulationName")]
public string Name{ get; set; }
[Column("SimulationAlgorithm")]
public string Algorithm{ get; set; }
public virtual Defect Defect { get; set; }
}
Fluent API (in OnModelCreating)
modelBuilder.Entity<Defect>()
.HasKey(h => new { h.InspId , h.Number });
modelBuilder.Entity<Defect>()
.HasOne<Simulation>(p => p.Simulation)
.WithOne(i => i.Defect)
.HasForeignKey<Simulation>(b => new { b.InspId , b.Number });
When the "Defect" class is populated through the dbContext all data is available; however, when I attempt to access the "Simulation" property of the "Defect" class I encounter the following error:
System.NullReferenceException: 'Object reference not set to an instance of an object.'
I have also verified there is valid data in our database where the "Defect" should have a "Simulation".
Any help? Throwing myself at the mercy of other coders....

Take a look at the Include method when loading from the database, it will be null unless you explicitly load:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.entityframeworkqueryableextensions.include?view=efcore-2.1
https://entityframeworkcore.com/querying-data-include-theninclude

Related

Entity Framework Core not loading related data

We are developing a new application using ASP.NET Core and EF Core. We're on the latest stable release (v1.1.2). We are unable to load related data via navigation properties.
I am aware that lazy loading is not supported in EF Core but every post on the subject I have looked at suggests that we should be able to explicitly load related data using .Include(). However, this is not working for us and the related entities are always null when we load them in code.
We have two entities - 'Exchange' and 'Trade'. 'Exchange' has a foreign key to 'Trade' and contains a Virtual Trade called Request and another called Offer, thus:-
[Table("Exchange")]
public partial class Exchange : BaseEntity
{
public string Pending { get; set; }
[Display(Name = "Exchange Date"), DataType(DataType.Date)]
public DateTime DateOfExchange { get; set; }
public decimal EstimatedHours { get; set; }
public decimal ActualHours { get; set; }
public string Description { get; set; }
public string FollowUp { get; set; }
public string Status { get; set; }
[ForeignKey("User")]
[Required]
public int Broker_Fk { get; set; }
public virtual User Broker { get; set; }
public int Request_Fk { get; set; }
public virtual Trade Request { get; set; }
public int Offer_Fk { get; set; }
public virtual Trade Offer { get; set; }
I have a View Model that instantiates an 'Exchange' which I know has a related 'Request':-
_vm.Exchanges = _context.Exchange.Include(i => i.Request).Where(t => t.Request.User_Fk == user.Id || t.Offer.User_Fk == user.Id).ToList();
This returns an Exchange, which I am passing to and rendering in the View Model:-
#foreach (var item in Model.Exchanges)
{
<span>#item.Request.Name</span> <br />
}
The problem is that #item.Request is null, even though I have explicitly included it when loading the Exchange. I know that there really is a related entity in existence because one of the other properties on Exchange is its foreign key, which is populated.
What am I missing? Every example I have seen posted suggests that what I've done should work.
Your model attributes are messed up:
[Table("Exchange")]
public partial class Exchange : BaseEntity
{
//...
[ForeignKey("Broker")]
[Required]
public int Broker_Fk { get; set; }
public virtual User Broker { get; set; }
[ForeignKey("Request")]
public int Request_Fk { get; set; }
public virtual Trade Request { get; set; }
//...
}

Updating a navigation property that is a relationship to the same type?

I am using code first and have several classes that have navigation properties between themselves.
Issue Class:
public class Issue
{
public Issue()
{
Complaints = new List<Complaint>();
SubIssues = new List<Issue>();
}
[Key,ForeignKey("Complaints")]
public int IssueID { get; set; }
public string Name { get; set; }
public bool IsSubCategory { get; set; }
public virtual Issue ParentIssue { get; set; }
public virtual ICollection<Issue> SubIssues { get; set; }
public virtual ICollection<Complaint> Complaints { get; set; }
}
The Complaint Class:
public class Complaint
{
public Complaint()
{
CreateDate = DateTime.Now;
}
public int ComplaintID { get; set; }
public DateTime CreateDate { get; set; }
[MaxLength(2000)]
public string Description { get; set; }
public bool IsClosed { get; set; }
[ForeignKey("IssueID")]
public virtual Issue Issue { get; set; }
public int IssueID { get; set; }
}
The Complaint class is working fine. Where I am running into difficulties is with the Issues class which references the same table for SubIssues and ParentIssue. The idea is that each Issue record with IsSubCategory == false can have 0 to many related Issue records as a collection of SubIssues and each Issue record with IsSubCategory == true will have a 1 to 1 relationship with an Issue record as ParentIssue.
Because of some DBA standards I also need to specify the naming of the Foreign key fields, i.e. ParentIssueID rather than the Issue_ParentIssueID (or whatever it auto gens)
I would prefer to do this with data annotations but could use the OnModelCreating process if need be.
How would I go about fixing the issue class so that the proper tables are created?
IssueID can't be both a primary and a foreign key to itself. You need a property (and field) ParentIssueId.
public int? ParentIssueID { get; set; }
The mapping, if using fluent mapping, should look like this:
modelBuilder.Entity<Issue>()
.HasMany(i => i.SubIssues)
.WithOptional(i => i.ParentIssue)
.HasForeignKey(i => i.ParentIssueID);
ParentIssueID is int? because it's an optional relationship.

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>();

Defining foreign key constraints with Entity Framework code-first

I have following entity class called Code. It stores categories of different kinds - the data for which I would have otherwise needed to create many small tables e.g. User Categories, Expense Categories, Address types, User Types, file formats etc.
public class Code
{
public int Id { get; set; }
public string CodeType { get; set; }
public string CodeDescription { get; set; }
public virtual ICollection<Expense> Expenses { get; set; }
public virtual ICollection<Address> Addresses { get; set; }
:
: // many more
}
The class Expense looks like this:
public class Expense
{
public int Id { get; set; }
public int CategoryId { get; set; }
public virtual Code Category { get; set; }
public int SourceId { get; set; }
public double Amount { get; set; }
public DateTime ExpenseDate { get; set; }
}
With the above class definitions, I have established 1:many relation between Code and Expense using the CategoryId mapping.
My problem is, I want to map the SourceId field in Expense to the Code object. Which means, Expense object would contain
public Code Source { get; set; }
If I use this, at runtime I get an error about cyclic dependencies.
Can someone please help?
You will need to disable cascading delete on at least one of the two relationships (or both). EF enables cascading delete by convention for both relationships because both are required since the foreign key properties are not nullable. But SQL Server doesn't accept multiple cascading delete paths onto the same table that are introduced by the two relationships. That's the reason for your exception.
You must override the convention with Fluent API:
public class Code
{
public int Id { get; set; }
//...
public virtual ICollection<Expense> Expenses { get; set; }
//...
}
public class Expense
{
public int Id { get; set; }
public int CategoryId { get; set; }
public virtual Code Category { get; set; }
public int SourceId { get; set; }
public virtual Code Source { get; set; }
//...
}
Mapping with Fluent API;
modelBuilder.Entity<Expense>()
.HasRequired(e => e.Category)
.WithMany(c => c.Expenses)
.HasForeignKey(e => e.CategoryId)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Expense>()
.HasRequired(e => e.Source)
.WithMany()
.HasForeignKey(e => e.SourceId)
.WillCascadeOnDelete(false);

Relationship Mapping in EF4 code-only CTP (when using inheritance?)

I'm producing a simple composite patterned entity model using EF4 w/ the code-first CTP feature:
public abstract partial class CacheEntity
{
[Key]public string Hash { get; set; }
public string Creator { get; set; }
public int EntityType { get; set; }
public string Name { get; set; }
public string Predecessor { get; set; }
public DateTime DateTimeCreated { get; set; }
public virtual ICollection<CacheReference> References { get; set; }
}
public partial class CacheBlob : CacheEntity
{
public byte[] Content { get; set; }
}
public partial class CacheCollection : CacheEntity
{
public virtual ICollection<CacheEntity> Children { get; set; }
}
public class CacheReference
{
public string Hash { get; set; }
[Key]public string Reference { get; set; }
public virtual CacheEntity Entity { get; set; }
}
public class CacheEntities : DbContext
{
public DbSet<CacheEntity> Entities { get; set; }
public DbSet<CacheReference> References { get; set; }
}
Before I split out the primitive/collection derived classes it all worked nicely, but now I get this:
Unable to determine the principal end of the 'Cache.DataAccess.CacheEntity_References'
relationship. Multiple added entities may have the same primary key.
I figured that it may have been getting confused, so I thought I'd spell it out explicitly using the fluent interface, rather than the DataAnnotation attributes. Here's what I think defines the relationship properly:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<CacheEntity>().HasKey(ce => ce.Hash);
modelBuilder.Entity<CacheEntity>().HasOptional(ce => ce.References).WithMany();
modelBuilder.Entity<CacheReference>().HasKey(ce => ce.Reference);
modelBuilder.Entity<CacheReference>().HasRequired(cr => cr.Entity).WithOptional();
}
But I must be wrong, because now I get this:
Entities in 'CacheEntities.CacheReferenceSet' participate in the
'CacheReference_Entity' relationship. 0 related 'Entity' were found. 1 'Entity' is expected.
Various other ways of using the fluent API yield different errors, but nothing succeeds, so I am beginning to wonder whether these need to be done differently when I am using inheritance.
Any clues, links, ideas, guidance would be very welcome.
using the MapHierarchy works for me:
protected override void OnModelCreating(ModelBuilder builder){
builder.Entity<CacheBlob>().HasKey(b=> b.Hash).MapHierarchy();
}
As an example.
Further reference : http://blogs.msdn.com/b/efdesign/archive/2009/10/12/code-only-further-enhancements.aspx