I m using .NET Core 2.0. I wrote, a lot for navigation property I know that does not support automatic lazy loading on EF Core at this time. I m using Microsoft approach to create navigation property. I`m trying to create a many-to-many relationship.
First create manually mapping table that is also included in ApplicationDbContext like new DbSet
public class ProductCategory
{
[Key]
public int ProductId { get; set; }
[ForeignKey("ProductId")]
public virtual Product Product { get; set; }
[Key]
public int CategoryId { get; set; }
[ForeignKey("CategoryId")]
public virtual Category Category { get; set; }
}
public class Product
{
public int Id { get; set; }
public virtual ICollection<ProductCategory> ProductCategory { get; set;
}
public class Category
{
public int Id { get; set; }
public virtual ICollection<ProductCategory> ProductCategory { get; set;
}
In OnModelCreating class
builder.Entity<ProductCategory>()
.HasKey(x => new { x.CategoryId, x.ProductId });
builder.Entity<ProductCategory>()
.HasOne(x => x.Product)
.WithMany(x => x.ProductCategory)
.HasForeignKey(x => x.ProductId);
builder.Entity<ProductCategory>()
.HasOne(x => x.Category)
.WithMany(x => x.ProductCategory)
.HasForeignKey(x => x.CategoryId);
When adding a new object in mapping table.
var productCategory = new ProductCategory
{
CategoryId = 1,
ProductId = 1
};
db.ProductCategory.Add(productCategory);
db.SaveChanges();
Item is added successfully, after that try to access Product or Category to test navigation property but receives only the current class in the mapping table.You can see the example when I`m trying to access product from category class:
model.Categories = this._categoryService
.All()
.Include(x => x.ProductCategory)
.ToList();
Product class is null?
It's because including navigation property automatically includes the inverse navigation property, but nothing more. You need to specifically ask for that using ThenInclude.
Assuming All method returns IQueryable<Category>, something like this:
model.Categories = this._categoryService
.All()
.Include(x => x.ProductCategory)
.ThenInclude(x => x.Product)
.ToList();
Note that this will also automatically include (load) the ProductCategory collection property of the Product entity.
Related
I'm trying to migrate from using TPT (a table per subclass) to TPH (One table for all subclasses).
This is my starting point for TPT:
entities:
[Serializable]
public abstract class VeganItem<TVeganItemEstablishment> : DomainEntity<int>
{
[Required]
public string Name { get; set; }
[Required]
public string CompanyName { get; set; }
[Required]
public string Description { get; set; }
public string Image { get; set; }
[Required]
public int IsNotVeganCount { get; set; } = 0;
[Required]
public int IsVeganCount { get; set; } = 0;
[Required]
public int RatingsCount { get; set; } = 0;
[Required]
public int Rating { get; set; }
[Required]
public List<Option> Tags { get; set; }
[PropertyName("veganItemEstablishments", Ignore = true)]
public virtual ICollection<TVeganItemEstablishment> VeganItemEstablishments { get; set; }
}
[ElasticsearchType(RelationName = "groceryitem", IdProperty = "Id")]
public class GroceryItem : VeganItem<GroceryItemEstablishment>
{
}
[ElasticsearchType(RelationName = "menuitem", IdProperty = "Id")]
public class MenuItem : VeganItem<MenuItemEstablishment>
{
}
OnModelCreating:
modelBuilder.Entity<GroceryItem>(gi =>
{
gi.HasIndex(e => new { e.CompanyName, e.Name }).IsUnique();
gi.Property(u => u.CreatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
gi.Property(u => u.UpdatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
gi.HasKey(e => e.Id);
gi.HasOne(q => q.UpdatedBy)
.WithMany()
.HasForeignKey(k => k.UpdatedById);
gi.HasOne(q => q.CreatedBy)
.WithMany()
.HasForeignKey(k => k.CreatedById);
gi.Property(e => e.Tags)
.HasConversion(
v => JsonSerializer.Serialize(v, null),
v => JsonSerializer.Deserialize<List<Option>>(v, null),
new ValueComparer<IList<Option>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => (IList<Option>)c.ToList()));
});
modelBuilder.Entity<MenuItem>(mi =>
{
mi.HasIndex(e => new { e.CompanyName, e.Name }).IsUnique();
mi.Property(u => u.CreatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
mi.Property(u => u.UpdatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
mi.HasKey(e => e.Id);
mi.HasOne(q => q.UpdatedBy)
.WithMany()
.HasForeignKey(k => k.UpdatedById);
mi.HasOne(q => q.CreatedBy)
.WithMany()
.HasForeignKey(k => k.CreatedById);
mi.Property(e => e.Tags)
.HasConversion(
v => JsonSerializer.Serialize(v, null),
v => JsonSerializer.Deserialize<List<Option>>(v, null),
new ValueComparer<IList<Option>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => (IList<Option>)c.ToList()));
});
public DbSet<GroceryItem> GroceryItems { get; set; }
public DbSet<MenuItem> MenuItems { get; set; }
So I want just one table called VeganItems. What is it that is actually causing there to be 2 tables - GroceryItems and MenuItems? As I have tried a couple of things and they didn't work. EF Core uses TPH by default so I'm unsure why it is using TPT. I'm wondering if it is because my base entity is a generic type.
EF Core uses TPH by default so I'm unsure why it is using TPT. I'm wondering if it is because my base entity is a generic type.
Generic type is one of the problems. The other is that it is not a base entity, but just base class. In order to be considered entity, there must be either DbSet<T>, or ModelBuilder.Entity<T>() call, or applied IEntityTypeConfiguration<T>, or some discorevered entity navigation property (either collection or reference) referring to it - see Including types in the model.
You don't have any of these, so the model is not even TPT (common table containing common properties + single table per each derived entity containing specific properties), but some sort of a TPC (Table-Per-Class, not currently supported by EF Core), where there is no common table - all the data is in concrete tables for each derived entity.
So, in order to use TPT you need to fix both issues. Generic class cannot be used as entity type because its type is not enough to identify it (each generic instantiation is different type, typeof(Foo<Bar>) != typeof(Foo<Baz>)).
Start by extracting the non generic part which will serve as base entity (removed non EF Core annotations for clarity):
// Base class (code/data reuse only, not an entity)
public abstract class DomainEntity<TId>
{
public TId Id { get; set; }
}
// Base entity
public abstract class VeganItem : DomainEntity<int>
{
[Required]
public string Name { get; set; }
[Required]
public string CompanyName { get; set; }
[Required]
public string Description { get; set; }
public string Image { get; set; }
[Required]
public int IsNotVeganCount { get; set; } = 0;
[Required]
public int IsVeganCount { get; set; } = 0;
[Required]
public int RatingsCount { get; set; } = 0;
[Required]
public int Rating { get; set; }
[Required]
public List<Option> Tags { get; set; }
}
// Base class (code/data reuse only, not an entity)
public abstract class VeganItem<TVeganItemEstablishment> : VeganItem
{
public virtual ICollection<TVeganItemEstablishment> VeganItemEstablishments { get; set; }
}
// Derived entity
public class GroceryItem : VeganItem<GroceryItemEstablishment>
{
}
// Derived entity
public class MenuItem : VeganItem<MenuItemEstablishment>
{
}
Then (optionally) add DbSet for it
public DbSet<VeganItem> VeganItems { get; set; }
Finally (mandatory) move the fluent configuration of the base entity members to its own block, and keep in derived only the configuration of the specific members of the derive type:
// Configure base entity
modelBuilder.Entity<VeganItem>(vi =>
{
vi.HasIndex(e => new { e.CompanyName, e.Name }).IsUnique();
vi.Property(u => u.CreatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
vi.Property(u => u.UpdatedDate)
.HasDefaultValueSql("CURRENT_TIMESTAMP");
vi.HasKey(e => e.Id);
vi.HasOne(q => q.UpdatedBy)
.WithMany()
.HasForeignKey(k => k.UpdatedById);
vi.HasOne(q => q.CreatedBy)
.WithMany()
.HasForeignKey(k => k.CreatedById);
vi.Property(e => e.Tags)
.HasConversion(
v => JsonSerializer.Serialize(v, null),
v => JsonSerializer.Deserialize<List<Option>>(v, null),
new ValueComparer<IList<Option>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => (IList<Option>)c.ToList()));
});
// Configure derived entities
modelBuilder.Entity<GroceryItem>(gi =>
{
});
modelBuilder.Entity<MenuItem>(mi =>
{
});
I am facing this problem for a several hours, could someone take a look what I am doing wrong?
Relationship One to One returns value only for one record, if there would be three Products with idCategory = 2 only one of them will have a Category. I have even tried using CategoryId instead of idCategory, still does not work.
Using EF Core 3.1.5 and .NET Core 3.1, downgrading versions do not work as well.
In Product class:
public int idCategory { get; set; }
[ForeignKey("idCategory")]
public virtual Category Category { get; set; }
In Category class:
public virtual Product Product { get; set; }
Also I could not add to DB two records in Products with same idCategory so I inserted to OnModelCreating:
builder.Entity<Product>().HasIndex(e => e.idCategory).IsUnique(false);
And Fluent API:
builder.Entity<Category>()
.HasOne<Product>(b => b.Product)
.WithOne(i => i.Category)
.HasForeignKey<Product>(b => b.idCategory);
In Controller:
[HttpGet]
[Route("products")]
public IEnumerable<Product> GetProducts()
{
return products.GetProducts();
}
In ProductRepository:
public IEnumerable<Product> GetProducts()
{
return context.Products.Include(x => x.Category)
.Where(x => x.Category.IsActive == true && x.IsActive == true).ToList();
}
In ConfigureServices:
services.AddControllers().AddNewtonsoftJson(options =>
options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
);
Result:
JSON Result - Photo
^ Same thing happends with lazy loading, but then 1st record has a value for category, and 2nd not
Are you sure isn't One to Many relationship ?
If I understand correctly you just want return the category of a product, why didn't you add this property "CategoryId" in class "Product" and create a other class "Categories" with all Categories.
Something like this :
Class Product
public virtual Product Product { get; set; }
public Categories CategoryId { get; set; }
Class Categories
public int Id { get; set; }
public ICollection<Product> Product {get; set;}
Like this you'll can add a category for each products recorded.
You can check the ASP Net Core 3.1 documentation about Access Data :
https://learn.microsoft.com/en-us/aspnet/core/data/ef-mvc/complex-data-model?view=aspnetcore-3.1
I have an Ticket entity:
public class Ticket
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Relation> RelatedTickets { get; set; }
}
I want to setup many-to-many self-relations in Entity Framework Core, so i made two one-to-many relations:
public class Relation
{
[Required, ForeignKey("TicketFrom")]
public int FromId { get; set; }
[Required, ForeignKey("TicketTo")]
public int ToId { get; set; }
public virtual Ticket TicketFrom { get; set; }
public virtual Ticket TicketTo { get; set; }
}
I've tried to create the relationship using fluent API:
builder.Entity<Relation>()
.HasKey(uc => new { uc.FromId, uc.ToId });
builder.Entity<Relation>()
.HasOne(c => c.TicketFrom)
.WithMany(p => p.RelatedTickets)
.HasForeignKey(pc => pc.FromId);
builder.Entity<Relation>()
.HasOne(c => c.TicketTo)
.WithMany(p => p.RelatedTickets)
.HasForeignKey(pc => pc.ToId);
But in result i have an error:
Cannot create a relationship between 'Ticket.RelatedTickets' and
'Relation.TicketTo', because there already is a relationship between
'Ticket.RelatedTickets' and 'Relation.TicketForm'. Navigation
properties can only participate in a single relationship.
The possible solution is to add Parent relation directly to TicketEntity:
public class Ticket
{
public int Id { get; set; }
[Required, ForeignKey("ParentRelation")]
public Nullable<int> ParentRelationId { get; set; }
public virtual Ticket ParentRelation {get;set;}
public virtual ICollection<Ticket> RelatedTickets { get; set; }
...
}
With fluent api like this:
modelBuilder.Entity<Ticket> =>
{
entity
.HasMany(e => e.RelatedTickets)
.WithOne(e => e.ParentRelation)
.HasForeignKey(e => e.ParentRelationId );
});
But it looks 'dirty' to store parent relation like this.
What is the right approach?
It's not possible to have just one collection with relations. You need two - one with relations the ticket equals TicketFrom and second with relations the ticket equals TicketTo.
Something like this:
Model:
public class Ticket
{
public int Id { get; set; }
public string Title { get; set; }
public virtual ICollection<Relation> RelatedTo { get; set; }
public virtual ICollection<Relation> RelatedFrom { get; set; }
}
public class Relation
{
public int FromId { get; set; }
public int ToId { get; set; }
public virtual Ticket TicketFrom { get; set; }
public virtual Ticket TicketTo { get; set; }
}
Configuration:
modelBuilder.Entity<Relation>()
.HasKey(e => new { e.FromId, e.ToId });
modelBuilder.Entity<Relation>()
.HasOne(e => e.TicketFrom)
.WithMany(e => e.RelatedTo)
.HasForeignKey(e => e.FromId);
modelBuilder.Entity<Relation>()
.HasOne(e => e.TicketTo)
.WithMany(e => e.RelatedFrom)
.HasForeignKey(e => e.ToId);
Note that a solution using Parent is not equivalent, because it would create one-to-many association, while if I understand correctly you are seeking for many-to-many.
Here is very good explanation how to make many-to-many relationship in EF Core
Many-to-many self referencing relationship
Every collection or reference navigation property can only be a part of a single relationship. While many to many relationship with explicit join entity is implemented with two one to many relationships. The join entity contains two reference navigation properties, but the main entity has only single collection navigation property, which has to be associated with one of them, but not with both.
builder.Entity<Relation>()
.HasKey(uc => new { uc.FromId, uc.ToId });
builder.Entity<Relation>()
.HasOne(c => c.TicketFrom)
.WithMany() // <-- one of this must be empty
.HasForeignKey(pc => pc.FromId)
.OnDelete(DeleteBehavior.Restrict);
builder.Entity<Relation>()
.HasOne(c => c.TicketTo)
.WithMany(p => p.RelatedTickets)
.HasForeignKey(pc => pc.ToId);
Just make sure that WithMany exactly matches the presence/absence of the corresponding navigation property.
Note that you have to turn the delete cascade off.
#IvanStoev is correct. This is an example of a more general self referencing many to many relationship with many parents and many children.
public class Ticket
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
public List<TicketTicket> TicketChildren { get; set; }
public List<TicketTicket> TicketParents { get; set; }
}
public class TicketTicket
{
public int TicketChildId { get; set; }
public Ticket TicketChild { get; set; }
public int TicketParentId { get; set; }
public Ticket TicketParent { get; set; }
}
modelBuilder.Entity<TicketTicket>()
.HasKey(tt => new {tt.TicketChildId, tt.TicketParentId});
modelBuilder.Entity<Ticket>()
.HasMany(t => t.TicketChildren)
.WithOne(tt => tt.ProductParent)
.HasForeignKey(f => tt.ProductParentId);
modelBuilder.Entity<Ticket>()
.HasMany(t => t.TicketParents)
.WithOne(tt => tt.TicketChild)
.HasForeignKey(tt => tt.TicketChildId);
I have a MVC4 application and although I have get parameters for my DropDownList from the database, I encounter some kind of problems while posting the DropDownList value to the database. There is lots of samples for different approach, but I would like to apply a method without using an extra approach i.e. Ajax, Javascript, etc. On the other hand, I have run into "FormCollection" to pass data, but I am not sure if FormCollection is the best way in this scene. Here are some part of the view, controller and model I use:
View:
#using (Html.BeginForm("Add", "Product", FormMethod.Post,
new { enctype = "multipart/form-data" }))
{
<p>Product Type : #Html.DropDownListFor(m => m.SelectedLookupId, new SelectList(Model.Lookups.Where(x => x.LookupType == "Product Type"), "LookupID", "LookupValue"), "--- Select ---") </p>
Controller:
[HttpPost]
public ActionResult Add(Product product)
{
if (ModelState.IsValid)
{
product.ProductType = // ??? Cannot get the SelectedLookupId
...
repository.SaveProduct (product);
TempData["message"] = string.Format("{0} has been saved", product.Name);
return View("Completed");
}
else
{
//there is something wrong with the data values
return View(product);
}
}
ViewModel:
public class ProductViewModel
{
public IEnumerable<Product> Products { get; set; }
public IEnumerable<Lookup> Lookups { get; set; } //Lookup for Product Types
public int SelectedLookupId { get; set; }
public Product Product { get; set; }
}
Thanks in advance for your helps.
Your action method should be receiving the view model, not the Product itself, like so:
[HttpPost]
public ActionResult Add(ProductViewModel productViewModel)
Unless I'm confused. But I assume the view snippet you posted above is from the Add view and that view's model is of type ProductViewModel. In your action method you are returning the Add view when the model state is invalid however you are passing a Product to that view. Again I may be confused because this should give you a runtime error that the types don't match.
Thanks for reply. Actually by using ViewModel rather than View, I have managed to solve the problem. On the other hand, after some research, I have applied another effective method in order to populate Dropdownlist without needing ViewModel. Furthermore with this example, I could use multiple foreign keys on the same Lookup table as shown below. Here is an an Applicant entity having 3 foreign keys and Lookup entity related to these keys. What I wanted to achieve with this example is exactly to use a Lookup table for only several Dropdownlist parameters i.e. Gender, Yes/No, Status,... due to no needing to create a table for the several parameters (these parameters are distinguished LookupType property on Lookup table). Here is the full example (I have shorted unrelated properties for brevity) below:
Applicant Entity:
public class Applicant
{
[Key]
public int ApplicantID { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
// for using "Multiple foreign keys within same table using Fluent API"
public int? HasDoneAnyProject { get; set; }
public int? IsInterestedAnyProgramme { get; set; }
public int? InterestedProgrammeId { get; set; }
public virtual Lookup PrimaryLookup { get; set; }
public virtual Lookup SecondaryLookup { get; set; }
public virtual Lookup TertiaryLookup { get; set; }
}
Lookup Entity:
public class Lookup
{
[Key]
public int LookupID { get; set; }
public string LookupType { get; set; }
public string LookupValue { get; set; }
// for using "Multiple foreign keys within same table using Fluent API"
public virtual ICollection<Applicant> PrimaryLookupFor { get; set; }
public virtual ICollection<Applicant> SecondaryLookupFor { get; set; }
public virtual ICollection<Applicant> TertiaryLookupFor { get; set; }
}
DbContext:
public class EFDbContext : DbContext
{
public DbSet<Applicant> Applicants { get; set; }
public DbSet<Lookup> Lookups { get; set; }
//for using "Multiple foreign keys within same table using Fluent API"
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Applicant>()
.HasOptional(b => b.PrimaryLookup)
.WithMany(a => a.PrimaryLookupFor)
.HasForeignKey(b => b.HasDoneAnyProject)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Applicant>()
.HasOptional(b => b.SecondaryLookup)
.WithMany(a => a.SecondaryLookupFor)
.HasForeignKey(b => b.IsInterestedAnyProgramme)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Applicant>()
.HasOptional(b => b.TertiaryLookup)
.WithMany(a => a.TertiaryLookupFor)
.HasForeignKey(b => b.InterestedProgrammeId)
.WillCascadeOnDelete(false);
}
}
Controller:
private void PopulateLookupsDropDownList(string lookupType, string foreignKey, object selectedLookups = null)
{
var lookupsQuery = repository.Lookups
.Select(x => x)
.Where(x => x.LookupType == lookupType)
.OrderBy(x => x.ParentLookupID).ToList();
ViewData[foreignKey] = new SelectList(lookupsQuery, "LookupID", "LookupValue", selectedLookups);
}
and for calling the Method for each of three Dropdownlist:
PopulateLookupsDropDownList("YesNo", "HasDoneAnyProject", applicant.HasDoneAnyProject);
PopulateLookupsDropDownList("YesNo", "IsInterestedAnyProgramme", applicant.IsInterestedAnyProgramme);
PopulateLookupsDropDownList("Programme", "InterestedProgrammeId", applicant.InterestedProgrammeId);
View: : Populating each of three Dropdownlist from the same Lookup table with different LookupType parameter:
<label>Has done any project before?</label>
#Html.DropDownList("HasDoneAnyProject", "---- Select ----")
<label>Are you interested in any programme?</label>
#Html.DropDownList("IsInterestedAnyProgramme", "---- Select ----")
<label>Interested programme name?</label>
#Html.DropDownList("InterestedProgrammeId", "---- Select ----")
I hope this approach will be useful for those who want to populate Dropdownlists from the same Lookup table. On the other hand, it is not only suitable for this, also can be used for populating Dropdownlists from different tables.
Regards.
Recently I've come back to the question NHibernate vs EF5 for the Enterprise Application.
I know many important differences, but this one is the most suprising for me.
Consider two classic entities, Customer and Order (1:n):
public class Customer
{
public Customer()
{
Orders = new HashSet<Order>();
}
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
**public virtual ICollection<Order> Orders { get; set; }**
}
public class Order
{
public virtual Guid Id { get; set; }
public virtual Guid CustomerId { get; set; }
public virtual string Number { get; set; }
public virtual DateTime Date { get; set; }
**public virtual Customer Customer { get; set; }**
}
For both NHibernate and EF5 there are two-way mappings.
Code snippets for loading all customers AND all Orders in context of DbContext and Session for EF5 and NHibernate accordingly:
using (TestOrmForDalDbEntities context = new TestOrmForDalDbEntities())
{
context.Configuration.ProxyCreationEnabled = false;
IQueryable<Customer> customers = context.Customers;
customers.Load();
IQueryable<Order> orders = context.Orders;
orders.Load();
}
using (ISession session = _sessionFactory.OpenSession())
{
var customers = session.Query<Customer>().ToList();
var orders = session.Query<Order>().ToList();
}
The result is:
EF5: Each Customer has a collection of appropriate Orders (EF5 automatically connect them)
NHibernate: None of Customers has a collection of appropriate Orders. Even though each Order HAS link to an appropriate Customer.
The question is: is it an NHibernate idea not to connect in such way or it's me who doesn't know how to configure NHibernate?
P.S. Mappings for NHibernate:
public class CustomerMap : ClassMap<Customer>
{
public CustomerMap()
{
Id(x => x.Id).UnsavedValue(Guid.Empty).GeneratedBy.Guid();
Map(x => x.Name);
HasMany(x => x.Orders).Cascade.All();
}
}
public OrderMap()
{
Id(x => x.Id).UnsavedValue(Guid.Empty).GeneratedBy.Guid();
Map(x => x.Number);
Map(x => x.Date);
Map(x => x.CustomerId);
References(x => x.Customer);
}
The relation from Customers to Orders (HasMany) should be Inverse, meaning, it's the Order endpoint that has the foreign key to the Customer.