Entity Framework - many to many with one primary - entity-framework

I have a Channel domain model as:
public class Channel
{
public string Id {get; set;}
public List<ChannelUser> ChannelUsers {get; set;}
}
and a ChannelUser class as follows (I have additional meta data on the table, removed here for brevity, which is why I used a specfic class to represent the many-to-many relationship).
public class ChannelUser
{
public string ChannelId {get; set;}
public string UserId {get; set;}
}
The primary key for ChannelUser is (ChannelId, UserId).
One (and only one) of these ChannelUsers can be the Owner of the Channel.
Theoretically, a User can be the Owner of many channels (but obviously a ChannelUser can only be the owner of the channel it is associated to).
I want to enforce that at database level rather than having a "IsOwner" property on ChannelUser and using business logic to ensure that the flag is only set once per channel. I want to also enforce that the ChannelOwner is one of the ChannelUsers and therefore don't want a relationship directly from Channel > User but keep it as Channel > ChannelUser.
Therefore I have updated Channel class as follows:
public class Channel
{
public string Id {get; set;}
public List<ChannelUser> {get; set;}
public ChannelUser Owner {get; set;}
}
And added the following fluent API expressions:
modelBuilder.Entity<ChannelUser>().HasKey(cu => new { cu.ChannelId, cu.UserId });
modelBuilder.Entity<ChannelUser>().HasOne<Channel>(x => x.Channel).WithMany(x => x.ChannelUsers)
.OnDelete(DeleteBehavior.NoAction);
modelBuilder.Entity<ChannelUser>().HasOne<User>(x => x.User).WithMany(x => x.UserChannels)
.OnDelete(DeleteBehavior.NoAction);
When adding migration this results in:
Both relationships between 'ChannelUser.Channel' and 'Channel.ChannelUsers' and between 'ChannelUser' and 'Channel.Owner' could use {'ChannelId'} as the foreign key. To resolve this configure the foreign key properties explicitly on at least one of the relationships.
Which I understand but I think this is what is desired. Because ChannelUser.ChannelId should always be the same Channel.Id for both relationships.
Please can anyone suggest how I can craft this relationship or an alternative approach that still enforces the many to many and the one to one relationships between the tables?

This isn't ideal approach but can be done, have a look at this ER diagram :
Ideal approach would be having proper Entity Mapping like :
User Class :
public class User
{
public string Id {get; set;}
public virtual List<Channel> Channels {get; set;}
// Other properties....
}
Channel Class :
public class Channel
{
public string Id {get; set;}
public List<User> Users {get; set;}
public User Owner {get; set;}
public string? OwnerId { get; set; }
public User Owner { get; set; }
}
ChannelUser Class to join the two:
public class ChannelUser
{
public string Id {get; set;}
public string ChannelId {get; set;}
public string UserId {get; set;}
}
Mapping :
modelBuilder.Entity<User>()
.HasMany<Channel>(user => user.channels)
.WithMany(channel => channel.users)
.Map(cu =>
{
cu.MapLeftKey("UserId");
cu.MapRightKey("ChannelId");
cu.ToTable("ChannelUser");
});
// configure one-to-many relationship for ownership
modelBuilder.Entity<Channel>()
.HasRequired<User>(c => c.User)
.WithMany(u => u.ChannelId )
.HasForeignKey<int>(c => c.UserId);
Responsibility of checking if user exists should be fairly easy in controller code after normalizing this, or we can use DB procedure, trigger to create constrain that checks if given owner ID is indeed in ChannelUser table for given channel.

Related

ForeignKey Attribute in database first application

Following the ForeignKey docs, and multiple examples online I was under the influence that if I give my property (foreign key) this attribute, it would get replaced in a Html.Display call by the first textual property of the parent table.
This doesn't happen and all I get is the same foreign key field.
Does this work in db first applications, and if so, how do I make it work (using ForeignKey)?
Thanks.
EDIT: Or is this Scaffolding exclusive behaviour?
UPDATE: Example code:
// Entity model in Case.cs
public partial class Case
{
public int ID {get; set;}
public string Description {get; set;}
public int Classification_ID {get; set;}
public virtual Classification Classification {get; set;}
}
// Entity model in Classification.cs
// It's a lookup table
public partial class Classification
{
public int ID {get; set;}
public string Label {get; set;}
}
// File with partials
[MetadataType(typeof(CaseMetadata))]
public partial class {}
public class CaseMetadata
{
[ForeignKey("Classification")]
public int Classification_ID {get; set;}
}

