Many-to-many table as model with extra column - c#

I have a code-first MVC4 C# application with these two models
public class ClassRoom {
public int ID { get; set; }
public string ClassName { get; set; }
public virtual ICollection<Pupil> Pupils { get; set; }
}
public class Pupil {
public int ID { get; set; }
public string PupilName { get; set; }
public virtual ICollection<ClassRoom> ClassRooms { get; set; }
}
This is a part of the context
modelBuilder.Entity<Location>()
.HasMany(l => l.ClassRooms)
.WithMany(o => o.Pupils)
.Map(m => m.MapLeftKey("PupilID")
.MapRightKey("ClassRoomID")
.ToTable("PupilClassRoomMM"));
Therefore there is a many-to-many table called "PupilClassRoomMM".
This all worked fine, but now I want to add a column to the PupilClassRoomMM-table.
I tried to make this Model, but the Add-Migration doesn't get it.
public class PupilClassRoomMM
{
[Key, Column(Order = 0)]
public int ClassRoomID { get; set; }
[Key, Column(Order = 1)]
public int PupilID { get; set; }
public bool IsPrimary { get; set; }
}
The error is:
"PupilClassRoomMM: Name: The EntitySet 'PupilClassRoomMM' with schema 'dbo' and
table 'PupilClassRoomMM' was already defined.
Each EntitySet must refer to a unique schema and table."
How can I add a field to an already existing many-to-many table (I don't want to lose its data).

Related

Load related data without foreign key constraint in ef-core-2.1

I want to load related entities data Parent by using Eager Loading O/RM pattern. But I can't specify a foregin key constraint on ParentId because it creates a cycle which is not allowed. Currently, I'm using an inner join to load Parent data explicitly.
Here is my Domain Model that I'm using.
[Table("Category")]
public class CategoryDM
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int CategoryId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
[Display(Name="Parent")]
public int ParentId { get; set; }
[NotMapped]
public CategoryDM Parent { get; set; }
}
Is there any way to load related entities like this? or any other recommended way to achieve this.
var result = _context.Category.Include(e => e.Parent);
This should work fine, here is an exemplary working model.
Model
public class Category : ISelfRelated<Category>
{
public int Id { get; set; }
public string Name { get; set; }
public string ThumbnailUrl { get; set; }
public int? ParentId { get; set; }
public Category Parent { get; set; }
public IEnumerable<Category> Children { get; set; }
}
Model configuration
category.HasOne(c => c.Parent)
.WithMany(c => c.Children)
.HasForeignKey(c => c.ParentId)
.HasPrincipalKey(c => c.Id)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired(false);

How to bridge a table on itself using EF Core Code-First

