One-to-One Relationship Not Loading All Entities - c#

I have a (contrived and over-simplified, due to contractual constraints) data model that should look like this:
public class ProvisionalData
{
public int ProvisionalDataID { get; set; }
public string Data { get; set; }
public Lot Lot { get; set; }
}
public class Destination
{
public int DestinationID { get; set; }
public string Name { get; set; }
}
public class LotDestination
{
public int LotDestinationID { get; set; }
public int DestinationID { get; set; }
public DateTime Month { get; set; }
public Destination Destination { get; set; }
}
public class Lot
{
public int LotID { get; set; }
public int ProvisionalDataID { get; set; }
public int LotDestinationID { get; set; }
public ProvisionalData ProvisionalData { get; set; }
public LotDestination LotDestination { get; set; }
}
The relationship from Lot to ProvisionalData is a required one-to-one on both sides. Please note, this is not the whole model, nor is the domain the concern. The concern is with configuring the one-to-one relationship.
My relevant to the one-to-one mapping fluent configurations:
public class LotConfig : EntityTypeConfiguration<Lot>
{
public LotConfig()
{
ToTable("Lot");
HasKey(x => x.LotID);
HasRequired(x => x.ProvisionalData)
.WithRequiredDependent(x => x.Lot)
.WillCascadeOnDelete(true);
}
}
public class ProvisionalDataConfig : EntityTypeConfiguration<ProvisionalData>
{
public ProvisionalDataConfig()
{
ToTable("ProvisionalData");
HasKey(x => x.ProvisionalDataID);
}
}
The other relationships shown are in fact set up - I have verified that they are being configured in my context, and all IDbSets exist and function correctly. In fact, everything "works" with this setup, except that some ProvisionalData entities on navigation properties are not populated by the following query:
var lotDestination = db.lotDestinations
.Where(x => x.DestinationId == destinationId && x.Month == month)
.Include(x => x.Lots)
.Include("Lots.ProvisionalData")
.Include(x => x.Destination)
.SingleOrDefault();
In my real dataset, this query will return a destination with 30 Lots. 16 of those lots have their ProvisionalData navigation property loaded. 14 do not. This problem persists when I manually loop through each Lot and db.Entry(lot).Reference(ProvisionalData).Load(). When I examine those entries, all 30 return true for .IsLoaded. The query and .Includes appears to be doing what they are supposed to, but some of the entities aren't coming back for reasons I don't understand. I'm hoping it's something simple that I can't see because I've been staring at it for too long.
However, when I change the relationship (ignoring existing database constraints) to a one-to-many with ProvisionalData entities looking like this:
public class ProvisionalData
{
public int ProvisionalDataID { get; set; }
public string Data { get; set; }
public IList<Lot> Lots { get; set; }
}
and a new Lot configuration like this:
public class LotConfig : EntityTypeConfiguration<Lot>
{
public LotConfig()
{
ToTable("Lot");
HasKey(x => x.LotID);
HasRequired(x => x.ProvisionalData)
.WithMany(x => x.Lots)
.HasForeignKey(x => x.ProvisionalDataID);
}
}
everything works flawlessly. The only drawback here is that this doesn't reflect the true constraints in the database, so you could technically try to add multiple Lots to the same piece of ProvisionalData, which would break when trying to save. I can build the logic in to prevent this myself, but why can't I express it here in Entity Framework? Is my configuration incorrect?
Also interestingly, when I switch the above-mentioned query around to this dumb version to test things (with the one-to-one mapping still in place in EF):
var quota = db.Lots
.Where(l => l.LotDestination.DestinationID == destinationId && l.LotDestination.Month == m)
.Include(x => x.ProvisionalData)
.Include(x => x.LotDestination)
.Include(x => x.LotDestination.Destination)
.Select(x => x.LotDestination)
.FirstOrDefault();
all of the provisional data comes back, but some of the Destinations do not. This hints to me that it has something to do with including navigation properties multiple levels deep across a one-to-one. Has anyone else experienced this behavior before?

EF does not officially support 1:1 associations other than in shared primary key 1:1 association situations.
What you are doing is create 1:many's and trying to tell EF that it's really a 1:1. The problem is, the db schema is really a 1:many schema and EF will have problems here.
If your requirement is 1:1, then you need to use a shared primary key (both entities have the same primary key), and one also treats it as a foreign key).

I am facing the problems before, Mine solution was using 1:many because if POCOA include POCOB
.Include(x => x.POCOA.POCOB)
really return the POCOB as a list, since we know it 100% return one record, then during getting the data, we can say a.SingleOrDefault();