EF Core, Primary Key is not auto generated for Entity which inherit from ICollection

Here is my Entity:
public class StackImage: ICollection<StackFile>
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual Guid Id { get; set; }
private IList<StackFile> StackFiles { get; set; } = new List<StackFile>();
public StackImage()
{
}
[...] // Implementation of ICollection
}
public class StackFile
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public virtual Guid Id { get; set; }
public string Url { get; set; }
public int Position { get; set; }
public StackFile(){}
}
stackImage.Add(new StackFile(url));
stackImage= await _stackImageRepository.UpdateAsync(stackImage);
await _unitOfWork.SaveChangesAsync();
In this sample after UpdateAsync, the StackImage Id is not generated (stackImage.Id == default) but the StackFile Id is correctly generated (stackImage[0].Id == default)
Did you already noticed this problem? My guess is, EF Core see StackImage as a list and doesn't try to generate a new Guid. How to fix this issue?
EDIT:
From what I can read on the web and by responses I received, It seems not possible to do it. If someone has the solution, please let us know :)
It seems to me that you want to design a database with (at least) two tables. A table with StackImages and a table with StackFiles.
You want to design a one-to-many relation between StackImages and StackFiles: every StackImage has zero or more StackFiles, every StackFile belongs to exactly one StackImage. In a database this is implemented using a foreign key.
Hence, it is not true that a StackImage is a StackFile. However, you can say that a StackImage has some StackFiles.
Following the entity framework code first conventions your classes should be similar to:
class StackImage
{
public Guid Id {get; set;}
...
// every StackImage has zero or more StackFiles (one-to-many):
public virtual ICollection<StackFile> StackFiles {get; set;}
}
class StackFile
{
public Guid Id {get; set;}
...
// every StackFile belongs to exactly one StackImage, using foreign key:
public Guid StackImageId {get; set;}
public virtual StackImage StackImage {get; set;}
}
finally the DbContext:
class MyDbcontext : DbContext
{
public DbSet<StackImage> StackImages {get; set;}
public DbSet<StackFile> StackFiles {get; set;}
}
Note the use of virtual properties to express the relations between the tables. As the foreign key StackImageId is supposed to be a real column, it is not virtual
In entity framework the columns of a table are represented by non-virtual properties,
the virtual properties represent the relations between the tables.
Because I followed the conventions, there is no need for attributes, nor fluent API. Entity framework detects the one-to-many collection and creates the proper tables for you. Only if you want different identifiers for your tables or columns you'll need fluent API or attributes.

Updating a many-to-many relationship neither entirely code nor database first

I seem to be struggling with a combination of naming conventions and understanding.
I have inherited a database and am building a MVC site that I was unable to get the "database-first" workflow to play nicely. In the end I manually constructed my context classes and have been working away happily.
I am now in a situation where I am unable to add an entity with a relationship to several other existing entities (the many-to-many).
My database looks (simplified for this question) like this:
ListItem Option OptionListItems
====== ====== ===============
Id Id ListItem_Id
Name Name Option_Id
My context contains a property that allows me to get all of my ListItems:
public virtual DbSet<ListItem> ListItems { get; set; }
And if I use some LINQ, I do something like the following, and the items are returned and the many-to-many relationship is satisfied and I get a list of Option within my ListItem:
var item = _context.ListItems
.Where(p => p.Id == id)
.Include(p => p.Options)
.SingleOrDefault();
In fact, I had to construct the cross-reference table in the database manually which I did when I tried to run the above query and the exception I got told me I had no object called dbo.OptionListItems. So I assumed we were all good.
Now I need to create a new ListItem and link it to one or more existing Option and I'm at a loss.
Once I've created my new ListItem in isolation, and attempt to call listItem.Options.Add(...) it fails, but I also get the exact same exception if I try to get a reference to a particular Option and try to do option.ListItems.Add(...).
The error is kind of amusing and is the opposite table name to what I have:
{"Invalid object name 'dbo.ListItemOptions'."}
I suspect that it goes against the grain of EF to build a type and a property on my context to directly access the cross reference table like this:
public virtual DbSet<OptionListItem> OptionListItems { get; set; }
But I'm completely baffled by the pattern to create new relationships.
We have this (many-to-many) working declaratively. Pseudocode:
public class ListItem
{
public ListItem()
{
this.RelatedOptions = new HashSet<OptionListItems>();
}
[Key]
public int Id {get; set;}
public string Name {get; set;}
public virtual ICollection<OptionListItems> RelatedOptions {get; set;}
}
public class Option
{
public Ortion()
{
this.RelatedItems = new HashSet<OptionListItems>();
}
[Key]
public int Id {get; set;}
public string Name {get; set;}
public virtual ICollection<OptionListItems> RelatedItems {get; set;}
}
public class OptionListItems
{
[Key]
public int Id {get; set;}
[Column("ListItemId", Order = 1)]
[ForeignKey("ParentListItem")]
public int ListItemId {get; set;}
[Column("OptionId", Order = 2)]
[ForeignKey("ParentOption")]
public int OptionId {get; set;}
public virtual ListItem ParentListItem {get; set;}
public virtual Option ParentOption {get; set;}
}
This should create full relationship declaratively
Credit goes to Steve Greene for pointing me in the right direction.
The table I had was created by convention and worked when I queried WorkItem with .Include(p => p.Options) however the convention seems to break down if you try to do an update. I'm unsure why, but the construction of the mapping table seems to be <Entity1>+<Entity2>+s when querying, but <Entity2>+<Entity1>+s when updating...
The good news, by using fluentAPI, I've created a specific mapping between the entities and forced the cross reference table and both querying and updating works!
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ListItem>()
.HasMany<Option>(s => s.Options)
.WithMany(c => c.ListItems)
.Map(cs =>
{
cs.MapLeftKey("ListItem_Id");
cs.MapRightKey("Option_Id");
cs.ToTable("OptionListItems");
});
}