This is a operation i have done many times in the past using database-first approach. I'm now trying it with code-first using EF Core and i'm failing horribly.
I have the following model:
public class DataMapping
{
[Key]
[Column(Order = 1)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public string Model { get; set; }
public string Property { get; set; }
public bool IgnoreProperty { get; set; }
[NotMapped] //<-- I had to add this as the migration was complaining that it did not know what the relation was
public List<DataMappingRelation> DataMappingRelations { get; set; }
public DateTime DateCreated { get; set; } = DateTime.UtcNow;
public DateTime? DateModified { get; set; }
}
and a Bridge model that basically creates a relations between two DataMapping items in the same table:
public class DataMappingRelation
{
[Key]
[Column(Order = 1)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
[ForeignKey("DataMappingId")]
public long? DataMapping1Id { get; set; }
public DataMapping DataMapping1 { get; set; }
[ForeignKey("DataMappingId")]
public long? DataMapping2Id { get; set; }
public DataMapping DataMapping2 { get; set; }
}
However this call does not work:
return _context.DataMappings.Where(x => x.Model == type.FullName)
.Include(x=>x.DataMappingRelations)
.ToList();
It does not like the Include and throws a null ref exception.
All i basically need to do here is for a given "DataMapping" get all the related DataMapping items based on the relations in the "DataMappingRelations" table.
Yes i have looked at this answer but again, it is an example of two seperate tables, not a single table bridging on itself.
I suspect i have done all of this wrong. How can i get this to work? All the examples i have found are bridging two seperate tables. this would be bridging the same table.
Its many-to-many with self but your whole configuration looks messy.
So first, your DataMapping model class should contain two list navigation properties for two foreign keys in the DataMappingRelation as follows:
public class DataMapping
{
......
public List<DataMappingRelation> DataMapping1Relations { get; set; }
public List<DataMappingRelation> DataMapping2Relations { get; set; }
.........
}
Now remove [ForeignKey("DataMappingId")] attribute from both DataMapping1 and DataMapping2 foreign keys as follows:
public class DataMappingRelation
{
[Key]
[Column(Order = 1)]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long Id { get; set; }
public long? DataMapping1Id { get; set; }
public DataMapping DataMapping1 { get; set; }
public long? DataMapping2Id { get; set; }
public DataMapping DataMapping2 { get; set; }
}
Then the Fluent API configuration should be as follows:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<DataMappingRelation>()
.HasOne(dmr => dmr.DataMapping1)
.WithMany(dm => dm.DataMapping1Relations)
.HasForeignKey(dmr => dmr.DataMapping1Id)
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<DataMappingRelation>()
.HasOne(dmr => dmr.DataMapping2)
.WithMany(dm => dm.DataMapping2Relations)
.HasForeignKey(dmr => dmr.DataMapping2Id)
.OnDelete(DeleteBehavior.Restrict);
}

Mapping one to many relationship to existing database views without keys

I need to create one to many relationship between two classes.Database do not contains primary or foreign keys. So i decided to create 2 views instead of 3 tables, and views also has no primary/foreign key (Changing the database schema is not allowed)
Views
[Table("Barcode")]
public partial class Barcode
{
[Column("_Barcode")]
public string _Barcode { get; set; }
[Column("_SkuRef")]
[MaxLength(16)]
public byte[] _SkuRef { get; set; }
[Key]
[Column("Id")]
public long Id { get; set; }
public string _SkuId { get; set; }
}
[Table("SKU")]
public partial class SKU
{
[Column("_Code", Order = 0)]
[StringLength(11)]
public string _Code { get; set; }
[Column("_Description", Order = 1)]
[StringLength(150)]
public string _Description { get; set; }
[Column("_ProductId", Order = 2)]
[StringLength(50)]
public string _ProductId { get; set; }
[Column("_Ref", Order = 3)]
[MaxLength(16)]
public byte[] _Ref { get; set; }
[Key]
[Column(Order = 4)]
[StringLength(36)]
public string Id { get; set; }
}
In this way, i tryed to use something like
using (SkuContext db = new SkuContext())
{
var Skutable = db.SKUs;
foreach(var s in Skutable)
{
Console.WriteLine(s.Id);
}
}
And its working well. But when im trying to add
public partial class Barcode
{
....
[ForeignKey("_SkuId")]
[Column("_SkuId")]
[StringLength(36)]
public string _SkuId { get; set; }
}
public partial class SKU
{
....
public IEnumerable<Barcode> Barcodes;
}
I'm facing
"The property '_SkuId' cannot be configured as a navigation property" error.
And my question is how to do the right thing? How to create relationship between two classes like these ?
I think you can create a one-to-many relationship between your SKU and Barcode views where one SKU has many Barcodes in the same way you would do it if it were tables. Use Fluent API to configure a one-to-many relationship between SKU and Barcode:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// one-to-many relationship
// _SkuId foreign key in Barcode
modelBuilder.Entity<Barcode>()
.HasRequired(c => c.SKU)
.WithMany(t => t.Barcodes)
.Map(m => m.MapKey("_SkuId"));
}
Update your classes like the following:
[Table("Barcode")]
public partial class Barcode
{
[Column("_Barcode")]
public string _Barcode { get; set; }
[Column("_SkuRef")]
[MaxLength(16)]
public byte[] _SkuRef { get; set; }
[Key]
[Column("Id")]
public long Id { get; set; }
// need to comment this out
//[Column("_SkuId")]
//[StringLength(36)]
//public string _SkuId { get; set; }
public virtual SKU SKU { get; set; }
}
[Table("SKU")]
public partial class SKU
{
[Column("_Code", Order = 0)]
[StringLength(11)]
public string _Code { get; set; }
[Column("_Description", Order = 1)]
[StringLength(150)]
public string _Description { get; set; }
[Column("_ProductId", Order = 2)]
[StringLength(50)]
public string _ProductId { get; set; }
[Column("_Ref", Order = 3)]
[MaxLength(16)]
public byte[] _Ref { get; set; }
[Key]
[Column(Order = 4)]
[StringLength(36)]
public string Id { get; set; }
public virtual ICollection<Barcode> Barcodes { get; set; }
}
You got error because your table does not contains foreign key. The foreign key attribute will not help cause you don't have such relationship in database.
If you are using ORM, such as Entity Framework, the solution will be to write a stored procedure and to make EF to work with it.
Another way is to implement your own business logic using classes from System.Data.SqlClient by specific reading, writing, etc..
So I consider that such an exercise does not needed in your simple situation. But if you are going just in this straight way, without changing tables, be ready for tons of documentation.

