Entity Framework Core: private or protected navigation properties
Asked Answered
L

2

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; }  
}
Leander answered 31/5, 2017 at 12:44 Comment(4)
If it was private/protected, how would EF be able to know the property exists?Yonder
Somehow specify in modelbuilder. In some cases EF could have access to private fields. See csharp.christiannagel.com/2016/11/07/efcorefieldsLeander
Then, if you know how to do it, what's the question? Did you try it out? What happened? What benefit would it give you to have a private navigation property? Why even declare it at all?Yonder
Approach in this article describe regular fields, not navigation oneLeander
O
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.

Ophthalmia answered 31/5, 2017 at 13:5 Comment(7)
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.Leander
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://mcmap.net/q/906133/-entity-framework-core-how-to-test-navigation-propery-loading-when-use-in-memory-datastore/1988021Leander
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 productionOphthalmia
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.Leander
See my 2nd example above, you can use nameof(Model.childs). It works even with private members, but only within nameof(...).Ophthalmia
For my case, I am not using lazy loading proxies, I need to set add elementMetadata.SetIsEagerLoaded(true)Phanerogam
Is there a way to use only a field for a navigation and not a property?Janessajanet
F
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

Freehand answered 31/5, 2017 at 12:52 Comment(1)
Not if you apply domain-driven design (DDD). Then restrictions are on the aggregate and its entities.Lai

© 2022 - 2024 — McMap. All rights reserved.