EF 4.1 CF Fluent API mapping problemo - entity-framework-4.1

I'm new to the fluent API. I have a legacy database which I can't alter at the moment. Simply, this is what I need to achieve:
public class ItemCategory
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<Item> Items { get; set; }
}
public class Item
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<ItemCategory> ItemCategories { get; set; }
public virtual ICollection<Item> RelatedItems { get; set; }
}
Items can be in many categories, RelatedItems can be in different categories to the current Item (which may not have any related items), the existing join tables look like this:
ItemCategoriesItems (ID,ItemCategoryID,ItemID)
RelatedItemCategoriesItems (ID,ItemCategoriesItemsID,RelatedItemCategoriesItemsID)
Hopefully it's obvious that the related items join table above contains 2 foreign keys to the item categories join table - one pointing to the current item and the other to the related item. Currently my onModelCreating code has:
modelBuilder.Entity<ItemCategory>()
.HasMany(c => c.Items)
.WithMany(set => set.ItemCategories)
.Map(mc =>
{
mc.ToTable("ItemCategoriesItems","testdb");
mc.MapLeftKey("ItemCategoryID");
mc.MapRightKey("ItemID");
});
... which gets the categories/items working but I'm stuck on how to get the RelatedItems.
Any help greatly appreciated!

Related

EF Core 2 Stopping Circular Dependency on Many to Many Relationship