The navigation on entity type has not been added to the model, or ignored, or entityType ignored

The navigation 'Tags' on entity type 'Notepad.Models.Note' has not been added to the model, or ignored, or entityType ignored.
public class Note
{
public Note()
{
CreationDate = DateTime.Now;
Tags = new HashSet<Tag>();
Parts = new HashSet<Part>();
}
public int ID { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
public virtual ICollection<Part> Parts { get; set; }
public DateTime? CreationDate { get; set; }
}
public class Tag
{
public Tag()
{
Notes = new HashSet<Note>();
}
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Note> Notes { get; set; }
}
It happens while adding a migration:
dnx ef migrations add DbData -c DataDbContext
Why do you think it happens?
EDIT:
DataDbContext:
public class DataDbContext : DbContext
{
public DbSet<Note> Notes { get; set; }
public DbSet<Tag> Tags { get; set; }
public DbSet<Part> Parts { get; set; }
}
You have Many-to-many relationship there. As the documentation says: http://docs.efproject.net/en/latest/modeling/relationships.html#id21
Many-to-many relationships without an entity class to represent the join table are not yet supported. However, you can represent a many-to-many relationship by including an entity class for the join table and mapping two separate one-to-many relationships.
So you must create additional "join" class like this:
public class NoteTag
{
public int NoteId { get; set; }
public Note Note { get; set; }
public int TagId { get; set; }
public Tag Tag { get; set; }
}
then, replace
ICollection<Tag> Tags {set;get}
in your Note class to
ICollection<NoteTag> NoteTags {set;get}
and also in Tag class:
ICollection<Note> Notes {set;get;}
to
ICollection<NoteTags> NoteTags {set;get}
and then override OnModelCreating method in DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<NoteTag>()
.HasKey(t => new { t.NoteId, t.TagId });
modelBuilder.Entity<NoteTag>()
.HasOne(pt => pt.Note)
.WithMany(p => p.NoteTags)
.HasForeignKey(pt => pt.NoteId);
modelBuilder.Entity<NoteTag>()
.HasOne(pt => pt.Tag)
.WithMany(t => t.NoteTags)
.HasForeignKey(pt => pt.TagId);
}
I am using EF 7, this problem took around 2 hours of my week end. :)
So, here is the simple solution -
I am having a profile class like this -
[Table("Profile")]
public class Profile
{
public Profile()
{
}
[Column(Order = 1)]
[Key]
public Guid ProfileID { get; set; }
[JsonIgnore]
public virtual ICollection<StudentLivingWith> StudentProfileMap { get; set; }
[JsonIgnore]
public virtual ICollection<StudentLivingWith> ParentProfileMap { get; set; }
}
I am using the ProfileID as a F-Key reference in my another table named "StudentLivingWith". (ya, I know the name is bit strange. :)) As you can see in below class, both the columns "StudentProfileID" and "ParentProfileID" refering to the same column "profileID" of my "Profile" table.
[Table("StudentLivingWith")]
public class StudentLivingWith
{
public StudentLivingWith()
{
}
[Column(Order = 1)]
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int StudentLivingWithID { get; set; }
[Column(Order = 2)]
[ForeignKey("StudentProfileID")]
public Guid StudentProfileID { get; set; }
[Column(Order = 3)]
[ForeignKey("ParentProfileID")]
public Guid ParentProfileID { get; set; }
[JsonIgnore]
[InverseProperty("StudentProfileMap")]
public virtual ICollection<Profile> StudentProfile { get; set; }
[JsonIgnore]
[InverseProperty("ParentProfileMap")]
public virtual ICollection<Profile> ParentProfile { get; set; }
}
So the conclusion is - you just need to add [InverseProperty] tag on the reference, and this simple solution did the trick for me.
I hope this will help. Thanks.

Multiple one-to-many relationships with entity framework code first

I am trying to form a relationship of 2 tables to a 3rd, on a 1 to many basis. I have the following code:
public class CompanyInvolvement
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
public DateTime StartDate { get; set; }
public DateTime? EndDate { get; set; }
public virtual Person Person { get; set; }
public virtual Company Company { get; set; }
}
public class Person
{
public Person()
{
CompanyInvolvements = new Collection<CompanyInvolvement>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Required]
public string ClientIdReference { get; set; }
public virtual ICollection<CompanyInvolvement> CompanyInvolvements { get; set; }
}
public class Company
{
public Company()
{
Involvements = new Collection<CompanyInvolvement>();
}
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid Id { get; set; }
[Required]
public string ClientIdReference { get; set; }
[Required]
public string CompanyName { get; set; }
public virtual ICollection<CompanyInvolvement> Involvements { get; set; }
}
So effectively a Person can have many involvements in companies and a Company can have many people involved with it. The model builder is used like so:
modelBuilder.Entity<CompanyInvolvement>().HasRequired(x => x.Person).WithMany(x => x.CompanyInvolvements);
modelBuilder.Entity<CompanyInvolvement>().HasRequired(x => x.Company).WithMany(x => x.Involvements);
I originally created the relationship using the modelbuilder, specifying left and right keys (CompanyId and PersonId) and this worked great. But now I need the Start and End dates for an Involvement, I guess I needed to create a dedicated entity.
The question: When I use the above structure, I can create and read out involvements for a company and also see involvements for a Person. However, when I try to do the following:
var person = _context.People.FirstOrDefault(x => x.Id == personId);
var involvement = company.Involvements.FirstOrDefault(x => x.Person == person );
company.Involvements.Remove(involvement);
_context.SaveChanges();
I get the following error:
A relationship from the 'CompanyInvolvement_Company' AssociationSet is
in the 'Deleted' state. Given multiplicity constraints, a
corresponding 'CompanyInvolvement_Company_Source' must also in the
'Deleted' state.
I think my virtual properties in the 3 entities are correct, but I have the feeling the modelbuilder logic I have may be slightly misconfigured?
I finally figured out what I was doing wrong. I needed to remove the Id property from the CompanyInvolvement entity and add the following composite key:
[Key, Column(Order = 0)]
public Guid PersonId { get; set; }
[Key, Column(Order = 1)]
public Guid CompanyId { get; set; }
I'm guessing by convention, these two properties were then linked as foreign keys to the Person and Company entities respectively. I also removed the modelbuilder mapping as stated in my original question. Once these were done, deleting CompanyInvolvements worked as expected.

Categories

Resources