Bidirectional links to the same class in Entity Framework (code first)

I would like to create bidirectional links to the same class. Id like for the relationship class to have the attributes that would explain how the two classes are related. It may be a parent-child relationship or it be a simple "reference" relationship.
Currently, if I use the setup below, Entity Framework will automatically create a 3rd foreign key in the link table for the "myChildNodes" relationship. The only way I can get Entity Framework to understand what I am trying to do on the link class is to create two collections I.E. (childOf and ParentOf).
I would like to dynamically add relationship types and not need to create a collection representing that relationship type. I would rather handle that in the repository for the node object.
Node
{
Public int id {get; set;}
Public datetime createDate {get; set;}
Public bool isModified {get; set;}
//I would like just one collection for all links from this node as the source node
Public virtual ICollection<Link> myChildNodes{get; set;}
//I don't want to use something like this that explicitly defines the relationship
//Public virtual ICollection<Node> parentOf{get; set;}
//Public virtual ICollection<Node> childOf{get; set;}
Public Node() {
}
}
Link {
Public int id {get; set;}
Public datetime createdDate {get; set;}
Public string linkType {get; set;}
[ForeignKey("SourceNode")]
Public int? SourceNodeId { get; set;}
Public Node SourceNode {get; set;}
[ForeignKey("TargetNode")]
Public int? TargetNodeId { get; set;}
Public Node TargetNode {get; set;}
Public Link() {
}
}
Has anyone had success with this design before?

How to model more entities to reference same kind of collections within single table

I'm not very experienced with EF and I'm trying to figure out what is the proper way or what are the options for creating entity, collection of which can be contained in other entities (different tables).
Let's say I have three existing classes (ModuleA, ModuleB, ModuleD) that I want to contain its own collection of Data entities.
I wanted a single table for Data entities but I don't like the idea of three nullable Guid columns for each FK.
That also gives me error on applying migration ("...may cause cycles or multiple cascade paths") - which could by probably solved by removing cascade delete and deleting Data manually, but I don't like that idea.
What I would like most is the single (shared) Guid property on Data for FKs to all three modules, which is not possible at least not without same ID existing in all three main tables at the same time (since it creates three FKs in DB).
class Data
{
public int Id {get; set;}
public byte[] Values {get; set;}
}
class ModuleA
{
public Guid Id {get; set;}
public ICollection<Data> Data {get; set;}
// some other stuff...
}
class ModuleB
{
public Guid Id {get; set;}
public ICollection<Data> Data {get; set;}
// ...
}
class ModuleC
{
public Guid Id {get; set;}
public ICollection<Data> Data {get; set;}
// some different other stuff...
}
You can create 3 module-to-data many-to-many tables like this:
class Data
{
public int Id {get; set;}
public byte[] Values {get; set;}
}
class ModuleAData
{
public ModuleAId {get; set;}
public DataId {get; set;}
}
class ModuleA
{
public Guid Id {get; set;}
public ICollection<ModuleAData> Data {get; set;}
// some other stuff...
}