EF5 Code first - Many to Many with extra fields - c#

I'm trying to use the awsome EF5 with code first - where I need to make a many-to-many table with extra fields.
I've got a products table, orders table and need a table of products that are in orders with a "size" field.
What I've done is created a new class of "ProductOrder" that is the connection table between them, and made a reference.
It WORKS when creating a new order, but is not working when fetching an order - it doesn't get the connected orders (that are present in the DB after the insertion).
Ideas why? :)
My Classes are:
public class Order
{
public int ID { get; set; }
...
public ICollection<ProductOrder> Products { get; set; }
public Order()
{
Products = new HashSet<ProductOrder>();
}
}
public class Product
{
public int ID { get; set; }
public ICollection<ProductOrder> Orders { get; set; }
}
public class ProductOrder
{
public int ID { get; set; }
public int ProductID { get; set; }
public int OrderID { get; set; }
public int Size { get; set; }
[ForeignKey("OrderID")]
public Order order { get; set; }
[ForeignKey("ProductID")]
public Product product { get; set; }
}
and in onModelCreating
modelBuilder.Entity<Order>()
.HasMany(p => p.Products)
.WithRequired(o => o.order)
.HasForeignKey(o => o.OrderID);
modelBuilder.Entity<Product>()
.HasMany(o => o.Orders)
.WithRequired(p => p.product)
.HasForeignKey(p => p.ProductID);

Your navigational properties need to be virtual

From what I've seen, marking the navigational properties as virtual turns lazy loading on for those properties.
The other way to get this to work without lazy loading is to add .include() statements to your select. This tells EF to pull back the extra data that you want.

Related

Mapping Table in DB first without foreign key

I have a program written with a database-first approach; I have a table ServicePlan and another ServicePlanDetails. They are not mapped to each other, but they have a common column PlanId; a servicePlan can contain multiple ServicePlanDetails like a list of it.
I don't want to make any change to the database, but I want to map them as well. How can I do this? Does doing this within the method of on model creating will do the work for me and will not change anything in the database? I have tried this but could get the result.
For simplicity, I have just added few columns and their mapping and not all of them:
public partial class ServicePlan
{
public ServicePlan()
{
ServicePlanDetails = new HashSet<ServicePlanDetail>();
}
public long PlanId { get; set; }
public decimal PhoneId { get; set; }
public byte? NLines { get; set; }
public DateTime? DateStart { get; set; }
public DateTime? DateEnd { get; set; }
public virtual ICollection<ServicePlanDetail> ServicePlanDetails { get; set; }
}
public partial class ServicePlanDetail
{
public long PlanId { get; set; }
public string? ServCode { get; set; }
public string? CountryCode { get; set; }
public bool? IsPlan { get; set; }
public decimal? Cost { get; set; }
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ServicePlan>(entity =>
{
entity.HasKey(e => e.PlanId).HasName("PK_UsersPlan");
entity.ToTable("ServicePlan");
entity.HasIndex(e => e.VideoTronId, "IDX_VTID").HasFillFactor(80);
entity.HasIndex(e => new { e.PhoneId, e.IsApproved }, "Ix_SrvcPlan").HasFillFactor(80);
entity.Property(e => e.Zone).HasMaxLength(50);
entity.HasMany(p => p.ServicePlanDetails)
.WithOne()
.HasPrincipalKey(p => p.PlanId)
.HasForeignKey(p => p.PlanId);
});
}
The error I get is :
Unable to determine the relationship represented by navigation 'ServicePlan.ServicePlanDetails' of type 'ICollection'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.'
I want to get the serviceplandetails with the same planid as the serviceplan into a list in the serviceplan.
PlanId cannot be both foreign and principal key for one to many.
public partial class ServicePlanDetail
{
public long Id { get; set; }
public long PlanId { get; set; }
public string? ServCode { get; set; }
public string? CountryCode { get; set; }
public bool? IsPlan { get; set; }
public decimal? Cost { get; set; }
}
Configuration
entity.HasMany(p => p.ServicePlanDetails)
.WithOne()
.HasPrincipalKey(p => p.PlanId)
.HasForeignKey(p => p.PlanId);
If in the database a Plan can have many ServicePlanDetails, and you link them by Plan ID, how do you differentiate one ServicePlanDetail against that Plan from another? What makes two ServicePlanDetail records unique? That is the crux of your problem. Your FK mapping is correct, but it won't work if PlanId is the PK on ServicePlanDetail. PKs must uniquely identify a single record. For instance if your plan is associated to service plan details applying to various users where multiple users reference the same plan and there is a UserID on ServicePlanId, the PK should be a composite of PlanId + UserId.
As a DB-First approach the database should already have the PKs and constraints set up. You just set up EF keys and relationship types to match that.
Now if the ServicePlanDetail's PK is declared as just PlanId, then the answer is that the relationship between Plan and ServicePlanDetail is 1-to-1, not 1-to-many. This becomes a .HasOne(p => p.ServicePlanDetail).WithOne(sp => sp.Plan) and there's really nothing you can do about that without altering the data relationships. You cannot magically change the relationship that EF will use if the underlying database schema cannot support that relationship.

