Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot add navigation property to owned types if it holds the foreign key #29234

Closed
Danielku15 opened this issue Sep 29, 2022 · 1 comment
Closed

Comments

@Danielku15
Copy link

File a bug

While it is currently possible to put foreign keys + navigation properties from owned types to other entities, it is not possible to have a navigation property from the target entity back to the owned type or parent entity. In the example below: It does not seem to be possible to add a collection to TeamMember which allows loading the UserStory items it is used.

We are in the need of bi-directional properties, but want to use owned types to reuse a commonly used set of navigation properties. e.g. in our real world use case we have multiple WorkItemAssignees in the UserStory, WorkItemAssignees also in other entities like Bug and also the WorkItemAssignees is pointing to multiple TeamMembers like Owner, Tester, Developer,...

If we flatten the owned type, bidirectional properties are possible.

Considering that the owned types are part of the parent entity, we think Attempt 3 should be a possible option to have from the TeamMember a navigation property to the UserStory because WorkItemAssignments cannot exist on their own. But also Attempt 5 would work fine.

Include your code

using Microsoft.EntityFrameworkCore;

var optionsBuilder = new DbContextOptionsBuilder<ScrumDbContext>()
    .UseInMemoryDatabase("Test");

await using var dbContext = new ScrumDbContext(optionsBuilder.Options);

Console.WriteLine("Creating user");
var daniel = dbContext.Set<TeamMember>().Add(new TeamMember
{
    Name = "Daniel"
}).Entity;

Console.WriteLine("Creating story");
dbContext.Set<UserStory>().Add(new UserStory
{
    Title = "US01",
    Assignees =
    {
        Owner = daniel
    }
});

Console.WriteLine("saving");
await dbContext.SaveChangesAsync();
Console.WriteLine("saved");

var allStoriesWithUsers = await
    dbContext.Set<UserStory>()
        .Include(u => u.Assignees.Owner)
        .ToArrayAsync();
foreach (var story in allStoriesWithUsers)
{
    Console.WriteLine($"Story: {story.Id} {story.Title}");
    Console.WriteLine($"  Owner: {story.Assignees.Owner.Name}");
}

var usersWithStories = await dbContext
    .Set<TeamMember>()
    .Include(t => t.UserStoriesWhereOwner)
    .ToArrayAsync();
foreach (var user in usersWithStories)
{
    Console.WriteLine($"User: {user.Id} {user.Name}");
    foreach (var story in user.UserStoriesWhereOwner)
    {
        Console.WriteLine($"  Story: {story.Title}");
    }
}

class UserStory
{
    public int Id { get; set; }
    public string Title { get; set; }
    public WorkItemAssignees Assignees { get; set; } = new();
}

class WorkItemAssignees
{
    public UserStory UserStory { get; set; }

    public int OwnerId { get; set; }
    public TeamMember Owner { get; set; }
}

class TeamMember
{
    public int Id { get; set; }
    public string Name { get; set; }

    public IList<UserStory> UserStoriesWhereOwner { get; set; }
    public IList<WorkItemAssignees> AssigneesWhereOwner { get; set; }
}

class ScrumDbContext : DbContext
{
    public ScrumDbContext(DbContextOptions<ScrumDbContext> options) : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<TeamMember>(e =>
        {
            e.HasKey(x => x.Id);
            e.Property(x => x.Id).ValueGeneratedOnAdd();

            // Attempt 1: Unhandled exception. System.ArgumentException: The expression 'x => x.Assignees.Owner' is not a valid member access expression. The expression should represent a simple property or field access: 't => t.MyProperty'. (Parameter 'memberAccessExpression')
            // e.HasMany(x => x.UserStoriesWhereOwner)
            //     .WithOne(x => x.Assignees.Owner)
            //     .HasForeignKey(x => x.Assignees.OwnerId);
            
            // Attempt  2: Unhandled exception. System.ArgumentException: The expression 'x => Convert(x.Assignees.OwnerId, Object)' is not a valid member access expression. The expression should represent a simple property or field access: 't => t.MyProperty'. When specifying multiple properties or fields, use an anonymous type: 't => new { t.MyProperty, t.MyField }'. (Parameter 'memberAccessExpression')
            // e.HasMany(x => x.UserStoriesWhereOwner)
            //     .WithOne()
            //     .HasForeignKey(x => x.Assignees.OwnerId);
        });
        modelBuilder.Entity<UserStory>(e =>
        {
            e.HasKey(x => x.Id);
            e.Property(x => x.Id).ValueGeneratedOnAdd();
            e.OwnsOne(x => x.Assignees, o =>
            {
                o.WithOwner(x => x.UserStory);
                o.HasOne(x => x.Owner)
                    // Attempt 3: compile error because expects IEnumerable<WorkItemAssignees>
                    //  .WithMany(x => x.UserStoriesWhereOwner)

                    // Attempt 4: Unhandled exception. System.InvalidOperationException: The collection navigation 'UserStoriesWhereOwner' cannot be added to the entity type 'TeamMember' because its CLR type 'IList<UserStory>' does not implement 'IEnumerable<WorkItemAssignees>'. Collection navigations must implement IEnumerable<> of the related entity.
                    // .WithMany(nameof(TeamMember.UserStoriesWhereOwner))

                    // Attempt 5: Compiles but: Unhandled exception. System.InvalidOperationException: The navigation 'TeamMember.AssigneesWhereOwner' is not supported because it is pointing to an owned entity type 'WorkItemAssignees'. Only the ownership navigation from the entity type 'UserStory' can point to the owned entity type. See https://aka.ms/efcore-docs-owned for more information.
                    // .WithMany(x => x.AssigneesWhereOwner)

                    // Can run, but UserStoriesWhereOwner is not linked
                    .WithMany()
                    .HasForeignKey(x => x.OwnerId);
            });
        });
    }
}

Include stack traces

If relevant I can provide stack-traces for all error examples above, but I think it would bloat this issue. It rather seems an active limitation/restriction which is not documented but still enforced.

Include provider and version information

EF Core version: 6.0.9, 7.0.0-preview.7.22376.2
Database provider: Microsoft.EntityFrameworkCore.SqlServer and Microsoft.EntityFrameworkCore.InMemory
Target framework: .net 6.0
Operating system: Microsoft Windows 11 Enterprise 21H2
IDE: dotnet run

@ajcvickers
Copy link
Member

Note for triage: related to #26505 and #1985.

/cc @AndriySvyryd

@ajcvickers ajcvickers closed this as not planned Won't fix, can't repro, duplicate, stale Oct 13, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants