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

Support unidirectional many-to-many relationships #3864

Closed
Tracked by #22960 ...
dealdiane opened this issue Nov 23, 2015 · 15 comments · Fixed by #28371
Closed
Tracked by #22960 ...

Support unidirectional many-to-many relationships #3864

dealdiane opened this issue Nov 23, 2015 · 15 comments · Fixed by #28371
Assignees
Labels
area-many-to-many closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. ef6-parity punted-for-6.0 type-enhancement
Milestone

Comments

@dealdiane
Copy link

dealdiane commented Nov 23, 2015

Support shadow navigation properties such that views over the navigations can function even when a CLR type doesn't have an actual navigation property. This would enable unidirectional many-to-many relationships like this:

class Post
{
  public int Id { get; set; }
  public ICollection<Tag> Tags { get; set; }
}

class Tag
{
  public int Id { get; set; }
}
builder.Entity<Post>()
  .HasMany(p => p.Tags)
  .WithMany();

Original issue:

Are there any plans to support this?

table.Include(t => EF.Property<Entity>(t, "Property"))
@divega
Copy link
Contributor

divega commented Nov 23, 2015

No plans for EF Core RTM.

We are going to enable unbound navigation properties only in metadata so that they can be used in the model snapshot for Migrations (see #2140) but we plan to block their use in runtime scenarios.

Anyway, in case we decide to enable this feature in the future. Could you explain how you expect it to work and how you would take advantage of it?

@dealdiane
Copy link
Author

dealdiane commented Nov 24, 2015

TL;DR I'm closing this as it might be only beneficial for a very specific use-case and by using extension methods and casting, I got it to work in the end without hurting performance (I hope).


The only reason why someone might find this beneficial is if they're using a generic repository pattern.

Suppose I have these DTOs in my "Core" project without a reference to EF Core.

public class Product
{
    public int Id { get; set; }

    public IEnumerable<string> Keywords { get; set; }
}

public class Review
{
    public int Id { get; set; }

    public Product Product { get; set; }
}

.. and these DTOs are from a separate project with a reference to EF Core. They inherit from the Core DTOs. You could argue that Keywords should've been kept in a separate table and I think you're right but I'm working with an existing database here so I don't have the flexibility of defining my own schema.

public class EFProduct
{
    public int Id { get; set; }

    public string KeywordsCombined 
    { 
        get
        {
            return String.Join(",", Keywords);
        }

        set
        {
            Keywords = value?.Split(",");
        }
    }
}

public class EfReview
{
    public EFProduct EFProduct
    {
        get
        {
            return Product as EFProduct;
        }
        set
        {
            Product = value;
        }
    }
}

I would configure my DBContext like so,

modelBuilder.Entity<EfProduct>()
    .Ignore(p => p.Keywords);

modelBuilder.Ignore<Review>();

modelBuilder.Entity<EfReview>
    .Ignore(r => r.Product)
    .HasOne(r => r.EFProduct)
    .WithMany()
    .HasForeignKey(r => r.ProductId);

Here's my generic repository interface:

public interface IRepository<TEntity> : IQueryable<TEntity>
{
     // IQueryable members here
     // CRUD members here
}

and the EF implementation of that interface:

public class EFRepository<TEntity> : IRepository<TEntity>
{
    public EntityFrameworkRepository(DbContext db)
    {
        Table = _db.Set<TEntity>();
    }

    public Type ElementType
    {
        get
        {
            return Table.ElementType;
        }
    }

    public Expression Expression
    {
        get
        {
            return Table.Expression;
        }
    }

    public IQueryProvider Provider
    {
        get
        {
            return Table.Provider;
        }
    }

    public virtual DbSet<TEntity> Table
    {
        get;
        private set;
    }
}

This is where I would find this helpful:

Suppose I have this interface in my core as well:

public interface IProductService
{
    IAsyncEnumerable<Review> GetReviews();
}

and my implementation from the class that has a reference to EF Core:

public class ProductService
{
    private readonly IRepostitory<Review> _reviews;

    public ProductService(IRepostitory<Review> reviews)
    {
        _reviews = review;
    }

    public IAsyncEnumerable<Review> GetReviews()
    {
        return _reviews
            .Include(r => EF.Property<Product>(r, "EFProduct"))
            .AsAsyncEnumerable();
    }
}

Since in my ProductService I am taking an instance of IRepository<Review> not IRepository<EFReview>, I would not have access to the EFProduct property. You might ask, "why not take a dependency on IRepository<EFReview> instead?". Well, yes, If I could I will but the project that ties everything together (a website in this case) do not know anything about EF at all which means it doesn't know EFReview. All it knows, is that I have an IProductService that takes an IRepository<Review> dependency (only the types from my Core project).

All this crazy abstraction is so that we can swap out different implementations of different services as we move multi-platform on our project.

@divega
Copy link
Contributor

divega commented Nov 24, 2015

Thanks. From the description I understand that your scenario is that you want to apply a very specific workaround to a limitation of EF (ideally you should be able to map the Keywords property) while keeping layers above the repository persistence agnostic.

I believe there are other scenarios in which not having a navigation property in the CLR type but still being able to refer to it in LINQ queries could be more generally useful, so I will reopen the issue for triage.

@divega divega reopened this Nov 24, 2015
@dealdiane
Copy link
Author

From the description I understand that your scenario is that you want to apply a very specific workaround to a limitation of EF (ideally you should be able to map the Keywords property) while keeping layers above the repository persistence agnostic.

I think you might be referring to the EFProduct property in the EFReview class. Actually, it's a workaround another limitation of EF. If EF could map the EFReview.Product to an EFProduct class then there is no need for the EFReview class - let me know if EF can do this, I might've just missed it. In my example, the EFReview class is only there as a wrapper so that the navigation property Product would work.

I believe there are other scenarios in which not having a navigation property in the CLR type but still being able to refer to it in LINQ queries could be more generally useful

I'm pretty sure there is and this would probably be especially useful when abstracting things although I could not think of anything at the moment except what I've given so far.

@rowanmiller rowanmiller added this to the Backlog milestone Nov 24, 2015
@Deilan
Copy link
Contributor

Deilan commented Jul 5, 2016

Any updates on the subject?

@rowanmiller
Copy link
Contributor

Not yet. We have higher priority features on the backlog, so this won't be in the first round of features we pick to implement.

@AndriySvyryd AndriySvyryd changed the title Support shadow properties in Include Support shadow navigations on non-shadow entities Sep 13, 2017
@HappyNomad
Copy link

Once #1368 (comment) and #8881 are both implemented then, given (1) a many-to-many relationship between Product and Org and (2) a many-to-many shadow navigation property PurchasedBy of type IList<Org>, I'll need to write:

modelBuilder.Entity<ProductDetail>().HasQueryFilter( d => d.Product.PurchasedBy.Any( o => o.ID == TenantID ) );

So I need to tack the PurchasedBy shadow property onto d.Product. Will the following work?

modelBuilder.Entity<ProductDetail>().HasQueryFilter( d => EF.Property<IEnumerable<Org>>( d, "Product.PurchasedBy" ).Any( o => o.ID == TenantID ) );

If support for this syntax isn't yet planned, then please address this issue (or #6412 but it's closed.) to enable this scenario.

@ab-tools
Copy link

Is there any update on this?
As I see a few other recent issues created and closed by pointing to this one, looks like I'm not the only one interested in that. :-)

It would be great to see unidirectional many-to-many relationships being supported soon!

@luizfbicalho
Copy link

One of the Object oriented features in the model is to limit the visibility of one entity.
What's the status on this feature @ajcvickers ?

@ajcvickers
Copy link
Member

@luizfbicalho @ab-tools This issue is in the Backlog milestone. We are currently planning EF Core 6.0 and there is a good chance this feature will make the cut. However, keep in mind that there are many other high priority features with which it will be competing for resources. Make sure to add your vote (👍) at the top.

@yecril71pl
Copy link
Contributor

modelBuilder.Entity < Dependent > ()
  .ToTable("Dependent")
  .HasMany < Superior > ()
  .WithMany (m => m .Dependents);

Unable to set up a many-to-many relationship between the entity types Dependent and Superior because one of the navigations was not specified. Provide a navigation in the HasMany call in OnModelCreating. Consider adding a private property for this.

  1. Microsoft.EntityFrameworkCore.Metadata.Builders.CollectionNavigationBuilder`2.WithMany(Expression`1 navigationExpression)
  2. Data.MedicContext.OnModelCreating(ModelBuilder modelBuilder)

@freer4
Copy link

freer4 commented Feb 25, 2022

Just a heads up, the documentation clearly states that single-property navigation is possible via a parameterless WithMany()

https://docs.microsoft.com/en-us/ef/core/modeling/relationships?tabs=fluent-api%2Cfluent-api-simple-key%2Csimple-key#single-navigation-property-1

As of 6.0.2 this does not work, who is in charge of the docs?

@stap123
Copy link

stap123 commented Feb 26, 2022

@freer4 At the very bottom of every docs page there is a link where you can report issues about problems for that doc specifically. It gives the team everything they need to know in order to fix the issue.

ajcvickers added a commit that referenced this issue Jul 5, 2022
ajcvickers added a commit that referenced this issue Jul 9, 2022
ajcvickers added a commit that referenced this issue Jul 10, 2022
ajcvickers added a commit that referenced this issue Jul 12, 2022
@ghost ghost closed this as completed in 447322c Jul 13, 2022
@ajcvickers ajcvickers modified the milestones: 7.0.0, 7.0.0-preview7 Jul 13, 2022
@ajcvickers ajcvickers added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Jul 13, 2022
@ajcvickers ajcvickers changed the title Support unidirectional many-to-many relationships through shadow navigations Support unidirectional many-to-many relationships Aug 18, 2022
@ajcvickers ajcvickers modified the milestones: 7.0.0-preview7, 7.0.0 Nov 5, 2022
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-many-to-many closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. ef6-parity punted-for-6.0 type-enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.