Many to many relationship issue with column name - EF Core

I have a problem with many to many relationship in EF core. I have the following models:
public class Institution
{
[Key]
public int Id { get; set; }
[JsonIgnore]
public virtual ICollection<InstitutionDepartment> InstitutionDepartments { get; set; }
}
public class InstitutionDepartment
{
[Column("Institution_Id")]
public int InstitutionId { get; set; }
[Column("Department_Id")]
public int DepartmentId { get; set; }
public Institution Institution { get; set; }
public Departments Department { get; set; }
}
public class Departments
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public bool Published { get; set; }
[JsonIgnore]
public virtual ICollection<InstitutionDepartment> InstitutionDepartments { get; set; }
}
I followed the many tutorials explaining how to correctly map these classes:
modelBuilder.Entity<InstitutionDepartment>()
.HasKey(x => new { x.DepartmentId, x.InstitutionId});
modelBuilder.Entity<InstitutionDepartment>()
.HasOne(pt => pt.Institution)
.WithMany(p => p.InstitutionDepartments)
.HasForeignKey(pt => pt.InstitutionId);
modelBuilder.Entity<InstitutionDepartment>()
.HasOne(pt => pt.Department)
.WithMany(t => t.InstitutionDepartments)
.HasForeignKey(pt => pt.DepartmentId);
I wrote my query:
var institutions = _context.Institutions
.Include(i => i.InstitutionDepartments)
.ThenInclude(id => id.Department);
But no matter what I do, I get the following error:
Invalid column name 'InstitutionId'.
Can anyone tell me what I'm doing wrong here ? D:
Note I don't get the error if I don't write the .ThenInclude(id => id.Department); part.
But that make the data incomplete
The issue came from a line of code that I didn't deem relevant at the time (I'll know better next time)
This was in the Institution model without the [NotMapped] annotation:
[NotMapped]
public IEnumerable<Departments> Departments
=> InstitutionDepartments?.Select(o => o.Department);
This was causing EF to look for a missing One to Many relationship between Institution and Department

Entity Framework 1 to Many creates new objects

I'm using EntityFramework for my Microsoft Sql Data Base.
First entity is Product:
public class Product
{
public Product()
{
ProductStories = new HashSet<ProductStory>();
}
public int ProductId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public bool Deleted { get; set; }
public HashSet<ProductStory> ProductStories { get; set; }
}
And another entity is ProductStory, which stores story about income or outcome of Products.
public class ProductStory
{
public int ProductStoryId { get; set; }
public virtual Product.Product Product { get; set; }
public int Count { get; set; }
public DateTime DateTime { get; set; }
}
So one Product could be in mane ProductStories, or in none.
I will not show all code(too big), so when I firstly create a single Product instance and save it in DB. Then I create a single ProductStory and reference to property Product to that instance of Product.
Then I save this ProductStory, there becomes 2 instances of ProductStory.
As I read, and I made this as virtual property:
public virtual Product.Product Product { get; set; }
How this problem could be solved?
I'm using EntityTypeConfiguration for tables configuration.
public class ProductMap : EntityTypeConfiguration<Product>
{
public ProductMap()
{
ToTable("Products").HasKey(x => x.ProductId);
Property(x => x.ProductId).IsRequired();
Property(x => x.Name).IsRequired().HasMaxLength(255).HasColumnName("Name");
//.HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute("IX_Name") { IsUnique = true }));
Property(x => x.Description).IsOptional().HasColumnName("Description");
Property(x => x.Deleted).HasColumnName("Deleted");
}
}
And for ProductStory:
class ProductStoryMap: EntityTypeConfiguration<ProductStory>
{
public ProductStoryMap()
{
ToTable("ProductStories").HasKey(ps => ps.ProductStoryId);
Property(ps => ps.ProductStoryId).IsRequired();
//Property(ps => ps.ProductId).IsRequired().HasColumnName("ProductId");
Property(ps => ps.Count).HasColumnName("Count");
Property(ps => ps.DateTime).HasColumnName("DateTime");
}
}
You have some errors in your code:
//Change this:
public HashSet<ProductStory> ProductStories { get; set; }
//For this (virtual is needed here, also use ICollection rather than any specific implementation)
public virtual ICollection<ProductStory> ProductStories { get; set; }
//Change this:
public virtual Product.Product Product { get; set; }
//For this (virtual makes no sense here)
public Product.Product Product { get; set; }
And lastly, ProductStory needs a way to keep the reference to its parent Product. This is what creates the Foreign Key relationship in your database and allows Entity Framework to link the tables. So add this to ProductStory:
public int ProductId { get; set; }
If you are still getting a duplicated object (which may happen), ensure you are setting the ProductId to the ProductStory you are saving.
The solution was about Entity Framework "bug/feature".
As I add new ProductStory into DataBase, it attaches the whole graph(including all other entities references and recreates them).
So before commiting new ProductStory, I have to set to null all it's navigation properties to avoid recreating.

