EntityFramework One to One-or Zero - c#

My "ShoppingCart" and "ShoppingCartItems" tables are already in my database. I am trying to add a new table called "discountCodes". Each shoppingCart can have one or zero discountCodes.
The error I am receiving is: Invalid column name 'discountId'.
[Table("ShoppingCarts")]
public class ShoppingCart
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
[Column("cartID")]
public string cartID { get; set; }
public virtual IList<ShoppingCartItem> CartItems { get; set; }
[Column("dateCreated")]
public DateTime? DateCreated { get; set; }
[Column("userID")]
public Guid UserID { get; set; }
public int? discountId { get; set; }
public virtual Discount discount { get; set; }
}
[Table("discountCodes")]
public class Discount
{
public int discountId { get; set; }
public string discountCode{get;set;}
[Required]
public int percentOff { get; set; }
[Required]
public Boolean isActive { get; set; }
public ShoppingCart ShoppingCart { get; set; }
}
public class ShoppingCartContext : DbContext
{
public ShoppingCartContext()
: base("MYDBConnectionString")
{
Database.SetInitializer<ShoppingCartContext>(new CreateDatabaseIfNotExists<ShoppingCartContext>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<ShoppingCart>().HasKey(t => t.cartID)
.HasOptional(t => t.discount)
.WithOptionalPrincipal(d => d.ShoppingCart)
.Map(t => t.MapKey("cartID"));
modelBuilder.Entity<Discount>().HasKey(t => t.discountId)
.HasOptional(q => q.ShoppingCart);
}
public DbSet<Discount> discountCodes { get; set; }
public DbSet<ShoppingCart> ShoppingCart { get; set; }
public DbSet<ShoppingCartItem> ShoppingCartItems { get; set; }
}

If you are working on an existing database you have to implement a DbMigration like it's explain here: Code First Migrations.
If you are in development phase, the easiest way is to drop the database.

Related

Zero-to-many relationship, or something else? Using FluentAPI

I have an "Item" table defined as such:
item_id
Name
Description
itemseries_id
itemtype_id
itemcondition_id
And then I have "ItemForSale" table:
itemforsale_id
item_id
price
date_added
Entities:
public class Item
{
public int ItemId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int ItemSeriesId { get; set; }
public ItemSeries ItemSeries { get; set; }
public int ItemConditionId { get; set; }
public ItemCondition ItemCondition { get; set; }
public int ItemTypeId { get; set; }
public ItemType ItemType { get; set; }
public List<ItemTag> ItemTags { get; set; }
public List<ItemImage> ItemImages { get; set; }
public List<ItemPurchase> ItemPurchases { get; set; }
public List<ItemSale> ItemSales { get; set; }
}
public class ItemForSale
{
public int ItemForSaleId { get; set; }
public decimal Price { get; set; }
public DateTime AddedDate { get; set; }
public int ItemId { get; set; }
public Item Item { get; set; }
}
How would I use the FluentAPI between these? I know I could add a reference to ItemForSale inside the Item entity class, but it doesn't make sense to me. So far I have mapped all of my One-to-one and many-to-many relationships, but the relationship between Item and ItemForSale is just confusing me.
Note: I am distinguishing between items that have been sold as a "Sale" or "ItemSale" and an item up for sale with no buyer as "ItemForSale"
From the EF Core Docs, you can do something like this:
class MyContext : DbContext
{
public DbSet<Item> Items { get; set; }
public DbSet<ItemSale> ItemSales { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<ItemSale>()
.HasOne(p => p.Item)
.WithMany(b => b.ItemSales)
.HasForeignKey(p => p.ItemId)
.IsRequired(false);
}
}
public class Item
{
public int ItemId { get; set; }
public string Url { get; set; }
public List<ItemSale> ItemSales { get; set; }
}
public class ItemSale
{
public int ItemSaleId { get; set; }
public string Title { get; set; }
public string Content { get; set; }
public Item Item { get; set; }
// note that the reference id is nullable
public int? ItemId { get; set; }
}
And then, mark the property ItemId in your model class as int?. While on EF 6, we had the HasOptional configuration option, but on EF Core, if the reference property can be nullable, it assumes the property starts from 0, like 0..N. I think that even the IsRequired(false) is not needed in this context as well, but here it goes.

One-to-one relationship does not carry both classes

I'm using the Entity Framework to persist and retrieve some information, I have a one-to-one relationship.
Product and Category, where a Product has a Category and a Category may have several Products.
I have the following structure
I created the two entities and made the relationship, but when I retrieve this information it brings me the information of the products however the category comes as null
Produto.cs
public class Produto
{
[Key]
public int id { get; set; }
public string descricao { get; set; }
public string observacao { get; set; }
public int status { get; set; }
public decimal valorVenda { get; set; }
public virtual Categoria categoria { get; set; }
}
Categoria.cs
public class Categoria
{
[Key]
[ForeignKey("categoriaid")]
public int id { get; set; }
public string descricao { get; set; }
public string observacao { get; set; }
public int status { get; set; }
public virtual Produto produto { get; set; }
}
ProdutoContexto.cs
public class ProdutoContexto : DbContext
{
public ProdutoContexto(DbContextOptions<ProdutoContexto> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Produto>()
.HasOne(a => a.categoria)
.WithOne(b => b.produto)
.HasForeignKey<Categoria>(b => b.id);
}
public DbSet<Produto> Produtos { get; set; }
}
CategoriaContexto.cs
public class CategoriaContexto : DbContext
{
public CategoriaContexto(DbContextOptions<CategoriaContexto> options) : base(options)
{
}
public DbSet<Categoria> Categorias { get; set; }
}
When I run the function to retrieve the information the following json is returned
[{"id":1,"descricao":"Coca-Cola","observacao":"Coca-Cola Gelada","status":1,"valorVenda":5.50,"categoria":null}]
My Query is:
[HttpGet]
public async Task<ActionResult<IEnumerable<Produto>>> GetProdutos()
{
return await _context.Produtos.ToListAsync();
}
Note that the category is null, how can it be done in such a way that the category is already loaded?
Category may have several Products.
Then its not one-to-one, instead its one-to-many and your model classes should be as follows:
public class Categoria
{
[Key]
public int id { get; set; }
public string descricao { get; set; }
public string observacao { get; set; }
public int status { get; set; }
public virtual ICollection<Produto> produtos { get; set; }
}
public class Produto
{
[Key]
public int id { get; set; }
[ForeignKey("categoria")]
public int categoriaId {get; set;}
public string descricao { get; set; }
public string observacao { get; set; }
public int status { get; set; }
public decimal valorVenda { get; set; }
public virtual Categoria categoria { get; set; }
}
And you don't need any FluentAPI configuration. So remove the modelBuilder.Entity<Produto>() configuration. And you also don't need two different DbContext for Produto and Categoria separately. Instead make your DbContext as follows:
public class ApplicationDbContexto : DbContext
{
public ApplicationDbContexto(DbContextOptions<ApplicationDbContexto> options) : base(options)
{
}
public DbSet<Categoria> Categorias { get; set; }
public DbSet<Produto> Produtos { get; set; }
}
And your query should be as follows:
[HttpGet]
public async Task<ActionResult<IEnumerable<Produto>>> GetProdutos()
{
return await _context.Produtos.Include(p => p.categoria).ToListAsync();
}

Conventions.Remove<OneToManyCascadeDeleteConvention>() in EF Core?

In my regular .NET Framework application, I was using EF 6.x and was also using some Inheritance, specifically:
PurchaseOrder.cs and SaleOrder.cs both inherit from Order.cs
And in the OnModelCreating() on my context class inheriting from IdentityDbContext, I was doing:
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
This used to work, but now I am moving my application to .NET Core 2.0 and I am using EF Core. What achieves the same thing in EF Core? Because right now I am getting the error:
System.Data.SqlClient.SqlException (0x80131904): Introducing FOREIGN KEY constraint 'FK_Order_Business_CustomerId' on table 'Order' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
UPDATE
Here's the code after Ahmar's answer. In my context class, I have:
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.HasDefaultSchema("PD");
builder.Entity<Customer>()
.HasMany(c => c.SaleOrders)
.WithOne(e => e.Customer)
.OnDelete(DeleteBehavior.SetNull);
builder.Entity<Supplier>()
.HasMany(po => po.PurchaseOrders)
.WithOne(e => e.Supplier)
.OnDelete(DeleteBehavior.SetNull);
builder.Entity<PurchaseOrder>()
.HasMany(li => li.LineItems)
.WithOne(po => po.PurchaseOrder)
.OnDelete(DeleteBehavior.SetNull);
builder.Entity<SaleOrder>()
.HasMany(li => li.LineItems)
.WithOne(po => po.SaleOrder)
.OnDelete(DeleteBehavior.SetNull);
}
And as far the Entities, they are:
public abstract class Business : IEntity
{
protected Business()
{
CreatedOn = DateTime.UtcNow;
}
public int Id { get; set; }
public string Name { get; set; }
public string TaxNumber { get; set; }
public string Description { get; set; }
public string Phone { get; set; }
public string Website { get; set; }
public string Email { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public ICollection<Address> Addresses { get; set; } = new List<Address>();
public ICollection<Contact> Contacts { get; set; } = new List<Contact>();
}
[Table("Customers")]
public class Customer : Business
{
public decimal AllowedCredit { get; set; }
public decimal CreditUsed { get; set; }
public int NumberOfDaysAllowedToBeOnMaxedOutCredit { get; set; }
public ICollection<SaleOrder> SaleOrders { get; set; }
}
[Table("Suppliers")]
public class Supplier : Business
{
public ICollection<PurchaseOrder> PurchaseOrders { get; set; }
}
public abstract class Order : IEntity
{
protected Order()
{
Date = DateTime.UtcNow;
CreatedOn = DateTime.UtcNow;
}
public int Id { get; set; }
public DateTime Date { get; set; }
public decimal ShippingCost { get; set; }
public Currency ShippingCurrency { get; set; }
public decimal ShippingConversionRate { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime? ModifiedOn { get; set; }
public ICollection<Invoice> Invoices { get; set; }
public ICollection<Note> Notes { get; set; }
}
[Table("PurchaseOrders")]
public class PurchaseOrder : Order
{
public int SupplierOrderNumber { get; set; }
public PurchaseOrderStatus Status { get; set; }
public decimal Vat { get; set; }
public decimal ImportDuty { get; set; }
public int SupplierId { get; set; }
public Supplier Supplier { get; set; }
public ICollection<PurchaseOrderLineItem> LineItems { get; set; }
}
[Table("SaleOrders")]
public class SaleOrder : Order
{
public decimal AmountToBePaidOnCredit { get; set; }
public SaleOrderStatus Status { get; set; }
public ICollection<SaleOrderLineItem> LineItems { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
}
So after doing what Ahmar suggested, I still get the same error when I do update-database.
You need to configure cascade delete behavior on each entity in .Net Core EF.
The Entity Framework Core Fluent API OnDelete method is used to specify the action which should take place on a dependent entity in a relationship when the principal is deleted.
The OnDelete method takes a DeleteBehavior enum as a parameter:
Cascade - dependents should be deleted
Restrict - dependents are
unaffected
SetNull - the foreign key values in dependent rows should
update to NULL
Example:
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<Employee> Employees { get; set; }
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public int? CompanyId { get; set; }
public Company Company { get; set; }
}
protected override void OnModelCreating(Modelbuilder modelBuilder)
{
modelBuilder.Entity<Company>()
.HasMany(c => c.Employees)
.WithOne(e => e.Company).
.OnDelete(DeleteBehavior.SetNull);
}
When deleting the Company, it will set CompanyId property in Employee table to null.
Get more detail at Configuring One To Many Relationships
PS. Please make sure your all referencing properties should be null able so, EF Core can set them null on delete. like CompanyId in about example.

How modelbuilder works

i have a question about relations between entities created in Code-First:
I have Models:
public class ProjectGroup
{
[Key]
public int ProjectGroupID { get; set; }
public string ProjectGroupName { get; set; }
//FK
public virtual ICollection<File> Files { get; set; }
public virtual ICollection<List> Lists { get; set; }
}
public class File
{
[Key]
public int FileID { get; set; }
public int ProjectGroupID { get; set; }
[Required]
[Display(Name="Ścieżka pliku")]
public string FilePath { get; set; }
[Required]
[Display(Name="Data Zapisu")]
[DataType(DataType.DateTime)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd hh:mm:ss}", ApplyFormatInEditMode = true)]
public DateTime FileSaveDate { get; set; }//no NULL=>error
[Display(Name="Suma MD5")]
[StringLength(32)]
public string FileMD5Hash { get; set; }
public string IPHost { get; set; }
public int FileTemplateID { get; set; }
[ForeignKey("ProjectGroupID")]
public virtual ProjectGroup ProjectGroup { get; set; }
[ForeignKey("FileTemplateID")]
public virtual FileTemplate FileTemplate { get; set; }
public virtual ICollection<List> Lists { get; set; }//klucz obcy dla listy
}
public class List
{
[Key]
public int ListID { get; set; }
public int UserID { get; set; }
public int ProjectGroupID { get; set; }
public int FileID { get; set; }
public bool Modified { get; set; }
public bool Verified { get; set; }
public bool Alive { get; set; }
[ForeignKey("UserID")]
public virtual User User { get; set; } //referencja,przekazanie nazwy FK
[ForeignKey("ProjectGroupID")]
public virtual ProjectGroup ProjectGroup { get; set; }
[ForeignKey("FileID")]
public virtual File File { get; set; }
}
And Context:
public class AWZDContext : DbContext
{
public AWZDContext()
{
}
public DbSet<User> Users { get; set; }
public DbSet<File> Files { get; set; }
public DbSet<List> Lists { get; set; }
public DbSet<RemotePC> RemotePCs { get; set; }
public DbSet<UserType> UserTypes { get; set; }
public DbSet<ProjectGroup> ProjectGroups { get; set; }
public DbSet<FileTemplate> FileTemplates { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<File>()
.HasRequired(f=>f.ProjectGroup)
.WithMany(t=>t.Files)
.WillCascadeOnDelete(false);
modelBuilder.Entity<List>()
.HasRequired(c => c.ProjectGroup)
.WithMany(d=>d.Lists)
.WillCascadeOnDelete(false);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
when I created models, I got the following errors: problem with multi-cascade delete. I added modelbuilder in on model creating.
First I added no parameter in WithMany() and it doubled relations in database like this
This created many double relations between List and ProjectGroup (File, the same, read below).
when changed to WithMany(d=>d.Lists) relations looks ok, made only once like between File and ProjectGroup.
Does modelBuilder double the effect of [foreignKey] in model?
Can anyone explain how this works? Why did it double relation earlier, with no parameter in WithMany()

One to many in Entity Framework using fluent mapping

Im trying to do a one-to-many map by using fluent api.
This is my classes
public class Product : EntityBase
{
public Product()
{
this.ProductArticles = new List<ProductArticle>();
}
[Key]
public int ProductId { get; set; }
public string Description { get; set; }
public string ReportText1 { get; set; }
public string ReportText2 { get; set; }
public bool Standard { get; set; }
public int ProductGroupId { get; set; }
public decimal? Surcharge1 { get; set; }
public decimal? Surcharge2 { get; set; }
public decimal? Surcharge3 { get; set; }
public decimal? Surcharge4 { get; set; }
public decimal PriceIn { get; set; }
public decimal PriceOut { get; set; }
public decimal PriceArtisanIn { get; set; }
public decimal PriceArtisanOut { get; set; }
public decimal PriceTotalIn { get; set; }
public decimal PriceTotalOut { get; set; }
public decimal PriceTotalOutVat { get; set; }
public decimal PriceAdjustment { get; set; }
public bool Calculate { get; set; }
public string Notes { get; set; }
[ForeignKey("ProductGroupId")]
public virtual ProductGroup ProductGroup { get; set; }
public virtual ICollection<ProductArticle> ProductArticles { get; set; }
}
public class ProductArticle : EntityBase
{
[Key]
public int ProductArticleId { get; set; }
public int ProductId { get; set; }
public int ArticleId { get; set; }
public decimal Qty { get; set; }
public decimal PriceIn { get; set; }
public bool Primary { get; set; }
public virtual Product Product { get; set; }
public virtual Article Article { get; set; }
}
Now i want from single Product include all ProductArticles
This is my mapping
public class ProductMap : EntityTypeConfiguration<Product>
{
public ProductMap()
{
// Primary Key
this.HasKey(p => p.ProductId);
// Table & Column Mappings
this.ToTable("Product");
this.HasMany(p => p.ProductArticles)
.WithOptional()
.Map(p => p.MapKey("ProductId").ToTable("ProductArticle"));
}
But it doesnt work.. Please help :)
First - by convention EF treats property with name equal to Id or EntityTypeName + Id is a primary key. So, you don't need to configure that manually.
Second - if you don't want table names to be plural, just remove that convention from your context instead of providing table name for each entity mapping:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
}
And last - EF smart enough to define foreign keys which have names like RelatedEntityTypeName + Id. So, you don't need any fluent configurations here.

Categories