EF Many-To-Many on single entity - entity-framework

I have an issue to implement many-to-many relationship with same entities. Here's my class:
public class District
{
[Key]
public int DistrictId { get; set; }
[Required]
public string Title { get; set; }
[Required]
public string Abbreviation { get; set; }
public List<District> SubDistricts { get; set; }
}
My goal is to have all districts within same table, and to have them correlated, many districts to many districts.
If I don't specify mappings, EF Code First acts as if it is one-to-many relationship.
I've tried to give directions to model builder, but it's not working:
modelBuilder.Entity<District>()
.HasMany(d => d.SubDistricts)
.WithMany(d => d.SubDistricts)
.Map(mc => { mc.ToTable("DistrictLinks", "dbo");
mc.MapLeftKey("ParentId");
mc.MapRightKey("ChildId");
});
Is there any way to do this with WF? Thanks in advance!

You must use the WithMany overload which doesn't take a parameter:
modelBuilder.Entity<District>()
.HasMany(d => d.SubDistricts)
.WithMany()
.Map(mc => { mc.ToTable("DistrictLinks", "dbo");
mc.MapLeftKey("ParentId");
mc.MapRightKey("ChildId");
});
It is not possible that the same navigation property is start and end of an association at the same time. They either must be different or the end is "unvisible" and not exposed in the model - which is the case in your model.

Your solution works well, thank you! In the meanwhile, I've came up with another way to resolve issue. Basically I've created two navigational properties in class:
public List<District> ChildDistricts { get; set; }
public List<District> ParentDistricts { get; set; }
so my mapping looks like this now:
modelBuilder.Entity<District>()
.HasMany(d => d.ParentDistricts)
.WithMany(d => d.ChildDistricts)
.Map(mc => { mc.ToTable("DistrictLinks", "dbo");
mc.MapLeftKey("ParentId");
mc.MapRightKey("ChildId");
});
As a result, I get exactly the same kind of table in SQL Server, but I believe I can navigate better like this. I actually forgot to mention that hierarchy is of importance here as well, not just links between districts.
Thank you once again :)

Related

EF Core Navigation property on ApplicationUser is always null