I am using the Sakila Sample Database from MySql on a MySql server. The Diagram looks as follows.
The important tables are the store, inventory and film tables. The is a many-to-many relationship between the tables and the linker table is the inventory table.
I scaffolded this Database in a new dotnetcore project using EFCore 2.
I am trying to get a list of stores and their list of films.
The Entities are defined as follows:
Store
public class Store
{
public Store()
{
Customer = new HashSet<Customer>();
Inventory = new HashSet<Inventory>();
Staff = new HashSet<Staff>();
}
public byte StoreId { get; set; }
public byte ManagerStaffId { get; set; }
public short AddressId { get; set; }
public DateTimeOffset LastUpdate { get; set; }
public Address Address { get; set; }
public Staff ManagerStaff { get; set; }
public ICollection<Customer> Customer { get; set; }
public ICollection<Inventory> Inventory { get; set; }
public ICollection<Staff> Staff { get; set; }
}
Inventory
public partial class Inventory
{
public Inventory()
{
Rental = new HashSet<Rental>();
}
public int InventoryId { get; set; }
public short FilmId { get; set; }
public byte StoreId { get; set; }
public DateTimeOffset LastUpdate { get; set; }
public Film Film { get; set; }
public Store Store { get; set; }
public ICollection<Rental> Rental { get; set; }
}
Film
public partial class Film
{
public Film()
{
FilmActor = new HashSet<FilmActor>();
FilmCategory = new HashSet<FilmCategory>();
Inventory = new HashSet<Inventory>();
}
public short FilmId { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public short? ReleaseYear { get; set; }
public byte LanguageId { get; set; }
public byte? OriginalLanguageId { get; set; }
public byte RentalDuration { get; set; }
public decimal RentalRate { get; set; }
public short? Length { get; set; }
public decimal ReplacementCost { get; set; }
public string Rating { get; set; }
public string SpecialFeatures { get; set; }
public DateTimeOffset LastUpdate { get; set; }
public Language Language { get; set;
public Language OriginalLanguage { get; set; }
public ICollection<FilmActor> FilmActor { get; set; }
public ICollection<FilmCategory> FilmCategory { get; set; }
public ICollection<Inventory> Inventory { get; set; }
}
My context looks as follows:
modelBuilder.Entity<Inventory>(entity =>
{
entity.ToTable("inventory", "sakila");
entity.HasIndex(e => e.FilmId)
.HasName("idx_fk_film_id");
entity.HasIndex(e => new { e.StoreId, e.FilmId })
.HasName("idx_store_id_film_id");
And lastly the repo looks as follows:
public IEnumerable<Store> GetStores()
{
return _context.Store.
Include(a => a.Inventory).
ToList();
}
Problem:
When I call this method from a Controller to get the list of stores I don´t get any json response on Postman. Yet if I debug into the list that is returned from the Controller I find the list of stores.
The problem is that the list contains:
store->inventory->film->store->inventory->film->store... Etc. Creating a circular dependency that fills up the allowed Process memory of the request.
Possible Solutions:
I think it has to do with the fact that on the Context both the Foreign Keys are defined as HasIndex instead of HasKey
entity.HasIndex(e => new { e.StoreId, e.FilmId })
.HasName("idx_store_id_film_id");
When I define it as HasKey then I get an Error:
'The relationship from 'Rental.Inventory' to 'Inventory.Rental' with
foreign key properties {'InventoryId' : int} cannot target the primary
key {'StoreId' : byte, 'FilmId' : short} because it is not compatible.
Configure a principal key or a set of compatible foreign key
properties for this relationship.'
To answer #hamzas comment, I did find a solution to this problem. I used EFCore to build the entities and the DBContext through scaffolding (DB First). As a best practice you should be using Models (Dtos) to represent the Data for the client. EFCore is very helpful in giving us the flexibility to access this M to N relationship however we want. This gives us the flexibility to represent this Data to the client however we want.
Whatever your use case might be. You have to convert the M to N relationship into an 1 to N model.
Use Case #1: You want to show all the movies for a specific store.
Solution
Step #1: You create a StoreDto (Model)
public class StoreDto
{
int StoreId { get; set; }
ICollection<FilmDto> Films { get; set; }
= new List<FilmDto> ();
}
Step #2: Create a FilmDto
public class FilmDto
{
int FilmId { get; set; }
int StoreId { get; set; }
string FilmName { get; set; }
}
Step #3: You provide a Mapping with auto mapper
public class MappingProfiles : Profile
{
public MappingProfiles()
{
CreateMap<Store, StoreDto>();
CreateMap<Film, FilmDto>();
}
}
Step #4: Query the data correctly, Unfortunately I don´t have this example anymore to test this code, so here is where you´ll have to experiment a bit
public Store GetFilmsForStore(byte StoreId)
{
return _context.Store.
Include(a => a.Inventory).
ThenInclude(i => i.Film)
ToList();
}
On the "Include" part you want to only get the Inventory entries where StoreId == Inverntory.StoreId and then Include the Films Object from the resulting list.
I hope you get the jist of it. You want to break up your m to n relationships and make them seem like 1 to m for your clients.

How to ignore navigation properties in asp.net mvc web api

I'm using ASP.NET Web API with Entity Framework but i'm facing a problem while generating the JSON for the navigation property:
I have two tables; Product and Category.
public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public virtual Category Category { get; set; }
}
public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
when I generate the JSON for Product it generates the JSON category which is fine but inside the Category JSON there is another JSON pointing to JSON product so a huge JSON file is create i tried to solve this issue by removing virtual but every time i update the model i face the same problem. is there any way to solve?
If you are using Newtonsoft.Json, then you can simply apply the attribute [JsonIgnore] to the properties you wish to ignore.
public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
[JsonIgnore]
public virtual ICollection<Product> Products { get; set; }
}
With this setup, whenever category is serialized to Json, it will ignore the Products collection

Relationship Using Code first with Existing database

When defining a relationship between two types is it important to include a navigation property on both types, such as in the following example:
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public Category Category { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
public ICollection<Product> Products { get; set; }
}
Can I do without including the navigation property in Category ?
If you just want it infered by code first convention then yes you need both on either side. I'd also make the collection "virtual" to support lazy loading.
You can set it up using the fluent configuration when the model is built. It would be something like this
modelBuilder.Entity<Product>()
.HasMany(x => x.Category)

Working with a Model class that has a foreign/navigation key to itself

I am trying to develop a catalog project in ASP.NET MVC 3 and using EF Code first with an existing Database. There is a Categories table in my database that points to itself. For that, I have written the following model class. --"Correct me if the model is wrong"--
public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public int? ParentCategoryID { get; set; }
public string CategoryDesc { get; set; }
[ForeignKey("ParentCategoryID")]
public virtual Category ParentCategory { get; set; }
public virtual ICollection<Product> Products { get; set; }
}
Question : I am unable to understand as to how can i work with this class. While using and passing the following code to the view
var cat = dbStore.Categories.Include("ParentCategory").ToList().
I got this error : Object reference not set to an instance of an object. This is happening because the root category has null ParentCategoryID. Please tell me how will you work with this code or any resource that can help me understand working in such scenarios. Just any sort of code will be helpful that uses the above the model, like displaying a list or a menu or anything, just anything.
Usually what you do is travel from top level categories to bottom level categories. Inorder to do that first you need to define SubCategories collection in your class
public class Category
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
public int? ParentCategoryID { get; set; }
public string CategoryDesc { get; set; }
[ForeignKey("ParentCategoryID")]
public virtual Category ParentCategory { get; set; }
[InverseProperty("ParentCategory")]
public virtual ICollection<Category> SubCategories{ get; set; }
public virtual ICollection<Product> Products { get; set; }
}
Then you retrieve the top level categories
var topCategories = dbStore.Categories
.Where(category => category.ParentCategoryID == null)
.Include(category => category.SubCategories).ToList();
After that you can traverse the hierachey
foreach(var topCategory in topCategories)
{
//use top category
foreach(var subCategory in topCategory.SubCategories)
{
}
}
If you do not have very many categories you can solve this by loading the whole collection of categories. I think EF will handle the fixup for you so all relations are properly populated.
As far as I know there are no SQL'ish databases/ORM's that can handle this scenario well. An approach I often use is to load the whole collection as I said above and then manually fix the relations. But I do think EF will do that for you.
Basically you should do:
var topCategories = dbStore.Categories.ToList().Where(category => category.ParentCategoryID == null);

Configuring EF code first without a foreign key

I have the following model:
public class Product
{
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Catalog> Matches { get; set; }
}
public class Catalog
{
public long Id { get; set; }
public string Title { get; set; }
}
Using Entity Framework code first I configure this using:
public DbSet<Product> Products { get; set; }
public DbSet<Catalog> Catalogs { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// not rules setup yet
}
Currently when EF creates my database it creates a nullable foreign key in the Catalogs table called Product_Id. Is there a way to configure EF to not create any foreign key in the Catalog table?
The catalog table contains imported items from a catalog that should have no relation to the Products table. At run time a search query will be fired for each product and the result will be added to the catalog collection of the product object.
For your purpose I would exclude the Matches collection from the model, either by data annotation...
public class Product
{
public long Id { get; set; }
public string Name { get; set; }
[NotMapped]
public virtual ICollection<Catalog> Matches { get; set; }
}
...or in Fluent code:
modelBuilder.Entity<Product>()
.Ignore(p => p.Matches);