Unable to create controller for many to many table in MVC EF Core - model-view-controller

I am trying to add a controller to my MVC application for a many-to-many table in my database. The bundle table and the node Table are joined by the bundle_node table. The following 3 snippets are the classes for each table.
Bundle Table:
public partial class Bundle
{
public Bundle()
{
Nodes = new HashSet<Node>();
}
public int Id { get; set; }
public DateTime StartUtc { get; set; }
public DateTime EndUtc { get; set; }
public int Quantity { get; set; }
public int? AgreementId { get; set; }
public decimal? BundlePrice { get; set; }
public virtual Agreement? Agreement { get; set; }
public virtual ICollection<Node> Nodes { get; set; }
}
Node table:
public partial class Node
{
public Node()
{
Bundles = new HashSet<Bundle>();
}
public int Id { get; set; }
public string Name { get; set; } = null!;
public virtual ICollection<Bundle> Bundles { get; set; }
}
bundle_node (join) table:
public partial class BundleNode
{
public int NodeId { get; set; }
public int BundleId { get; set; }
public virtual Bundle Bundle { get; set; } = null!;
public virtual Node Node { get; set; } = null!;
}
In dbContext, inside the modelbuilders for each entity I have the following relationships mapped/defined:
Bundle:
entity.HasMany(d => d.Nodes)
.WithMany(p => p.Bundles)
.UsingEntity<Dictionary<string, object>>(
"BundleNode",
r => r.HasOne<Node>().WithMany().HasForeignKey("NodeId").OnDelete(DeleteBehavior.ClientSetNull).HasConstraintName("fk_bundle_node_node"),
l => l.HasOne<Bundle>().WithMany().HasForeignKey("BundleId").OnDelete(DeleteBehavior.ClientSetNull).HasConstraintName("fk_bundle_node_bundle"),
j =>
{
j.HasKey("NodeId", "BundleId").HasName("bundle_node_pkey");
j.ToTable("bundle_node", "admin");
j.IndexerProperty<int>("NodeId").HasColumnName("node_id");
j.IndexerProperty<int>("BundleId").HasColumnName("bundle_id");
});
Node Modelbuilder relationship mapping:
entity.HasMany(d => d.Bundles)
.WithMany(p => p.Nodes)
.UsingEntity<Dictionary<string, object>>(
"BundleNode",
l => l.HasOne<Bundle>().WithMany().HasForeignKey("BundleId").OnDelete(DeleteBehavior.ClientSetNull).HasConstraintName("fk_bundle_node_bundle"),
r => r.HasOne<Node>().WithMany().HasForeignKey("NodeId").OnDelete(DeleteBehavior.ClientSetNull).HasConstraintName("fk_bundle_node_node"),
j =>
{
j.HasKey("NodeId", "BundleId").HasName("bundle_node_pkey");
j.ToTable("bundle_node", "admin");
j.IndexerProperty<int>("NodeId").HasColumnName("node_id");
j.IndexerProperty<int>("BundleId").HasColumnName("bundle_id");
});
BundleNode modelbuilder:
entity.HasOne(d => d.Bundle)
.WithMany()
.HasForeignKey(d => d.BundleId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("fk_bundle_node_bundle");
entity.HasOne(d => d.Node)
.WithMany()
.HasForeignKey(d => d.NodeId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("fk_bundle_node_node");
I am using a db first approach which is how I ended up in this mess because it seems overkill to me that the two principle tables should point to the join table rather than just having the join table reference the foreign keys of the two principle tables. Anyway, when I try and create a controller based on the BundleNode model, I get the following error:
The message is telling me to add a foreign key which points to a primary key in another entity which is mapped to BundleNode. As far as I am concerned the following part of the modelbuilder for BundleNode does just that:
entity.HasOne(d => d.Bundle)
.WithMany()
.HasForeignKey(d => d.BundleId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("fk_bundle_node_bundle");
entity.HasOne(d => d.Node)
.WithMany()
.HasForeignKey(d => d.NodeId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("fk_bundle_node_node");
When I did the scaffolding it skipped the join table which apparently is normal but it did create the Node modelbuilder relationship. I have created the Bundle and the BundleNode relationships myself but I have obviously gone wrong somewhere. It does seem overkill to have everything pointing to everything else like some kind of coding Mexican standoff.

Related

EF Core 6 / Many to many relationship with custom connection table / ORA-00904 : invalid identifier

Hey im trying to create a many to many relationship as following:
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public Author Author { get; set; }
public ICollection<Category> Categories { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string CategoryName { get; set; }
public ICollection<Book> Books { get; set; }
}
public class Book2Category
{
public int BookId { get; set; }
public int CategoryId { get; set; }
}
The c# classes are exactly the same like the oracle database tables.
My fluent Api OnModelCreating method:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>(entity =>
{
entity.HasMany(b => b.Categories)
.WithMany(c => c.Books)
.UsingEntity<Book2Category>(d =>
{
d.HasKey(k => new { k.BookId, k.CategoryId});
});
});
modelBuilder.Entity<Category>(entity =>
{
entity.HasMany(b => b.Books)
.WithMany(c => c.Categories)
.UsingEntity<Book2Category>(d =>
{
d.HasKey(k => new { k.BookId, k.CategoryId});
});
});
}
I always get error "ORA-00904: \"BooksId\": invalid identifier" because EF Core is trying to execute the following select statement:
/*some selects before*/
left join (
select b2c.BookId, b2c.CategoryId, b2c.BooksId, b2c.CategoriesId
from Book2Category b2c
/*some more after but unimportant*/
)
So it takes the defined Keys BookId and CategoryId from OnModelCreating.
But also it genarates BooksId and CategoriesId (with an S).
This is autogenerated and comes from the properties defined in classes Book and Category.
What i want to achieve is to configure the entities in OnModelCreating() that i get rid of the two unnecessary b2c.BooksId, b2c.CategoriesId.
I know that there is a name convention for EF Core but i dont want to rename the columns in Book2Category.
Is that possible somehow?

how To Write a Query That Get Each Entity With Sub Entities When We Have a Self-Relation Entity

I using Ef Core 2.2.and I have an Entity It has Self-Relation And Each Entity Might have a List Of Entities
This Is My Entity
public class CourseGroup
{
public int ID { get; set; }
public string Title { get; set; }
public bool IsDeleted { get; set; }
//Foreign key
public int? ParentId { get; set; }
//Navigations Property
public CourseGroup ParentCourseGroup { get; set; }
//Relatons => Self Relation
public ICollection<CourseGroup> Groups { get; set; }
}
And This Is My Configurations
class CourseGroupConfig : IEntityTypeConfiguration<CourseGroup>
{
public void Configure(Microsoft.EntityFrameworkCore.Metadata.Builders.EntityTypeBuilder<CourseGroup> builder)
{
builder.HasKey(c => c.Id);
builder.Property(c => c.Id).ValueGeneratedOnAdd();
builder.Property(c => c.Title).HasMaxLength(60);
//Relations
builder.HasOne(c => c.ParentCourseGroup).WithMany(c => c.Groups).HasForeignKey(c => c.ParentId);
}
}
And I Wanna get All Entities With Sub Entities. How Can I Write This Query With Linq?
Please Do me a favor to Write This Query

Entity Framework Core 2 querying multi level collections with theniclude

I created two classes:
public class A {
public int Id { get; set; }
public ICollection<B> Bs { get; set; }
}
public class B {
public ICollection<C> C1s { get; set; }
public ICollection<C> C2s { get; set; }
}
then I tried to fetch them with ThenInclude method:
var result = context.As //public DbSet<A> As { get; set; }
.Include(a => a.Bs)
.ThenInclude(b => b.C1s)
.Include(a => a.Bs)
.ThenInclude(b => b.C2s)
.SingleOrDefaultAsync(a => a.Id.Equals(id)); //id is given
return await result;
But unfortunately both C1s and C2s collections are empty.
How to retrieve C entities which are related to B one?
I replaced .ThenInclude() methods with
.Include("Points.MasterPoints")
.Include("Points.SlavePoints")
That solved my issue.

Cannot implicitly convert type error when building query to populate viewmodel

I wanted to make a simple query to retrieve component data + language data for a specific article and then put that into a viewmodel.
The ProductComponent table is a child-table of Product and the relevant fields in it are the ComponentID and ProductId (foreign key, parentId), so I wanted to link ProductComponents to Product and ProductTranslations where I have all the language specific data, so I tried to make it all in one query to retrieve a list of components for a certain product.
Here is the query:
public IEnumerable<ProductComponents> ListComponents(int productid, string language)
{
var query = (from c in context.ProductComponents
from ct in context.ProductTranslations
where ct.ProductId == c.ComponentId
where ct.ProductLanguage == language
from cp in context.Product
where cp.ProductId == c.ComponentId
where c.ProductId == productid
select new EnumComponents
{
ProductId = c.ComponentId,
Name = ct.ProductName,
SKU = cp.SKU
});
return query;
}
That gives this error, and highlighting the return Query; part as well:
Cannot implicitly convert type 'System.Linq.IQueryable<Artikelhantering.Models.EnumComponents>' to 'System.Collections.Generic.IEnumerable<Artikelhantering.Models.ProductComponents>'. An explicit conversion exists (are you missing a cast?)
Here is most of the data model, based on what I found googling and looking through Stack Overflow the relationships between tables might be the culprit, so I am including most of it.
public class Product
{
[Key]
public int ProductId { get; set; }
[DisplayName("Article nr")]
public string SKU { get; set; }
[DisplayName("Product Category")]
public int ProductCategoriesId { get; set; }
[DisplayName("Alternative Category")]
public int? AdditionalCategoriesId { get; set; }
[DisplayName("Show purchase button?")]
public bool Purchase { get; set; }
[DisplayName("Show Product?")]
public bool ShowProduct { get; set; }
[DisplayName("Picture name")]
public string Picture { get; set; }
[DisplayName("Is reference product?")]
public bool Reference { get; set; }
[DisplayName("Inprice")]
public decimal inPrice { get; set; }
[DisplayName("Additional costs")]
public decimal AddCost { get; set; } /
[DisplayName("Weight in kg")]
public decimal Weight { get; set; }
[DisplayName("Volume in m^3")]
public decimal Volume { get; set; }
[DisplayName("Vat code, use 0")]
public decimal VAT { get; set; }
public virtual IList<ProductTranslations> ProductTranslations { get; set; }
public virtual IList<ProductPrices> ProductPrices { get; set; }
public virtual IList<ProductComponents> ProductComponents { get; set; }
public virtual IList<ProductAccessories> ProductAccessories { get; set; }
public virtual ProductCategories ProductCategories { get; set; }
public virtual ProductCampaigns ProductCampaigns { get; set; }
}
public class ProductTranslations
{
[Key]
public int ProductTranslationsId { get; set; }
public int ProductId { get; set; } // This one links to the Product class
[DisplayName("Language Code")]
public string ProductLanguage { get; set; }
[DisplayName("Description")]
public string Description { get; set; }
[DisplayName("Product Name")]
public string ProductName { get; set; }
[MaxLength(255)]
[DisplayName("Meta Keywords")]
public string MetaKeywords { get; set; }
[MaxLength(255)]
[DisplayName("Meta Description")]
public string MetaDescription { get; set; }
public virtual Product Product { get; set; }
}
public class ProductComponents
{
[Key]
public int ProductComponentsId { get; set; }
public int ProductId { get; set; }
public int ComponentId { get; set; }
public virtual IList<ProductTranslations> ProductTranslations { get; set; }
public virtual Product Product { get; set; }
}
And then I define the relationships between the models like this:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<ProductCategories>()
.HasMany(x => x.ProductCategoriesTranslations) // Categories has many Translations
.WithRequired(y => y.ProductCategories) // Translations require a category
.HasForeignKey(p => p.ProductCategoriesId);
modelBuilder.Entity<Product>()
.HasMany(x => x.ProductPrices) // Product has many ProductPricings
.WithRequired(y => y.Product) // ProductPricing has required Product
.HasForeignKey(p => p.ProductId);
modelBuilder.Entity<Product>()
.HasMany(x => x.ProductTranslations) // Product has many ProductTranslations
.WithRequired(y => y.Product) // ProductTranslations has required Product
.HasForeignKey(p => p.ProductId);
modelBuilder.Entity<Product>()
.HasMany(x => x.ProductComponents) // Product has many ProductComponents
.WithRequired(y => y.Product) // ProductComponents has required Product
.HasForeignKey(p => p.ProductId);
modelBuilder.Entity<Product>()
.HasMany(x => x.ProductAccessories) // Product has many ProductAccessories
.WithRequired(y => y.Product) // ProductAccessories has required Product
.HasForeignKey(p => p.ProductId);
}
I am guessing I need to define a proper relationship for ProductComponents to ProductTranslations and Product, but I am not quite sure how, I've tried various ways to create a relationship between ProductComponents -> ProductTranslations but without any success. Ofcourse the issue might well be somehing else.
Looks like I figured it out on my own. Pretty simple and obvious error now that it finally clicked. The key was to rename public IEnumerable<ListComponents> to public IEnumerable<EnumComponents>
It seems logical in retrospect, I should be using the view model here and not the model, because that's what I am trying to populate after all, I can then call the model from inside the view model just fine.
public IEnumerable<EnumComponents> ListComponents(int productid, string language)
{
//return context.ProductComponents.Where(m => m.ProductId == productid);
var query = (from c in context.ProductComponents
where c.ProductId == productid
select new EnumComponents
{
Name = c.ProductTranslations
.Where(i => i.ProductLanguage == language)
.Where(i => i.ProductId == c.ComponentId)
.Select(i => i.ProductName)
.Single(),
SKU = c.Product.SKU,
ProductId = c.ComponentId
});
return (query);
}

many to many mapping in entity framework code first

I'm using Entity Framework 4 CTP5 Code First and I have a model along the lines of:
public class User {
public int UserId { get; set; }
public string Email { get; set; }
public ICollection<Customer> TaggedCustomers { get; set; }
}
public class Customer {
public int CustomerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<User> TaggedBy { get; set; }
}
There is a many to many relationship where a User can 'tag' a Customer and a Customer can be 'tagged' by many users. I have a working DbContext and I can query customers using
var customers = DbContext.Customers.Include(c => c.TaggedBy);
But each customer will have all users that have tagged the customer. How do I restrict the TaggedBy collection to just result with a specifed UserId?
I've tried along the lines of DbContext.Customers.Include(c => c.TaggedBy.Select(x => x.Id == userId)); but that throws an error.
EF Feature CTP5: Fluent API Samples - ADO.NET team blog - Site Home - MSDN Blogs
modelBuilder.Entity<Product>()
.HasMany(p => p.Tags)
.WithMany(t => t.Products)
.Map(m =>
{
m.MapLeftKey(p => p.ProductId, "CustomFkToProductId");
m.MapRightKey(t => t.TagId, "CustomFkToTagId");
});
Code First Mapping Changes in CTP5 - ADO.NET team blog - Site Home - MSDN Blogs
modelBuilder.Entity<Product>()
.HasMany(p => p.SoldAt)
.WithMany(s => s.Products)
.Map(mc => {
mc.ToTable("ProductsAtStores");
mc.MapLeftKey(p => p.Id, "ProductId");
mc.MapRightKey(s => s.Id, "StoreId");
});
Mark your collections as virtual and then you can easily do:
public class User
{
public int UserId { get; set; }
public string Email { get; set; }
public virtual ICollection<Customer> TaggedCustomers { get; set; }
}
public class Customer
{
public int CustomerId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<User> TaggedBy { get; set; }
}
using(var context = new MyDbContext())
{
var user = context.Users.Single(o => o.UserId == 0);
var customers = user.TaggedCustomers;
}
Results in much cleaner code in my opinion.

Resources