I'm trying to have a list of "buddies" for an ApplicationUser (just using the standard ASP NET Core Identity implementation).
In my mind, an ApplicationUser can have multiple Buddies, and a Buddy can have multiple ApplicationUser's. This means its a many to many relationship.
In line with EF Core's existing limitations with M2M relationships, I made a join class that looks like this:
public class ApplicationUserBuddies
{
public string UserGUID { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public string BuddyGUID { get; set; }
public ApplicationUser BuddyUser { get; set; }
}
I then configure ModelBuilder to recognise this relationship with in my OnModelCreating override
builder.Entity<ApplicationModels.ApplicationUserBuddies>().HasOne(x => x.ApplicationUser)
.WithMany(x => x.Buddies).HasForeignKey(x => x.UserGUID).IsRequired();
However, when I query through this table using EF, the "buddy" navigation property is always null. The actual string is set correctly (to that users GUID). This is how I am getting those details (note I am using Include):
var buddies = await _context.ApplicationUserBuddies.Include(x => x.BuddyUser).Where(x => x.UserGUID == UserGUID)
.Select(x => x.BuddyUser).ToListAsync();
Another strange thing is in my ApplicationUserBuddies table, I get a third column that I haven't set up anywhere called BuddyUserID
I think the issue I am having is because I have a many to many relationship that references one table (AspNetUsers table) and that could be affecting it.
How can I have a list of users as a navigation property from AspNetUsers and have it work correctly? Thanks everyone.
EDIT
I have changed my model to be in line with EF conventions
public class ApplicationUserBuddies
{
public string UserId { get; set; }
public ApplicationUser ApplicationUser { get; set; }
public string BuddyUserId { get; set; }
public ApplicationUser BuddyUser { get; set; }
}
And my model builder now looks like this:
builder.Entity<ApplicationModels.ApplicationUserBuddies>().HasKey(x => new { x.UserId, x.BuddyUserId });
builder.Entity<ApplicationModels.ApplicationUserBuddies>().HasOne(x => x.ApplicationUser)
.WithMany(x => x.Buddies).HasForeignKey(x => x.BuddyUserId).IsRequired();
And now I have the columns UserId, BuddyUserId, and the erronerous BuddyUserId1. I think that this is essentially the same issue that I had at the outset.

Entity Framework core many to many not inserting

I am using EF7 and have a scenario which needs a many to many relationship.
I have a ParticipantSIR entity and a ParticipantAssessmentReport entity. There is a link table ParticipantSIRAssessmentReport between them.
public class ParticipantSIR
{
public int ParticipantSIRID { get; set; }
public virtual ICollection<ParticipantSIRAssessmentReport> ParticipantSIRAssessmentReport { get; set; }
public virtual Participant Participant { get; set; }
}
public class ParticipantAssessmentReport
{
public int ParticipantAssessmentReportID { get; set; }
public virtual ICollection<ParticipantSIRAssessmentReport> ParticipantSIRAssessmentReport { get; set; }
}
public partial class ParticipantSIRAssessmentReport
{
public int ParticipantSIRID { get; set; }
public int ParticipantAssessmentReportID { get; set; }
public virtual ParticipantAssessmentReport ParticipantAssessmentReport { get; set; }
public virtual ParticipantSIR ParticipantSIR { get; set; }
}
modelBuilder.Entity<ParticipantSIRAssessmentReport>(entity =>
{
entity.HasKey(e => new { e.ParticipantSIRID, e.ParticipantAssessmentReportID });
entity.HasOne(d => d.ParticipantAssessmentReport).WithMany(p => p.ParticipantSIRAssessmentReport).HasForeignKey(d => d.ParticipantAssessmentReportID).OnDelete(DeleteBehavior.Restrict);
entity.HasOne(d => d.ParticipantSIR).WithMany(p => p.ParticipantSIRAssessmentReport).HasForeignKey(d => d.ParticipantSIRID).OnDelete(DeleteBehavior.Restrict);
});
This appears to be the way this needs to be setup with EF core including the third entity. I got some of the information from. http://ef.readthedocs.io/en/latest/modeling/relationships.html#many-to-many
When I insert data the 2 outside entities get populated but not the link table.
Since there are no navigation properties between the ParticipantSIR and ParticipantAssessmentReport then I'm not sure how to add the linked data.
_db.ParticipantAssessmentReport.Add(participantAssessmentReport);
foreach (var sir in participantSirs)
{
_db.ParticipantSIR.Add(sir);
}
_db.SaveChanges();
Assuming we're talking about EF Core 1.0rc1 it looks like you have created your model correctly (except the virtual keyword doesn't do anything yet as lazy loading hasn't been implemented).
As many-to-many hasn't been implemented yet as of 1.0rc1 you need to do some extra work. See https://github.com/aspnet/EntityFramework/issues/1368#issuecomment-180066124 for the classic blog Post, Tag, PostTag example code.
In your case you need to explictly add to ParticipantSIRAssessmentReport, something like this:
var participantSIRAssessmentReport = new ParticipantSIRAssessmentReport {ParticipantSIR = participantSIR, ParticipantAssessmentReport = participantAssessmentReport };
_db.ParticipantSIRAssessmentReport.Add(participantSIRAssessmentReport);
_db.SaveChanges();
To map Many-To-Many relationships in EF you need to add the following to your DbContext's OnModelCreating() method:
modelBuilder.Entity<ParticipantSIR>()
.HasMany(e => e.ParticipantSIRAssessmentReport)
.WithMany(e => e.ParticipantSIR)
.Map(e => e.ToTable("ParticipantSIRAssessmentReport") //Name of the linking table
.MapLeftKey("ParticipantSIRId") //Name of the Left column
.MapRightKey("ParticipantSIRAssessmentReportId")); //Name of the right column
From here the relationship will be handled using the Collections inside each of the classes.

EF Many to Many: A has many B's, B doesn't need to know

Say I have the following. Note that Category doesn't need to have a reference to the projects it's associated with. Is there a way to configure this, or does convention dictate that I should be throwing a collection of Projects in my Category model?
public class Project
{
public Guid Id { get; set; }
public virtual ICollection<Category> Categories { get; set; }
}
public class Category
{
public Guid Id { get; set; }
}
There are a few similar questions around but the answers don't seem to address it.
Update:
As I thought, I do need to have that intermediate table, but EF does it for me by following Moho's answer.
Use Fluent API:
modelBuilder.Entity<Project>()
.HasMany( p => p.Categories )
.WithMany();
This is possible using Fluent api:
modelBuilder.Entity<Project>()
.HasMany(p => p.Categories)
.WithMany()
.Map(m =>
{
m.MapLeftKey("Id");
m.MapRightKey("Id");
m.ToTable("ProjectsCategories");
});

Fluent API - collection navigation

I am working with asp.net mvc with durandal & breeze templates.
I have the following classes (removed some properties for clarity):
public class Rolling
{
[Key]
public int Id { get; set; }
...
public virtual List<Itinerary> Itineraries { get; set; }
}
public class Itinerary
{
[Key]
public int Id { get; set; }
...
public int? VehicleId { get; set; }
public int? TrailerId { get; set; }
...
public virtual Rolling Vehicle { get; set; }
public virtual Rolling Trailer { get; set; }
}
I have the following fluent API:
modelBuilder.Entity<Rolling>()
.HasMany(c => c.Itineraries)
.WithOptional(c => c.Vehicle)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Rolling>()
.HasMany(c => c.Itineraries)
.WithOptional(c => c.Trailer)
.WillCascadeOnDelete(false);
Please note the order of declaring these fluent APIs.
At runtime, I perform a breeze query client side to get all the rolling entities:
var query = entityQuery.from('Rollings')
.where(predicates)
.orderBy(orderyByClause)
.expand('Itineraries');
For each one, I check the itineraries collection property:
rollings()[0].itineraries
I noted that I only retrieved itineraries for rollings where the Trailer property of Itinerary is filled. None of the rollings where the Vehicle property of Itinerary is filled.
BUT if I swap fluent API like this:
modelBuilder.Entity<Rolling>()
.HasMany(c => c.Itineraries)
.WithOptional(c => c.Trailer)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Rolling>()
.HasMany(c => c.Itineraries)
.WithOptional(c => c.Vehicle)
.WillCascadeOnDelete(false);
Then I noted that I only retrieved itineraries for rollings where the Vehicle property of Itinerary is filled. None of the rollings where the Trailer property of Itinerary is filled.
Is this a normal behaviour? A bug?
Thanks for investigating.
It looks like this is a follow up question for Navigation property no more working after migration of breeze 1.4.2.
I was able to verify the behavior you described.
I also verified that this is the EF behavior by running the query on the server:
var rolling = mgr.Context.Rollings.Include("Itineraries").First();
var itineraries = mgr.Context.Itineraries.Local;
You will notice that itineraries will only have Itineraries with either Vehicle or Trailer filled (depending on the fluent API order of definitions).
It looks like a bug to me, but an Entity Framework bug (not Breeze's bug).
I think Microsoft will have a better saying in this, so posted your question at http://social.msdn.microsoft.com/Forums/en-US/7438371d-1072-4a9a-aab8-d12842493066/possible-bug-include-not-working-properly.

EF 4.1 Code First ModelBuilder HasForeignKey for One to One Relationships

Very simply I am using Entity Framework 4.1 code first and I would like to replace my [ForeignKey(..)] attributes with fluent calls on modelBuilder instead. Something similar to WithRequired(..) and HasForeignKey(..) below which tie an explicit foreign key property (CreatedBySessionId) together with the associated navigation property (CreatedBySession). But I would like to do this for a one to one relationsip instead of a one to many:
modelBuilder.Entity<..>().HasMany(..).WithRequired(x => x.CreatedBySession).HasForeignKey(x => x.CreatedBySessionId)
A more concrete example is below. This works quite happily with the [ForeignKey(..)] attribute but I'd like to do away with it and configure it purely on modelbuilder.
public class VendorApplication
{
public int VendorApplicationId { get; set; }
public int CreatedBySessionId { get; set; }
public virtual Session CreatedBySession { get; set; }
}
public class Session
{
public int SessionId { get; set; }
[ForeignKey("CurrentApplication")]
public int? CurrentApplicationId { get; set; }
public virtual VendorApplication CurrentApplication { get; set; }
public virtual ICollection<VendorApplication> Applications { get; set; }
}
public class MyDataContext: DbContext
{
public IDbSet<VendorApplication> Applications { get; set; }
public IDbSet<Session> Sessions { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Session>().HasMany(x => x.Applications).WithRequired(x => x.CreatedBySession).HasForeignKey(x => x.CreatedBySessionId).WillCascadeOnDelete(false);
// Note: We have to turn off Cascade delete on Session <-> VendorApplication relationship so that SQL doesn't complain about cyclic cascading deletes
}
}
Here a Session can be responsible for creating many VendorApplications (Session.Applications), but a Session is working on at most one VendorApplication at a time (Session.CurrentApplication). I would like to tie the CurrentApplicationId property with the CurrentApplication navigation property in modelBuilder instead of via the [ForeignKey(..)] attribute.
Things I've Tried
When you remove the [ForeignKey(..)] attribute the CurrentApplication property generates a CurrentApplication_VendorApplicationId column in the database which is not tied to the CurrentApplicationId column.
I've tried explicitly mapping the relationship using the CurrentApplicationId column name as below, but obviously this generates an error because the database column name "CurrentApplicationId" is already being used by the property Session.CurrentApplicationId:
modelBuilder.Entity<Session>().HasOptional(x => x.CurrentApplication).WithOptionalDependent().Map(config => config.MapKey("CurrentApplicationId"));
It feels like I'm missing something very obvious here since all I want to do is perform the same operation that [ForeignKey(..)] does but within the model builder. Or is it a case that this is bad practise and was explicitly left out?
You need to map the relationship as one-to-many and omit the collection property in the relationship.
modelBuilder.Entity<Session>()
.HasOptional(x => x.CurrentApplication)
.WithMany()
.HasForeignKey(x => x.CurrentApplicationId)