I am trying to connect two tables with a code first migration. I thought EF would create many to many relationship table itself but I get error "build failed". While building whole project everything works fine. It's just the migration.
Following are my models -
Task:
[Key]
public int Id { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? EndedAt { get; set; }
[Column(TypeName = "text")]
public string CreatedBy { get; set; }
[Required]
[Column(TypeName = "text")]
public string Title { get; set; }
[Required]
[Column(TypeName = "text")]
public string Description { get; set; }
public virtual TaskGroups TaskGroup { get; set; }
public string Status { get; set; }
[Column(TypeName = "text")]
public string WantedUser { get; set; }
TaskGroup:
[Required]
public int Id { get; set; }
[Required]
public string GroupName { get; set; }
public virtual Tasks Tasks { get; set; }
At first I've tried with ICollection<> but I got the same error.
My project is .Net Core 3.
Any ideas?
Edit
Tasks
[Key]
public int Id { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? EndedAt { get; set; }
[Column(TypeName = "text")]
public string CreatedBy { get; set; }
[Required]
[Column(TypeName = "text")]
public string Title { get; set; }
[Required]
[Column(TypeName = "text")]
public string Description { get; set; }
public string Status { get; set; }
[Column(TypeName = "text")]
public string WantedUser { get; set; }
public IList<TaskGroupTask> TaskGroupTask { get; set; }
TaskGroups
[Key]
public int Id { get; set; }
[Required]
public string GroupName { get; set; }
public IList<TaskGroupTask> { get; set; }
TaskGroupTask
public int TaskId { get; set; }
public int TaskGroupId { get; set; }
public Tasks Tasks { get; set; }
public TaskGroups TaskGroups { get; set; }
DbContext
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<TaskGroupTask>(e =>
{
e.HasKey(p => new { p.TaskId, p.TaskGroupId });
e.HasOne(p => p.Tasks).WithMany(t =>
t.TaskGroupTask).HasForeignKey(p => p.TaskId);
e.HasOne(p => p.TaskGroups).WithMany(tg =>
tg.TaskGroupTask).HasForeignKey(p => p.TaskGroupId);
});
}
public DbSet<TaskGroupTask> TaskGroupTask { get; set; }
You will need to create a joining entity, like MyJoiningEntity or TaskGroupTask, whose sole purpose is to create a link between Task and TaskGroup. Following models should give you the idea -
public class Task
{
public int Id { get; set; }
public string Description { get; set; }
public IList<JoiningEntity> JoiningEntities { get; set; }
}
public class TaskGroup
{
public int Id { get; set; }
public string GroupName { get; set; }
public IList<JoiningEntity> JoiningEntities { get; set; }
}
// this is the Joining Entity that you need to create
public class JoiningEntity
{
public int TaskId { get; set; }
public int TaskGroupId { get; set; }
public Task Task { get; set; }
public TaskGroup TaskGroup { get; set; }
}
Then you can configure the relation in the OnModelCreating method of your DbContext class, like -
modelBuilder.Entity<JoiningEntity>(e =>
{
e.HasKey(p => new { p.TaskId, p.TaskGroupId });
e.HasOne(p => p.Task).WithMany(t => t.JoiningEntities).HasForeignKey(p => p.TaskId);
e.HasOne(p => p.TaskGroup).WithMany(tg => tg.JoiningEntities).HasForeignKey(p => p.TaskGroupId);
});
This will define a composite primary key on JoiningEntity table based on the TaskId and TaskGroupId properties. Since this table's sole purpose is to link two other tables, it doesn't actually need it's very own Id field for primary key.
Note: This approach is for EF versions less than 5.0. From EF 5.0 you can create a many-to-many relationship in a more transparent way.
Since I have some time, I've decided to pull all pices of the code in one place. I think it would be very usefull sample how to create code-first many-to-many relations for database tables. This code was tested in Visual Studio and a new database was created without any warnings:
public class Task
{
public Task()
{
TaskTaskGroups = new HashSet<TaskTaskGroup>();
}
[Key]
public int Id { get; set; }
public DateTime? CreatedAt { get; set; }
public DateTime? EndedAt { get; set; }
[Column(TypeName = "text")]
public string CreatedBy { get; set; }
[Required]
[Column(TypeName = "text")]
public string Title { get; set; }
[Required]
[Column(TypeName = "text")]
public string Description { get; set; }
public string Status { get; set; }
[Column(TypeName = "text")]
public string WantedUser { get; set; }
[InverseProperty(nameof(TaskTaskGroup.Task))]
public virtual ICollection<TaskTaskGroup> TaskTaskGroups { get; set; }
}
public class TaskGroup
{
public TaskGroup()
{
TaskTaskGroups = new HashSet<TaskTaskGroup>();
}
[Required]
public int Id { get; set; }
[Required]
public string GroupName { get; set; }
[InverseProperty(nameof(TaskTaskGroup.TaskGroup))]
public virtual ICollection<TaskTaskGroup> TaskTaskGroups { get; set; }
}
public class TaskTaskGroup
{
[Key]
public int Id { get; set; }
public int TaskId { get; set; }
[ForeignKey(nameof(TaskId))]
[InverseProperty(nameof(TaskTaskGroup.Task.TaskTaskGroups))]
public virtual Task Task { get; set; }
public int TaskGroupId { get; set; }
[ForeignKey(nameof(TaskGroupId))]
[InverseProperty(nameof(TaskTaskGroup.Task.TaskTaskGroups))]
public virtual TaskGroup TaskGroup { get; set; }
}
public class TaskDbContext : DbContext
{
public TaskDbContext()
{
}
public TaskDbContext(DbContextOptions<TaskDbContext> options)
: base(options)
{
}
public DbSet<Task> Tasks { get; set; }
public DbSet<TaskGroup> TaskGroups { get; set; }
public DbSet<TaskTaskGroup> TaskTaskGroups { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=localhost;Database=Task;Trusted_Connection=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TaskTaskGroup>(entity =>
{
entity.HasOne(d => d.Task)
.WithMany(p => p.TaskTaskGroups)
.HasForeignKey(d => d.TaskId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_TaskTaskGroup_Task");
entity.HasOne(d => d.TaskGroup)
.WithMany(p => p.TaskTaskGroups)
.HasForeignKey(d => d.TaskGroupId)
.HasConstraintName("FK_TaskTaskGroup_TaskCroup");
});
}
}
Related
Im having the problem that some of my objects aren't populated fully and I can't understand why this happens I tested with the following code:
[HttpGet]
[Route("Artikel/Test")]
public async Task<IActionResult> Test()
{
return Json(await db.Artikel_Meldungen
.Include(m => m.Meldung).ThenInclude(a => a.ErsatzArtikel)
.Where(x=>x.Artikel_MeldungId==1726)
.ToListAsync());
}
[HttpGet]
[Route("Artikel/Meldung")]
public async Task<IActionResult> GetMeldungen()
{
return Json((await db.Artikel_Meldungen
.Include(m => m.Meldung).ThenInclude(a => a.ErsatzArtikel)
.ToListAsync())
.Where(x => x.Artikel_MeldungId == 1726));
}
with the following classes:
[Table("Meldung", Schema = "BDB")]
public class Meldung
{
[Key]
public int Meldung_Id { get; set; }
public string Erfasser { get; set; }
public Artikel ErsatzArtikel { get; set; }
[NotMapped]
public Artikel Parent { get; set; }
public long ErsatzArtNr { get; set; }
[Column(TypeName = "nvarchar(2)")]
public string ErsatzAusfKz { get; set; }
public virtual ICollection<Artikel_Meldung> Artikel_Meldungen { get; set; }
public string Kommentar { get; set; }
public DateTime StartZeitpunkt { get; set; }
public DateTime EndeZeitpunkt { get; set; }
}
[Table("Artikel_Meldung", Schema = "BDB")]
public class Artikel_Meldung
{
[Key]
public int Artikel_MeldungId { get; set; }
public int Meldung_Id { get; set; }
public Meldung Meldung { get; set; }
public Artikel Artikel { get; set; }
public long ArtNr { get; set; }
[Column(TypeName = "nvarchar(2)")]
public string Ausfkz { get; set; }
}
[Table("Artikeldaten_PRE", Schema = "BDB")]
public class Artikel
{
[Key]
public long ArtNr { get; set; }
[Column(TypeName = "nvarchar(2)")]
public string AusfKz { get; set; }
public virtual ICollection<Artikel_Meldung> Artikel_Meldungen { get; set; }
}
As for my understanding both should return the same result. But for some reason the "Artikel/Meldung" endpoint isn't populating the ErsatzArtikel object. Am I doing something wrong or is this a bug?
Regards Martin
Adding AsNoTracking() AsSplitQuery() fixed the issue
I am new to Entity Framework Core 3.1 and trying to define the one-to-many relationship between two tables. I am currently struggling and getting compilation errors. Could somebody tell me what the problem could be.
The error is:
PersonNote does not contain the definition for PersonNote
I am currently getting is at line
entity.HasOne(d => d.PersonNote)
How else could I define one-to-many relationship?
The two tables are Person and PersonNote. One Person can have many PersonNotes. I have defined the models for them
public class Person
{
public int Id { get; set; }
public int? TitleId { get; set; }
public string FirstName { get; set; }
public string FirstNamePref { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public DateTime? DateOfBirth { get; set; }
public string Gender { get; set; }
public int AddressId { get; set; }
public string TelephoneNumber { get; set; }
public string MobileNumber { get; set; }
public string Email { get; set; }
public int? PartnerId { get; set; }
public bool Enabled { get; set; }
public string CreatedBy { get; set; }
public DateTime Created { get; set; }
public string ModifiedBy { get; set; }
public DateTime Modified { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordStartDateTime { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordEndDateTime { get; set; }
public Address Address { get; set; }
public Title Title { get; set; }
public Client Client { get; set; }
internal static IEnumerable<object> Include(Func<object, object> p)
{
throw new NotImplementedException();
}
public PersonNote PersonNote { get; set; }
}
public class PersonNote
{
public int Id { get; set; }
public int PersonId { get; set; }
public string Note { get; set; }
public int AuthorId { get; set; }
public string CreatedBy { get; set; }
public DateTime Created { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordStartDateTime { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime RecordEndDateTime { get; set; }
}
public IEnumerable<PersonNote> GetPersonNotes(int personId)
{
var PersonNotes = PersonNote
.Include(x => x.)
.Where(x => x.Id == personId)
.ToList();
return PersonNotes;
}
I have tried the following in OnModelCreating:
modelBuilder.Entity<PersonNote>(entity =>
{
entity.ToTable("PersonNote", "common");
entity.HasOne(d => d.PersonNote)
.WithMany(p => p.Person)
.HasForeignKey(d => d.PersonId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_commonPersonNote_commonPerson");
});
You should have have something like this (other properties are omitted):
class Person
{
[Key]
public int Id { get; set; }
public List<PersonNote> PersonNotes { get; set; }
}
class PersonNote
{
[Key]
public int Id { get; set; }
public int PersonId { get; set; }
}
class StackOverflow : DbContext
{
public DbSet<Person> Persons { get; set; }
public DbSet<PersonNote> PersonNotes { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasMany(p => p.PersonNotes)
.WithOne()
.HasForeignKey(p => p.PersonId);
}
}
I'm getting this error when trying to update relationship (one to one)
with fluent api this are my classes :
public class Organisation
{
[Key]
public int OrganisationId { get; set; }
public string organisationName { get; set; }
public string FirstName { get; internal set; }
public string LastName { get; internal set; }
public virtual ApplicationUser User { get; set; }
[Required]
public string ApplicationUserId { get; set; }
public int? OrganisationDetalisId { get; set; }
public virtual OrganisationDetalis OrDetalis { get; set; }
public virtual ICollection<Aeroa> aeroa { get; set; }
public virtual ICollection<Order> orders { get; set; }
}
public class OrganisationDetalis
{
[Key]
public int OrganisationDetalisId { get; set; }
//remove for clear code
public int OrganisationId { get; set; }
public virtual Organisation organisation { get; set; }
}
public DbSet<Organisation> organisation { get; set; }
public DbSet<OrganisationDetalis> OrDetalis { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<OrganisationDetalis>()
.HasKey(o => o.OrganisationId);
modelBuilder.Entity<Organisation>()
.HasOptional(ad => ad.OrDetalis).WithRequired(oo => oo.organisation);
}
but I keep getting that error when updating view nugget console
where is the problem?
When calling the method directly below I get a ObjectDisposedException when calling Mapper.Map with the retrieved list.
System.ObjectDisposedException: The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
public IEnumerable<Models.Provider> Get(string owner)
{
List<Data.Models.Provider> providers;
using (var db = new Data.ProviderDirectoryContext())
{
providers = db.Providers.Where(p => p.Owner.Name == owner).ToList();
}
var dtoProviders = Mapper.Map<List<Data.Models.Provider>, List<Models.Provider>>(providers);
return dtoProviders;
}
I previously had the code like this (below), I wasn't getting an error, but the database was getting pounded when doing the mapping, and it was taking too long. I don't want to hit the database, when doing the mapping.
public IEnumerable<Models.Provider> Get(string owner)
{
using (var db = new Data.ProviderDirectoryContext())
{
var providers = db.Providers.Where(p => p.Owner.Name == owner).ToList();
var dtoProviders = Mapper.Map<List<Data.Models.Provider>, List<Models.Provider>>(providers);
return dtoProviders;
}
}
How can I retrieve all the data before doing the mapping?
Here is the DbContext and the Data.Models.Provider for your reference.
public class ProviderDirectoryContext : DbContext
{
public DbSet<Owner> Owners { get; set; }
public DbSet<Location> Locations { get; set; }
public DbSet<LocationAuditLog> LocationAuditLog { get; set; }
public DbSet<Office> Offices { get; set; }
public DbSet<OfficePhoneNumber> OfficePhoneNumbers { get; set; }
public DbSet<OfficeAuditLog> OfficeAuditLog { get; set; }
public DbSet<OfficeDay> OfficeDays { get; set; }
public DbSet<Provider> Providers { get; set; }
public DbSet<ProviderPhoneNumber> ProviderPhoneNumbers { get; set; }
public DbSet<ProviderAuditLog> ProviderAuditLog { get; set; }
public DbSet<ProviderType> ProviderTypes { get; set; }
public DbSet<ProviderSpecialty> ProviderSpecialties { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Provider>().HasRequired(cn => cn.Owner).WithMany().WillCascadeOnDelete(false);
modelBuilder.Entity<Office>().HasRequired(cn => cn.Owner).WithMany().WillCascadeOnDelete(false);
}
}
public class Provider
{
public int Id { get; set; }
public int OwnerId { get; set; }
public virtual Owner Owner { get; set; }
public int? ProviderTypeId { get; set; }
public virtual ProviderType ProviderType { get; set; }
public int? ProviderSpecialtyId { get; set; }
public virtual ProviderSpecialty ProviderSpecialty { get; set; }
[Required]
[StringLength(75)]
public string FirstName { get; set; }
[StringLength(75)]
public string MiddleName { get; set; }
[Required]
[StringLength(75)]
public string LastName { get; set; }
[StringLength(100)]
public string EmailAddress { get; set; }
public virtual ICollection<ProviderPhoneNumber> PhoneNumbers { get; set; }
public string Note { get; set; }
public DateTime? InactiveOn { get; set; }
public int OfficeId { get; set; }
public virtual Office Office { get; set; }
public virtual ICollection<ProviderAuditLog> AuditLog { get; set; }
[Required]
public DateTime CreatedOn { get; set; }
[Required]
[StringLength(75)]
public string CreatedBy { get; set; }
[Required]
public DateTime ModifiedOn { get; set; }
[Required]
[StringLength(75)]
public string ModifiedBy { get; set; }
}
Thanks for the help!
The problem is that the Models.Provider class contains other classes like Models.Office, and Models.PhoneNumbers that were not eagerly loaded by the query. In addition to that, the Models.Provider class needs to be flattened. The Mapper wants to recursively map everything, and it keeps going down to the next class. For example, Provider.Office.Location.Offices.
The solution is to flatten Models.Provider and add .Include() to the query so it eagerly loads the data required.
I'll clean this up a bit more, but this is currently working.
public IEnumerable<Models.Provider> Get(string owner)
{
List<Data.Models.Provider> providers;
using (var db = new Data.ProviderDirectoryContext())
{
providers = db.Providers
.Where(p => p.Owner.Name == owner)
.Include("ProviderType")
.Include("ProviderSpecialty")
.Include("Office")
.Include("PhoneNumbers")
.ToList();
}
var dtoProviders = Mapper.Map<List<Data.Models.Provider>, List<Models.Provider>>(providers);
return dtoProviders;
}
public class Provider
{
public int Id { get; set; }
public int OwnerId { get; set; }
public int OfficeId { get; set; }
public string OfficeName { get; set; }
public int? ProviderTypeId { get; set; }
public string ProviderTypeName { get; set; }
public int? ProviderSpecialtyId { get; set; }
public string ProviderSpecialtyName { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public string EmailAddress { get; set; }
public virtual ICollection<PhoneNumber> PhoneNumbers { get; set; }
public string Note { get; set; }
public DateTime? InactiveOn { get; set; }
public DateTime CreatedOn { get; set; }
public string CreatedBy { get; set; }
public DateTime ModifiedOn { get; set; }
public string ModifiedBy { get; set; }
}
I am not sure how much this will help with performance but declaring the variable you don't want to dispose outside the using statement should fix your dispose exception.
public IEnumerable<Models.Provider> Get(string owner)
{
IEnumerable<Models.Provider> dtoProviders;
using (var db = new Data.ProviderDirectoryContext())
{
List<Data.Models.Provider> providers = db.Providers.Where(p => p.Owner.Name == owner).ToList();
dtoProviders = Mapper.Map<List<Data.Models.Provider>, List<Models.Provider>>(providers);
}
return dtoProviders;
}
I have a composite table called ImporterState, that are tied to a table called Importer and State. The error happens here context.Importers.Include(q => q.States). Why is this happening?
{"Invalid object name 'ImporterStates'."}
[Table("HeadlineWebsiteImport", Schema = "GrassrootsHoops")]
public class Importer
{
public int Id { get; set; }
public string Name { get; set; }
public string RssUrl { get; set; }
public string Type { get; set; }
public string Keywords { get; set; }
public bool Active { get; set; }
public DateTime DateModified { get; set; }
public DateTime DateCreated { get; set; }
public int WebsiteId { get; set; }
public HeadlineWebsite Website { get; set; }
[InverseProperty("Importers")]
public ICollection<State> States { get; set; }
}
[Table("State", Schema = "GrassrootsHoops")]
public class State
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Abbr { get; set; }
[InverseProperty("States")]
public ICollection<Headline> Headlines { get; set; }
[InverseProperty("States")]
public ICollection<Importer> Importers { get; set; }
}
The many to many is not possible using attributes only.
try using something like:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Importer>()
.HasMany(i => i.States)
.WithMany(s => s.Importers)
.Map(m =>
{
m.MapLeftKey("ImporterId");
m.MapRightKey("StateId");
m.ToTable("ImporterState");
});
}