Consider the following 3 entities A, B, and C. A-to-C is a many-to-many relationship. B-to-A is a one-to-many relationship (B has many A, which implies B-to-C is also many-to-many).
If these overcomplicated relationships seems too vague to you, please consider this example:
a Track (A) has multiple Artists (C), an Artist has multiple Tracks (Many-to-Many)
an Album (B) has multiple Tracks. (One-to-Many, a Track cannot belong to multiple Albums)
so that (implication):
an Album has multiple Artists, and an Artist has multiple Albums (Many-to-Many)
Question: How to entities with such relationships? With the following code, I've established relationships between Album-to-Artist, Track-to-Artist. How to create Artist-Album relationship as it seems to require joining 4 tables/entities.
public class Context: DbContext
{
// irrelevant code omitted...
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TracksToArtists>()
.HasKey(r => new {r.TrackId, r.ArtistId});
modelBuilder.Entity<TracksToArtists>()
.HasOne(r => r.Track)
.WithMany(t => t.Artists)
.HasForeignKey(r => r.TrackId);
modelBuilder.Entity<TracksToArtists>()
.HasOne(r => r.Artist)
.WithMany(a => a.Tracks)
.HasForeignKey(r => r.ArtistId);
}
}
public class Track
{
public Guid Id { get; set; }
public string Name { get; set; }
public Guid AlbumId { get; set; }
public Album Album { get; set; }
public List<TracksToArtists> Artists { get; set; }
}
public class Album
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<Track> Tracks { get; set; }
// TODO: create album-to-artist many-to-many relationship
// public List<SomethingArtistMaybe?> Artists { get; set; }
}
public class Artist
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<TracksToArtists> Tracks { get; set; }
// TODO: create album-to-artist many-to-many relationship
// public List<SomethingAlbumMaybe?> Albums { get; set; }
}
// many-to-many relationship
public class TracksToArtists
{
public Guid TrackId { get; set; }
public Track Track { get; set; }
public Guid ArtistId { get; set; }
public Artist Artist { get; set; }
}
It's quite straightforward if just using raw SQL queries, yet with ORM everything becomes a bit painful.
Optimally, I'd like to avoid introducing AlbumToArtist table as it may create inconsistency in data. It should be something like this:
If I understand you correctly, Artist doesn't have a direct relationship to Album, the relationship is only via Track.
Consider putting the Album in the center, so that each Artist has multiple Album and vice versa (many-to-many), then have Album navigate to many Tracks (one-to-many).
Anyway, whether you choose to keep your model as in your question or as I suggested, accessing the graph from point to another (in your question Albums of each Artist), use eager loading:
public async Task<IEnumerable<Album>> GetAlbums(Artist artist)
{
return await myDbContext.Artists
.Include(artist => artist.Tracks)
.ThenInclude(track => track.Album)
.Where(a.Id == artist.Id)
.Select(artist =>
artist.Tracks.Select(track =>
track.Album))
.Distinct()
.ToListAsync();
}
You can also enable lazy-loading so you don't have to explicitly include the navigation properties in your query, but there is a performance cost for that.
Related
The context is : A Student can make appointments to Mentor and Mentor can have many appointments. Mentor may be student or lecturer.
I don't separate Student and Mentor but merge them into one entities User
public class User
{
public string Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public string Avatar { get; set; }
public string MeetUrl { get; set; }
public bool? IsMentor { get; set; }
public bool IsActive { get; set; }
public ICollection<Appointment> Appointments { get; set; }
}
public class Appointment
{
public string Id { get; set; }
public string StudentId { get; set; }
public User Student { get; set; }
public string MentorId { get; set; }
//public User Mentor { get; set; }
public bool IsApproved { get; set; }
}
I don't know how to config the relation of these entities with fluent-api to fit with the context.
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<Appointment>()
.HasOne<User>(a => a.Student)
.WithMany(st => st.Appointments)
.HasForeignKey(st => st.StudentId);
builder.Entity<Appointment>()
.HasOne<User>(a => a.Mentor)
.WithMany(m => m.Appointments)
.HasForeignKey(a => a.MentorId);
}
Should I separate User into 2 entities Student, Mentor?
What you are trying to do here with fluent syntax is set up "navigation properties".
Consider the following piece your code:
builder.Entity<Appointment>()
.HasOne<User>(a => a.Student)
.WithMany(st => st.Appointments)
.HasForeignKey(st => st.StudentId);
Here you are saying Appointments and User have a one-to-many relation where StudentId is your FK and Student represents the one User item this relation is linked to. You are also saying Appointments represents that many appointments that are linked to the User record the via the FK StudentId.
In other words, in this piece of code, you have specified that Appointments represents all the appointments of the current User as a student and not as a mentor. However, in the next fluent syntax, you are similarly trying to set the same property Appointments to represent the list of appointments that the current user has as a mentor.
You can't have the same property for both these use cases. You would need two properties in your User table like AppointmentsAsStudent and AppointmentsAsMentor.
I am struggling to write the code to generate a foreign key relationship between a one-to-one relationship of Bill and Pay:
public class Bill
{
public string Identifier { get; set; }
public Guid WebSiteId { get; set; }
}
public class Pay
{
public string Account { get; set; }
public Guid WebSiteId { get; set; }
}
The naming was done poorly - Identifier is Account, but it's probably not worth the refactor at this moment (there's static raw SQL queries in the codebase referencing the names directly).
The way to join these two tables in SQL is like this:
SELECT *
FROM Bills b
JOIN Pay p ON (b.Identifier = p.Account AND b.WebSiteId = p.WebSiteId)
And it will guarantee a one-to-one relationship between Bill and Pay.
How do I get EF core to understand this relationship?
I would like to use .Include for my joins:
context.Bills
.Include(x => x.Pays)
Meaning my models would look something like this
public class Bill
{
public string Identifier { get; set; }
public Guid WebSiteId { get; set; }
public virtual Pay Pay { get; set; }
}
public class Pay
{
public string Account { get; set; }
public Guid WebSiteId { get; set; }
public virtual Bill Bill { get; set; }
}
If you need both properties to uniquely identify a Pay, then I presume that you have a composite primary key on Pay entity.
builder.Entity<Pay>().HasKey(p => new { p.Account, p.WebSiteId });
In which case, you can configure the relationship using fluent API:
builder.Entity<Bill>()
.HasOne<Pay>(b => b.Pay)
.WithOne<Bill>(p => p.Bill)
.HasForeignKey<Bill>(b => new { b.Identifier, b.WebSiteId });
What is the effective way to resolve ambiguity of many-to-many relationships that point to the same entity either through annotations or fluent configuration? Given models such as:
public class Team
{
public int TeamId { get; set; }
public string Name { get; set; }
// Teams can be owned by multiple users
public List<User> Owners { get; set; }
// Teams can have multiple members
public List<User> Members { get; set; }
}
public class User
{
public int UserId { get; set; }
public string Name { get; set; }
// User can own zero to many teams
public List<Team> Owners { get; set; }
// User can be a member of zero to many teams
public List<Team> Members { get; set; }
}
Scaffolding results in an error along the lines of "Unable to determine the relationship by navigation "Team.Owners" of type "List".
Is this something that can be effectively resolved by manually creating join entities such as TeamOwner and TeamMember or would EF Core still struggle with ambiguity?
Thanks for any help you can provide.
You have two Navigation properties on each entity, and EF doesn't have a convention to identify which goes with which. So you need to configure the model to explicitly relate the navigation properties. You'll also want to pick a descriptive name for the linking table. EG:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Team>()
.HasMany(t => t.Owners)
.WithMany(o => o.OwnerOf)
.UsingEntity(j => j.ToTable("TeamOwners"));
modelBuilder.Entity<Team>()
.HasMany(t => t.Members)
.WithMany(o => o.MemberOf)
.UsingEntity(j => j.ToTable("TeamMembers"));
base.OnModelCreating(modelBuilder);
}
You can relate the navigation properties with annotations, but can't name the linking table. eg
public class Team
{
public int TeamId { get; set; }
public string Name { get; set; }
[InverseProperty(nameof(User.OwnerOf))]
public List<User> Owners { get; set; }
[InverseProperty(nameof(User.MemberOf))]
public List<User> Members { get; set; }
}
I wanted Movie, Actor, Director, User etc entities to have exactly one Image and an Image to belong to exactly one entity. I defined and configured them as -
Models : (simplified)
public class Movie
{
public int Id { get; set; }
public string Title { get; set; }
public Image Image { get; set; }
}
public class Actor
{
public int Id { get; set; }
public string Name { get; set; }
public Image Image { get; set; }
}
// Director, User etc are defined in similar way
public class Image
{
public int Id { get; set; }
public string Base64 { get; set; }
public int? MovieId { get; set; }
public int? ActorId { get; set; }
public int? DirectorId { get; set; }
public int? UserId { get; set; }
}
Configurations : (simplified)
public class MovieConfig : IEntityTypeConfiguration<Movie>
{
public void Configure(EntityTypeBuilder<Movie> builder)
{
builder.ToTable("Movie");
builder.HasKey(p => p.Id);
builder.Property(p => p.Title).IsRequired(true).HasColumnType("nvarchar(128)");
builder.HasOne(e => e.Image)
.WithOne()
.HasForeignKey<Image>(e => e.MovieId)
.OnDelete(DeleteBehavior.Cascade);
}
}
// Actors, Director, User etc are configured in similar way
public class ImageConfig : IEntityTypeConfiguration<Image>
{
public void Configure(EntityTypeBuilder<Image> builder)
{
builder.ToTable("Image");
builder.HasKey(p => p.Id);
builder.Property(p => p.Base64).IsRequired(true).HasColumnType("nvarchar(MAX)");
}
}
This generates the schema with Image table having one-to-one relationship with each of Movie, Actor, Director, User etc table, as expected.
What's bothering me are all those nullable foreign-key fields in Image, because -
a new nullable foreign-key must be added whenever a new entity with Image is introduced
for any image entry only one of those foreign-key columns is going to have a value
What other ways I could define and configure the entities to achieve the same result while avoiding those nullable foreign-keys?
Edit :
Or is it, in general practice, considered OK to have a schema like the one I currently have (with multiple nullable foreign-keys where only one of them can have a value)?
I don't have a lot of experience in database design/schema and best/general practices. It just felt wrong to me and that's where the question came.
So, please feel free to give your opinion and suggestion?
Create relationship for MovieId, ActorId, DirectorId and UserId in Image table in sql server.
Then re-update your dbcontext and those foreign IDs will be auto saved from movie, actor, director and user when inserting.
I have a question that I hope someone might be able to help me out with. I'm using Entity Framework 6 with Dynamic Filters to do stuff like filtering out soft deleted entities like so:
modelBuilder.Filter("SoftDeleteEntity", (ISoftDeleteEntity d) => d.DateDeleted, null);
However - I have a need to filter the entire graph when querying. So let's say that I have:
public class Company
{
public int CompanyId { get; set; }
public string Name { get; set; }
public bool Active { get; set; }
public List<Product> Products { get; set; }
}
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public Company Company { get; set; }
}
It's easy for me to filter out the in-active companies by creating a new filter:
modelBuilder.Filter("InactiveCompaniesFilter", (Company c) => c.Active, true);
However - if I now query my Products then I'll still get all Products belonging to inactive companies. I can of course avoid this be doing this everywhere I query my products:
_dbContext.Products.Where(i => i.Company.Active)
But as the application grows this becomes a major pain.
So the question is - can I create an Entity Framework filter that works across child/parent entities?
Thanks a lot :)