Configuring EF code first without a foreign key - entity-framework-4.1

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);

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.

Entity Framework in MY.SQL ASP.NET Core Angular project fails with MySqlException error in syntax near CONSTRAINT when trying to DropForeignKey

I am running a project using code first migrations. I have a big model and everything ran smoothly untill this happened. So part of the big picture was like looking this. Initial state model number one:
public class WebClient
{
public int Id { get; set; }
public string FirstName { get; set; }
public IList<Trade> Trades { get; set; }
public IList<Portfolio> Portfolios { get; set; }
public IList<Strategy> Strategies { get; set; }
[Required]
public string EMail { get; set; }
}
And the second model:
public class Strategy
{
public int Id { get; set; }
public string Name { get; set; }
public string ShortDesc { get; set; }
public string EntryRules { get; set; }
public string ExitRules { get; set; }
public IList<Trade> Trades { get; set; }
}
When I ran this migration to MYSQL database Entity Framework created a webClientId column in the strategies table and set it to be the foreign key for WebCLients.Id (for the id in the webclients table) and also created an index for that which is pretty cool.
After this I realized I forgot to input a relation to the webclient inside the strategy model. So I put two lines in and got this.
public class Strategy
{
public int Id { get; set; }
public string Name { get; set; }
public string ShortDesc { get; set; }
public string EntryRules { get; set; }
public string ExitRules { get; set; }
public IList<Trade> Trades { get; set; }
public virtual WebClient WebClient { get; set; } //new stuff
public int WebClientId { get; set; } //new stuff
}
For this Entity Framework suggested a following migrations which is a bit weird to start with.
migrationBuilder.DropForeignKey(
name: "FK_Strategies_WebClients_WebClientId",
table: "Strategies");
migrationBuilder.AlterColumn<int>(
name: "WebClientId",
table: "Strategies",
nullable: false,
oldClrType: typeof(int),
oldNullable: true);
migrationBuilder.AddForeignKey(
name: "FK_Strategies_WebClients_WebClientId",
table: "Strategies",
column: "WebClientId",
principalTable: "WebClients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
So it drops the old key to create a new one which is exactly the same apart from that it is not nullable. Ok well let's do it. However when I run a database update on that I get an error and I have no idea on how to deal with it.
MySql.Data.MySqlClient.MySqlException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CONSTRAINT FK_Strateg
ies_WebClients_WebClientId' at line 1
If I run the command with a --verbose flag I can catch the bit of SQL and see where it crashes...
ALTER TABLE Strategies DROP CONSTRAINT FK_Strategies_WebClients_WebClientId;
Has anyone come across this issue ? Will be real glad to hear any hints, Thanks!
Ok so the work around is like this. Seems to me MySQL doesn't understand:
ALTER TABLE Strategies DROP CONSTRAINT FK_Strategies_WebClients_WebClientId;
That is for SQL Server / Oracle / MS Access. More on this here
I just changed that line in the migrations to
migrationBuilder.Sql("ALTER TABLE Strategies DROP FOREIGN KEY FK_Strategies_WebClients_WebClientId;");
Everything updated fine after that. Result - use DROP FOREIGN KEY in MySql instead of DROP CONSTRAINT. No idea why that is not fized in the MySql Adapter for Entity Framework.
This is happening because Entity Framework doesn't understand that WebClientId is a foreign key so it is adding a new foreign key field for you. The Entity Framework convention for naming automatically inserted foreign key fields is TableName_Id,
Entity Framework Code First provides a set of data annotation attributes that can be applied on domain classes or the properties of domain classes.The ForeignKey attribute is used to specify which property is the foreign key in a relationship (ForeignKey Attribute specifies the foreign key for the Navigation property in Entity Framework).
Example:
public class Strategy
{
public int Id { get; set; }
public string Name { get; set; }
public string ShortDesc { get; set; }
public string EntryRules { get; set; }
public string ExitRules { get; set; }
public IList<Trade> Trades { get; set; }
public int WebClientId { get; set; } //Foreign Key property
[ForeignKey("WebClientId")]
public WebClient WebClient { get; set; } //Respective Entity
}
public class WebClient
{
public int Id { get; set; }
public string FirstName { get; set; }
[Required]
public string EMail { get; set; }
public IList<Trade> Trades { get; set; }
public IList<Portfolio> Portfolios { get; set; }
public IList<Strategy> Strategies { get; set; }
}
You can get more information from this link.

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)

EF Code First Custom Collections

When creating code first collections can you implement a custom class that implements ICollection. The code below is conceptual not actual
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; }
//Want to Avoid This
public ICollection<Product> Products { get; set; }
//Use his instead of above
public ProductList ProductsInCategory {get;set;}
}
public class ProductsList :ICollection<Product>
{
public int DiscontinuedProductsCount
{
return internalList.Count();
}
//Icollection Methods Excluded
}
EF can indeed support any collection which inherits from ICollection. We create a deletable collection to support auto deletions and also create collections for child objects to keep the size of our main object smaller.