Related

Proper action for EF Core onDelete in followers functionality

I'm working on a practice app with .NET 6 and EF Core and I'm trying to implement a followers system. However I'm having issues with setting up the onDelete for the join-table:
This is my Profile class:
public class Profile {
public int Id {get; set;}
public string Email { get; set; }
public ICollection<ProfileFollowing> Followers { get; set; }
public ICollection<ProfileFollowing> Followings { get; set; }
}
And this Is my Join Table, which keeps records of the followings:
public class ProfileFollowing {
public int ObserverId { get; set; }
public Profile Observer { get; set; }
public int TargetId { get; set; }
public Profile Target { get; set; }
}
I'm setting this up in my model builder the following way:
modelBuilder.Entity<ProfileFollowing>(profileFollowing =>
{
profileFollowing
.HasOne(o => o.Observer)
.WithMany(p => p.Followings)
.HasForeignKey(o => o.ObserverId)
.OnDelete(DeleteBehavior.Cascade);
profileFollowing
.HasOne(t => t.Target)
.WithMany(p => p.Followers)
.HasForeignKey(t => t.TargetId)
.OnDelete(DeleteBehavior.Cascade);
profileFollowing.HasIndex(pf => new {pf.ObserverId, pf.TargetId}).IsUnique();
});
However, apparently SQL Server does not like when both on the onDelete are marked as cascades as this May Cause Cycles Or Multiple Cascade Paths. The idea of multiple cascades is that when I delete a Profile it may attempt to delete the same records in my ProfileFollowings table twice, right?
Well is there any workaround for this? I see the very same implementation (i.e. this one or a similar one), however nobody mentions the issues with deleting the records. I want to be sure that if I delete a Profile, the ProfileFollowings will update correctly and all of the other Profiles will have their followers/followings adjusted.
PS: I know one workaround would be to set one of the FK (TargetId/ObserverId) to null and set onDelete action to be SetNull, but this way my unique index on ObserverId & TargetId becomes useless.

How do I tell EF Core about two properties of the same type?

