13

Is it somehow possible to define navigation properties in EFCore with private or protected access level to make this kind of code work:

class Model {
   public int Id { get; set; }
   virtual protected ICollection<ChildModel> childs { get; set; }  
}
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
silent_coder
  • 6,222
  • 14
  • 47
  • 91

2 Answers2

17

You have two options, using type/string inside the model builder.

modelBuilder.Entity<Model>(c =>
    c.HasMany(typeof(Model), "childs")
        .WithOne("parent")
        .HasForeignKey("elementID");
);

Not 100% sure it works with private properties, but it should.

Update: Refactoring-safe version

modelBuilder.Entity<Model>(c =>
    c.HasMany(typeof(Model), nameof(Model.childs)
        .WithOne(nameof(Child.parent))
        .HasForeignKey("id");
);

Or use a backing field.

var elementMetadata = Entity<Model>().Metadata.FindNavigation(nameof(Model.childs));
    elementMetadata.SetField("_childs");
    elementMetadata.SetPropertyAccessMode(PropertyAccessMode.Field);

Alternatively try that with a property

var elementMetadata = Entity<Model>().Metadata.FindNavigation(nameof(Model.childs));
    elementMetadata.SetPropertyAccessMode(PropertyAccessMode.Property);

Be aware, as of EF Core 1.1, there is a catch: The metadata modification must be done last, after all other .HasOne/.HasMany configuration, otherwise it will override the metadata. See Re-building relationships can cause annotations to be lost.

Community
  • 1
  • 1
Tseng
  • 61,549
  • 15
  • 193
  • 205
  • This is awesome. I wasn't able to test second variant, but seems like the first one works for me for protected navigation property, at least for inmemory database. – silent_coder May 31 '17 at 17:10
  • Hm... actually I'm already not sure it's working as I expect. The problem that I can't test this porperly, since EF autoload all entities already loaded to a given context. And in inmemory case it's looks like all data all the time loaded, so I a bit lost, how to verify this. I even create special question for that: https://stackoverflow.com/q/44296548/1988021 – silent_coder Jun 01 '17 at 00:29
  • It should work on the real provider too, as long as you a) eager load it with `.Include(nameof(Model.childs))` or `.Include("childs")` or `.Include("childs.grandchilds")` (the `.ThenInclude equivalent). You shouldn't use InMemory Provider anyways, except for unit tests, it's not meant to be used in production – Tseng Jun 01 '17 at 06:08
  • Thanks, looks like I was able to check that. The only thing which worry me - is it possible to avoid using strings somehow? Since this could be broken on refactoring. – silent_coder Jun 01 '17 at 11:21
  • See my 2nd example above, you can use `nameof(Model.childs)`. It works even with private members, but only within `nameof(...)`. – Tseng Jun 01 '17 at 11:48
  • For my case, I am not using lazy loading proxies, I need to set add `elementMetadata.SetIsEagerLoaded(true)` – Ryan Teh Jun 05 '20 at 14:46
  • Is there a way to use only a field for a navigation and not a property? – sprengo Feb 04 '21 at 23:42
0

I am not sure if that is possible, the whole model should be available and accessible at a low level with any restrictions on DTO's ViewModels etc

Mark Redman
  • 24,079
  • 20
  • 92
  • 147
  • 8
    Not if you apply domain-driven design (DDD). Then restrictions are on the aggregate and its entities. – Fred Dec 04 '19 at 12:19