NHibernate Query Over to sort IList in Entity

I have two entities
public class ProductEntity
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<PriceScheduleEntity> PriceSchedules { get; set; }
}
public class PriceScheduleEntity
{
public virtual Guid Id { get; set; }
public virtual DateTime Date{get;set;}
public virtual ProductEntity Product { get; set; }
}
One to Many mapping have been done.
One product may have many priceschedules
When i do the query
IList<ProductEntity> entityList = NHSession.QueryOver<ProductEntity>()
.Where(x => x.Name.IsLike("%" + matchString + "%"))
.OrderBy(x => x.Name).Asc.List();
It gives me the priceschedules as they were added.
I want them sorted through the effective date.
Please help me with the query.
in mapping (e.g. FLuentNHibernate mapping)
HasMany(x => x.PriceSchedules).OrderBy(s => s.Date)
you should do an ordered insert in code so it won't break this sorted contract after adding an item in code.

entity framework - many to many relationship

Hi I try use Many to Many relationship with EF Fluent API. I have 2 POCO classes.
public class Project
{
public int ProjectId { get; set; }
public virtual ICollection<Author> Authors { get; set; }
public Project()
{
Authors = new List<Author>();
}
}
public class Author
{
public int AuthorId { get; set; }
public virtual ICollection<Project> Projects { get; set; }
public Author()
{
Projects = new List<Project>();
}
}
And I map many to many relationship with this part of code:
////MANY TO MANY
modelBuilder.Entity<Project>()
.HasMany<Author>(a => a.Authors)
.WithMany(p => p.Projects)
.Map(m =>
{
m.ToTable("ProjectAuthors");
m.MapLeftKey("ProjectId");
m.MapRightKey("AuthorId");
});
This created table ProjectsAuthors in DB. It is my first attempt with this case of relationship mapping.
If I omitted this mapping it created table AuthorProject with similar schema. It is correct bevahior?
By trial and error I found the following. Given two classes...
public class AClass
{
public int Id { get; set; }
public ICollection<BClass> BClasses { get; set; }
}
public class BClass
{
public int Id { get; set; }
public ICollection<AClass> AClasses { get; set; }
}
...and no Fluent mapping and a DbContext like this...
public class MyContext : DbContext
{
public DbSet<AClass> AClasses { get; set; }
public DbSet<BClass> BClasses { get; set; }
}
...the name of the created join table is BClassAClasses. If I change the order of the sets...
public class MyContext : DbContext
{
public DbSet<BClass> BClasses { get; set; }
public DbSet<AClass> AClasses { get; set; }
}
...the name of the created join table changes to AClassBClasses and the order of the key columns in the table changes as well. So, the name of the join table and the order of the key columns seems to depend on the order in which the entity classes are "loaded" into the model - which can be the order of the DbSet declarations or another order if more relationship are involved - for example some other entity refering to AClass.
In the end, it doesn't matter at all, because such a many-to-many relationship is "symmetric". If you want to have your own name of the join table, you can specify it in Fluent API as you already did.
So, to your question: Yes, naming the join table AuthorProjects is correct behaviour. If the name had been ProjectAuthors it would be correct behaviour as well though.

Categories

Resources