I have a set of models representing legal cases. One of the actions a user can do on a case is generate a document. This action is saved as a History entity, with an associated HistoryFile entity that contains the data about the file. Other actions may result in a History entity, with zero or multiple associated HistoryFile entities.
Cut-down versions of these two classes looks like this...
public class History {
public int Id { get; set; }
public ObservableCollection<HistoryFile> HistoryFiles { get; set; }
}
public class HistoryFile {
public int Id { get; set; }
public int HistoryId { get; set; }
public History History { get; set; }
}
The next requirement is that a user can pick up on a document that was previously generated and continue working on it. The bit where I'm getting stuck is that the HistoryFile entity needs a reference back to the History entity that held the previous version. This means that I need to add two lines of code to the HistoryFile entity...
public class HistoryFile {
public int Id { get; set; }
public int HistoryId { get; set; }
public History History { get; set; }
public int? PreviousHistoryId { get; set; }
public virtual History PreviousHistory { get; set; }
}
This means that there are two links from a HistoryFile to a History, one required one which is the parent History entity (via the History property) and an optional one via the PreviousHistory property.
I can't work out how to set this up for EF Core. As the code stands now, when I try to add a migration, I get the following error...
Cannot create a relationship between 'History.HistoryFiles' and 'HistoryFile.PreviousHistory' because a relationship already exists between 'History.HistoryFiles' and 'HistoryFile.History'. Navigation properties can only participate in a single relationship. If you want to override an existing relationship call 'Ignore' on the navigation 'HistoryFile.PreviousHistory' first in 'OnModelCreating'.
I tried adding the following to my DbContext...
builder.Entity<HistoryFile>(entity => {
entity.HasOne(hf => hf.History)
.WithMany(h => h.HistoryFiles)
.HasForeignKey(hf => hf.HistoryId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(hf => hf.PreviousHistory)
.WithMany(h => h.HistoryFiles)
.HasForeignKey(hf => hf.PreviousHistoryId)
.OnDelete(DeleteBehavior.Restrict);
});
...but it didn't make any difference.
Anyone able to tell me how I configure this so that EF Core knows that there are two distinct links between the two entities?
I'm using EF Core 5.0.7 in a .NET5 project in case it makes a difference.
Thanks
Got it.
I needed to add the following two lines to the History class...
public virtual ICollection<HistoryFile> HistoryFilesParentHistory { get; set; }
public virtual ICollection<HistoryFile> HistoryFilesPreviousHistory { get; set; }
...and then change the code I added to the DbContext to look like this...
builder.Entity<HistoryFile>(entity => {
entity.HasOne(hf => hf.History)
.WithMany(h => h.HistoryFilesParentHistory)
.HasForeignKey(hf => hf.HistoryId)
.OnDelete(DeleteBehavior.Restrict);
entity.HasOne(hf => hf.PreviousHistory)
.WithMany(h => h.HistoryFilesPreviousHistory)
.HasForeignKey(hf => hf.PreviousHistoryId)
.OnDelete(DeleteBehavior.Restrict);
});
This worked fine.

API endpoint returns no data yet no errors

I have this simple controller in a NetCore 2 Entity Framework API app that is supposed to get a list of nationalparks based on the stateID:
[HttpGet("ListNationalParksByState/{stateId}")]
public async Task<ActionResult<IEnumerable<NationalParkList>>> GetNationalParksForState(Guid stateID)
{
var nationalparkList = await _context.NationalParkList.Where(n => n.StateId == stateID).ToListAsync();
return nationalparkList;
}
But my returned JSON only shows this:
[{"state":{"nationalparkList":[
However, when I set a breakpoint in that controller, it shows it found 3 nationalparks (I'm not sure was Castle.Proxies is):
[0] {Castle.Proxies.NationalParkListProxy}
[1] {Castle.Proxies.NationalParkListProxy}
[2] {Castle.Proxies.NationalParkListProxy}
Expanding those shows the 3 nationalparks and all their properties.
Here is my NationalParkList model:
public partial class NationalParkList
{
public NationalParkList()
{
NationalParkLinks = new HashSet<NationalParkLinks>();
}
public string NationalParkId { get; set; }
public Guid StateId { get; set; }
public string NationalParkTitle { get; set; }
public string NationalParkText { get; set; }
public virtual StateList State { get; set; }
public virtual ICollection<NationalParkLinks> NationalParkLinks { get; set; }
}
Here is how it's defined in my dbcontext:
modelBuilder.Entity<NationalParkList>(entity =>
{
entity.HasKey(e => e.NationalParkId)
.HasName("PK_NationalParkList");
entity.ToTable("nationalparkList");
entity.Property(e => e.NationalParkId)
.HasColumnName("nationalparkID")
.HasMaxLength(50)
.ValueGeneratedNever();
entity.Property(e => e.StateId).HasColumnName("stateID");
entity.Property(e => e.NationalParkText).HasColumnName("nationalparkText");
entity.Property(e => e.NationalParkTitle)
.HasColumnName("nationalparkTitle")
.HasMaxLength(3000);
entity.HasOne(d => d.State)
.WithMany(p => p.NationalParkList)
.HasForeignKey(d => d.StateId)
.HasConstraintName("FK_nationalparkList_stateList");
});
I'm not getting any errors, I'm just not getting any data.
Does anyone see why I'd get no data when I hit this controller?
Thanks!
As discussed in the comments, most likely the existence of cycles in your object graph is causing the serialization to malfunction. Im surprised that you arent getting exceptions during run time if this is the case.
AFAIK you have two options:
Decorate props in your class that you dont want to be serialized with [JsonIgnore] or similar, in order to avoid cycles in your object graphs.
The biggest issue with this approach that I see is its inflexibility: consider 2 endpoints A, B and an entity class Foo with multiple properties, including both x,y; A needs all props of Foo except x and B all props except y. How would you handle this having only one possible serialization configuration for class Foo?
Furthermore, from a purist point of view, adding such decorators increases the responsabilities/knowledge of an entity class with stuff unrelated to the business logic.
Map your entities into lower-level objects AKA DTOs
Another approach is to map your entities into instances of (mostly) behaviorless classes that can be considered data transfer objects.
In most web/controller layers you will see data objects going in and out. In your case for example, you could refactor into the following:
public class NationalParkListData
{
public string Id { get; set; }
public Guid StateId { get; set; }
public string Title { get; set; }
public string Text { get; set; }
// Depending on how rest-compliant your approach is, you
// might include non-collection nested objects or not
public StateListData State { get; set; }
public int NationalParkLinksCount { get; set; }
}
[HttpGet("/states/{stateId:guid}/national-parks")]
public async Task<IActionResult> GetNationalParksForState(Guid stateId, CancellationToken cancellationToken)
{
var stateNationalParks = await _context.NationalParkList
.Where(n => n.StateId == stateId)
.ToListAsync(cancellationToken);
IEnumerable<NationalParkListData> result = // your mapper logic
return this.Ok(result);
}
In this case, you can easily notice that the issue mentioned in the previous approach does not exist as its handled by the mapping layer. For implementing a mapping layer the most common approach is to use libraries like AutoMapper.

Many entities to one junction table NHibernate modelling

I would like to be able to add a collection of Notes to any of my main entities in my NHibernate application. I can see how you could do this with a seperate junction table per entity. However, I would like to be able to avoid this and only have one junction table - if this is possible.
Below is the code so far, however this will result in all Notes being loaded for every Entity and I only want to load the notes for that particular entity. What are the alternative approaches I need to take?
public class Entity
{
public virtual int Id { get; set; }
}
public class EntityType1 : Entity
{
public EntityType1()
{
Notes = new List<Note>();
}
public virtual string EntityTypeName { get; set; }
public virtual IList<Note> Notes {get;set;}
}
public class EntityType2 : Entity
{
public EntityType2()
{
Notes = new List<Note>();
}
public virtual string EntityType2Name { get; set; }
public virtual IList<Note> Notes { get; set; }
}
public class Note
{
public virtual int Id { get; set; }
public virtual IList<Entity> Entities { get; set; }
public virtual string NoteText { get; set; }
}
}
namespace FluentNHib.Mappings
{
public class EntityMap : ClassMap<Entity>
{
public EntityMap()
{
Id(m => m.Id);
}
}
public class EntityType1Map : ClassMap<EntityType1>
{
public EntityType1Map()
{
Id(m => m.Id);
Map(m => m.EntityTypeName1);
HasManyToMany(m => m.Notes).Table("EntityToNotes")
.ParentKeyColumn("EntityId")
.ChildKeyColumn("NoteId")
.LazyLoad()
.Cascade.SaveUpdate();
}
}
public class EntityType2Map : ClassMap<EntityType2>
{
public EntityType2Map()
{
Id(m => m.Id);
Map(m => m.EntityType2ame);
HasManyToMany(m => m.Notes).Table("EntityToNotes")
.ParentKeyColumn("EntityId")
.ChildKeyColumn("NoteId")
.LazyLoad()
.Cascade.SaveUpdate();
}
}
public class NoteMap : ClassMap<Note>
{
public NoteMap()
{
Id(m => m.Id);
Map(m => m.NoteText);
}
}
I am not sure what the real issue is:
...however this will result in all Notes being loaded for every Entity and I only want to load the notes for that particular entity...
Is the issue in lazy loading? or in fact that Entity1 and Entity2 can have same ID, therefore the references are mixed? (I expect that and this should be part of the answer below)
Anyhow, I would say that we can achieve what you need: map the Note with just one table EntityToNotes. And that is good.
But, in general, I would descourage you from using the many-to-many. It is just my own feeling, experience. Below are some links with more explanation:
Am I doing many to many incorrectly when using fluent nhibernate?
NHibernate how do you map a crossreference table to a bag?
Nhibernate: How to represent Many-To-Many relationships with One-to-Many relationships?
Draft of the SOLUTION:
So, firstly we have to extend the table "EntityToNotes" with two columns
EntityToNoteId column - we need a primary key for new pairing object
Discriminator column
The Discriminator column will be used for (almost like a standard inheritance)
inserting Discriminator value during creation
filtering te IList<Notes> per Entity
These could be the pairing Entity (with an abstract base gathering the common stuff)
public abstract class EntityToNote<TEntity>
{
public abstract string Discriminator { get; set; }
public virtual TEntity Entity {get;set;}
public virtual Note Note {get;set;}
}
// the pairing objects
public class EntityType1ToNote : EntityToNote<EntityType1>
{
string _discriminator = "EntityType1"; // here we set the discriminator
public virtual string Discriminator
{
get { return _discriminator; }
set { _discriminator = value; }
}
...
// Similar for other pairing objects
The Entities will now be referencing lists of pairing objects
public class EntityType1 : Entity
{
public virtual IList<EntityType1ToNote> Notes {get;set;}
...
public class EntityType2 : Entity
{
public virtual IList<EntityType2ToNote> Notes { get; set; }
...
Here is snippet of the mapping (all other Entities will have usual mapping, including ClassMaps for EntityType1ToNote, EntityType2ToNote...)
public class EntityType1Map : ClassMap<EntityType1>
{
public EntityType1Map()
{
Id(m => m.Id);
Map(m => m.EntityTypeName1);
HasMany(m => m.Notes)
// this "table" setting is redundant, it will come from EntityType1ToNote
//.Table("EntityToNotes")
.KeyColumn("EntityId")
// here is the trick, that only related rows will be selected
.Where("Discriminator = 'EntityType1'")
.Cascade.AllDeleteOrphan();
}
}
As I tried to explain in the links provided, we gained this way a lot. Mostly the ability to use more columns on the pairing table - e.g. Discriminator (later we can have more columns like SortBy...) and we are able to use powerful searching with subqueries - see Query on HasMany reference
Also, in fact, the pairing could be mapped via the real inheritance... But the main point here is: Instead of many-to-many we introduced the pairing object and gained a lot

Difficulty Concerning EF Code First Fluent API, TPH, and Foreign Keys

I have two tables in my database. One is called Users, and the other is called Widgets. The Widgets table represents 3 entities in my code model. One of the entities, Widget, is a parent class for the other two entities, WidgetTypeA and WidgetTypeB. Both WidgetTypeA and WidgetTypeB have navigation properties to the User entity, which is persisted to the Users table in the database. I'm having trouble getting Code First to use the same foreign key for both the WidgetTypeA and WidgetTypeB entities (UserId). Does anyone know how to do this? It seems like it should be a common problem with Table Per Hierarchy mapping.
My entity classes are as follows:
public class Widget
{
public int Id { get; set; }
public string Name { get; set; }
}
class WidgetMap : EntityTypeConfiguration<Widget>
{
public WidgetMap()
{
ToTable("Widgets");
HasKey(w => w.Id);
Property(w => w.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(w => w.Name)
.IsRequired()
.HasMaxLength(75)
.IsUnicode(true);
}
}
public class WidgetTypeA : Widget
{
public int UserId { get; set; }
public virtual User User { get; set; }
public string Color { get; set; }
public int DepthLevel { get; set; }
}
class WidgetTypeAMap : EntityTypeConfiguration<WidgetTypeA>
{
public WidgetTypeAMap()
{
Map(w => w.Requires("WidgetTypeId").HasValue(1));
HasRequired(w => w.User)
.WithMany(u => u.WidgetTypeAs)
.HasForeignKey(w => w.UserId)
.WillCascadeOnDelete(false);
Property(w => w.Color)
.IsOptional()
.IsUnicode(true)
.HasMaxLength(75);
Property(w => w.DepthLevel)
.IsOptional();
}
}
public class WidgetTypeB : Widget
{
public int UserId { get; set; }
public virtual User User { get; set; }
}
class WidgetTypeBMap : EntityTypeConfiguration<WidgetTypeB>
{
public WidgetTypeBMap()
{
Map(w => w.Requires("WidgetTypeId").HasValue(2));
HasRequired(w => w.User)
.WithMany(u => u.WidgetTypeBs)
.HasForeignKey(w => w.UserId)
.WillCascadeOnDelete(false);
}
}
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public int Age { get; set; }
public virtual ICollection<WidgetTypeA> WidgetTypeAs { get; set; }
public virtual ICollection<WidgetTypeB> WidgetTypeBs { get; set; }
}
class UserMap : EntityTypeConfiguration<User>
{
public UserMap()
{
ToTable("Users");
HasKey(u => u.Id);
Property(u => u.Username)
.IsRequired()
.HasMaxLength(75)
.IsUnicode(true);
Property(u => u.Age)
.IsRequired();
}
}
At any rate, I keep getting the error
Invalid column name 'UserId1'
when I try to perform the following operations:
using (var entities = new MyEntities())
{
User u = new User
{
Username = "Frank",
Age = 14
};
entities.Users.Add(u);
entities.SaveChanges();
WidgetTypeA wa1 = new WidgetTypeA
{
Name = "0SDF81",
UserId = u.Id,
DepthLevel = 6
};
entities.WidgetTypeAs.Add(wa1);
entities.SaveChanges();
}
Not sure if this can be fixed or not. I can always specify a second UserId foreign key for the Widgets table, but that seems pointless. Perhaps there's a way to do this using Fluent API?
You cannot map properties defined in different derived entities to the same column. That is limitation in EF. If your WidgetTypeA has UserId property and your WidgetTypeB has UserId property they must be different columns in the database. It should work if you move both UserId and User properties from derived types to the parent Widget type.
I know its a long way late, but hopefully may help other readers.
Although Ladislav was correct that using a mapped Foreign Key is not supported in EF6, I did find a useful workaround.
It is possible to define a computed column specification whose expression simply refers to the original column. Userid in the description above. This can be used as the discriminator for the TPH mapping. With this approach, the column need not be persisted, but can be used for TPH, with the original column being available for use as a foreign key.

Categories