Is it possible to set up this relationship via the Fluent API?

ef-code-first ef-fluent-api entity-framework-core

Question

I have two code first entities, Package and PackageEntry that I am having trouble setting up in EF Core.

I am trying to achieve the following with the code first entities and the Fluent API:

  • A Package can contain any number of PackageEntries
  • Each PackageEntry has a reference to a single Package entity (a different instance of a package, unrelated to the parent Package reference that contains the collection of PackageEntries)

The two entities:

    public class Package{   
      public Package()
      {
        _packageEntries = new List<PackageEntry>();
      }
      //trimmed other properties

      private readonly List<PackageEntry> _packageEntries;

      [NotMapped]
      public IReadOnlyCollection<PackageEntry> PackageEntries => _packageEntries.ToList().AsReadOnly();

      }

and

public class PackageEntry
{
    public int DisplayOrder { get; set; }
    public int PackageID { get; set; }
    public Package Package { get; set; }
    public int Quantity { get; set; }
    public Package ParentPackage { get; set; }
    public int ParentPackageID { get; set; }
}

What I currently have using the Fluent API, which is not working is:

modelBuilder.Entity<Package>().HasMany(x => x.PackageEntries).WithOne();
modelBuilder.Entity<PackageEntry>().HasOne(x => x.Package).WithOne().HasForeignKey(typeof(PackageEntry), "PackageID");

It isn't throwing errors, but what I am seeing is that when a PackageEntry is added to a package, it is not getting saved when calling SaveChanges on the context.

Am I doing something wrong with the Fluent API or something else?

EDIT I had missed adding the top level package to the context, once that was done the package entry that gets added to it is being saved. I would still appreciate comments on the Fluent API setup and any best practices.

From the PackageEntry entity, I need to know both the Parent Package and the contained Package which will be separate references to the same type. I can't seem to set this up with the Fluent API, when the Parent Package is loaded via EF it doesn't contain any PackageEntry objects, even if their ParentPackageID is set correctly.

1
0
6/17/2018 6:29:23 PM

Accepted Answer

Upon some offline advice from an EF expert, I have worked around this issue by removing the navigation property for PackageEntry.Package and simply manually handle the foreign key for that package entity.

Once I did that, now when the Parent Package entity is loaded, it properly loads the children PackageEntries.

So, the PackageEntry class now looks like this:

public class PackageEntry
{
    public int DisplayOrder { get; set; }
    public int PackageID { get; set; }
    //public Package Package { get; set; } //Handle manually
    public int Quantity { get; set; }
    public Package ParentPackage { get; set; }
    public int ParentPackageID { get; set; }
}

And the Fluent API code:

navigation = builder.Metadata.FindNavigation(nameof(Package.PackageEntries));
//EF access the PackageEntries collection property through its backing field
navigation.SetPropertyAccessMode(PropertyAccessMode.Field);

modelBuilder.Entity<Package>().HasMany(x => x.PackageEntries)
            .WithOne("ParentPackage")
            .HasForeignKey(nameof(PackageEntry.ParentPackageID))
            .IsRequired()
            .OnDelete(DeleteBehavior.Restrict);
1
6/26/2018 12:12:35 PM

Popular Answer

Your Package.PackageEntries collection is marked [NotMapped], and it does not have a setter. No matter what, EntityFramework is not going to pick that up.

I've never tried using an IReadonlyCollection<T> with EntityFramework, but I would imagine that EF won't like that either.

Your first try should be to remove the attribute and arrange the property like this:

public virtual IReadOnlyCollection<PackageEntry> PackageEntries {
    get {
        return _packageEntries.ToList().AsReadonly();
    }
    protected internal set {
        _packageEntries = value;
    }
}

Granted, that would require you to remove the readonly from the private member variable.

That being said, I'm not sure if EF has an internal list that it eventually assigns to the property, but I would imagine that it would just call the Add() method on the collection (which is why your properties must be ICollection<T> instead of IEnumerable<T>.

Therefore, if that is all still not working, you should make _packageEntries protected internal and use that as your EF collection. Then you can only publicly expose your PackageEntries as you are doing now.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow