Reputation: 3466
Is there a way in Entity Framework (and I assume it will be with fluent syntax as data annotations are somewhat limited) to model a many-to-many relationship in which both sides are optional (a 0..M to 0..N relationship)? The use case is this: I would like to allow users to add tags to entities. Tags to entities is a M:N relationship, but neither should be required. That is, a tag can exist that is not applied to any entities and an entity can be untagged. This seems fairly reasonable to me. I can't simply model this using:
public virtual ICollection<Tag> Tags { get; set; }
and
public virtual ICollection<Entity> Entities { get; set; }
because each class has other relationships, and I get a "foreign key constraint may cause cycles or multiple cascade paths." I was hoping maybe I could do something like:
modelBuilder.Entity<Tag>().HasOptional(t => t.Entities);
modelBuilder.Entity<Entity>().HasOptional(t => t.Tags);
but I am warned that EF is "Unable to determine the principal end of the association." From reading, it seems that such relationships HAVE to have a principal end, but in my case, that's undesirable.
I could add a class to represent the bridge table and handle the mapping manually, but I'd prefer not to clutter the code. I was wondering if there is another way to model this in EF.
To fill in a bit more detail, there is also an Author class (which amounts to Users). Authors and tags are 1:M and Authors to Entities are also 1:M. So of course, the problem is that the Entities class occurs twice in the cascade tree. Making the Tag/Entity relationship optional would fix this. I could also fix it if there was a way to get to Tags through Entities, but since Tags can exist without being connected to an entity, I figured that would be impossible.
Here's a summary of the related code:
public class Author
{
public Guid Id { get; set; }
public virtual List<Entity> Entities { get; set; }
public virtual List<Tag> Tags { get; set; }
}
public class Tag
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; }
public virtual Author Author { get; set; }
public virtual ICollection<Entity> Entities { get; set; }
}
public class Entity
{
public Guid Id { get; set; }
public Guid AuthorId { get; set; }
public virtual Author Author { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
EDIT:
Using .HasMany().WithMany()
as suggested below gives me this:
CREATE TABLE [dbo].[TagEntities] (
[Tag_Id] [uniqueidentifier] NOT NULL,
[Entity_Id] [uniqueidentifier] NOT NULL,
CONSTRAINT [PK_dbo.TagEntities] PRIMARY KEY ([Tag_Id], [Entity_Id])
)
but what I WANT is for Tag_Id and Entity_Id to be nullable on this table. Maybe this model doesn't make as much sense as I thought?? Can you have a bridge table where both sides are nullable?
Upvotes: 4
Views: 2968
Reputation: 3466
I don't know if this is the RIGHT answer, but I solved this by creating a base class called DbEntity that other classes inherited from. So now Author has just:
// Both entities and tags are part of this collection
public virtual List<DbEntity> Entities { get; set; }
Both "Entities" (which has special meaning in my code) and "Tags" subclass DbEntity. This eliminated the multiple cascade paths while preserving the navigation properties, although I do need to do this:
author.Entities.OfType<Tag>();
or
author.Entities.OfType<Entity>();
to get specific sets of entities.
Upvotes: 0
Reputation: 32437
Use
modelBuilder.Entity<Tag>().HasMany(t => t.Entities)
.WithMany(t => t.Tags);
Instead of
modelBuilder.Entity<Tag>().HasOptional(t => t.Entities);
modelBuilder.Entity<Entity>().HasOptional(t => t.Tags);
Upvotes: 3