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.
Related
I am trying to set up audit properties for each of my Entities with an abstract Base class
public abstract class Base
{
public bool IsActive { get; set; }
public bool IsDeleted { get; set; }
public int CreatedByUserId { get; set; }
[ForeignKey("CreatedByUserId")]
public virtual User CreatedBy { get; set; }
public int ModifiedByUserId { get; set; }
[ForeignKey("ModifiedByUserId")]
public virtual User ModifiedBy { get; set; }
public DateTime DateCreated { get; set; }
public DateTime DateModified { get; set; }
}
Somehow the Data Annotations doesn't work in EF Core but was working in my EF 6 Project
I am now receiving this error:
Unable to determine the relationship represented by navigation 'Address.CreatedBy' of type 'User'. Either manually configure the relationship, or ignore this property using the '[NotMapped]' attribute or by using 'EntityTypeBuilder.Ignore' in 'OnModelCreating'.
These are my models:
public class Address : Base
{
public int Id { get; set; }
public string StringAddress { get; set; }
public string City { get; set; }
public string State { get; set; }
public string ZipCode { get; set; }
public int UserId { get; set; }
public User User { get; set; }
}
public class User : Base
{
public int Id { get; set; }
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
public string Email { get; set; }
public string ContactNumber { get; set; }
public string SecondaryContactNumber { get; set; }
public int RoleId { get; set; }
public Role Role { get; set; }
public HashSet<Address> Addresses { get; set; }
}
What's weird is when I remove the Base inheritance from my other entities apart from User, EF Core is able to set the FK without any errors.
How do I configure it manually with Fluent API?
I already have a BaseConfig class as starting point to be inherited by my other entity config classes:
public class BaseConfig<TEntity> : IEntityTypeConfiguration<TEntity> where TEntity : Base
{
public virtual void Configure(EntityTypeBuilder<TEntity> builder)
{
builder.Property(x => x.DateCreated).HasDefaultValueSql("GETDATE()");
builder.Property(x => x.DateModified).HasDefaultValueSql("GETDATE()");
// Am I setting this correctly?
builder
.HasOne(b => b.CreatedBy)
.WithMany()
.HasForeignKey(p => p.CreatedByUserId);
}
}
Here are the 2 entities I have defined and I am using EF Core 3.0.
Whenever I try to create a new migration, I keep getting an error saying can not have the same navigation property in multiple relationships.
Me being a noob in EF Core, any pointers in resolving this issue would be very helpful.
public class Event
{
public Guid Id { get; set; }
public EventContext StartContext { get; set; }
public EventContext EndContext { get; set; }
public Guid CreatedBy { get; set; }
public Guid UpdatedBy { get; set; }
}
public class EventContext
{
public Guid Id { get; set; }
public Guid EventId { get; set; } //fk
public Event Event { get; set; }
public DateTime ApplicableDate { get; set; }
}
As the EF Core tools will tell you:
Cannot create a relationship between 'Event.EndContext' and 'EventContext.Event', because there already is a relationship between 'Event.StartContext' and 'EventContext.Event'. Navigation properties can only participate in a single relationship.
There are two simple solutions to this issue:
Use two navigation properties on the EventContext type
If you actually need the navigation property on EventContext and not just on Event, then the following will work:
public class Event
{
public Guid Id { get; set; }
public Guid CreatedBy { get; set; }
public Guid UpdatedBy { get; set; }
public EventContext StartContext { get; set; }
public EventContext EndContext { get; set; }
}
public class EventContext
{
public Guid Id { get; set; }
public Guid EventId { get; set; }
public DateTime ApplicableDate { get; set; }
// One navigation property for each corresponding navigation property on `Event`.
public Event StartContextEvent { get; set; }
public Event EndContextEvent { get; set; }
}
public class Context : DbContext
{
public DbSet<Event> Events { get; set; }
public DbSet<EventContext> EventContexts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Event>(
entity =>
{
entity.HasOne(e => e.StartContext)
.WithOne(e => e.StartContextEvent)
.HasForeignKey<EventContext>(e => e.EventId);
entity.HasOne(e => e.EndContext)
.WithOne(e => e.EndContextEvent)
.HasForeignKey<EventContext>(e => e.EventId);
});
}
}
Use a one-way navigation property
If you can live without the navigation property on EventContext and are happy to just use the one on Event, then just remove it from EventContext and the following will work for you as well:
public class Event
{
public Guid Id { get; set; }
public Guid CreatedBy { get; set; }
public Guid UpdatedBy { get; set; }
public EventContext StartContext { get; set; }
public EventContext EndContext { get; set; }
}
public class EventContext
{
public Guid Id { get; set; }
public Guid EventId { get; set; }
public DateTime ApplicableDate { get; set; }
}
public class Context : DbContext
{
public DbSet<Event> Events { get; set; }
public DbSet<EventContext> EventContexts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Event>(
entity =>
{
entity.HasOne(e => e.StartContext)
.WithOne()
.HasForeignKey<EventContext>(e => e.EventId);
entity.HasOne(e => e.EndContext)
.WithOne()
.HasForeignKey<EventContext>(e => e.EventId);
});
}
}
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.
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.
I'm using CodeFirst for my devemopment. For all model classes in my Entity I have a base class named CommonFields
public class CommonFields
{
public int Status { get; set; }
public DateTime CreatedOn { get; set; }
public int CreaedBy { get; set; }
public DateTime ModifiedOn { get; set; }
public int ModifiedBy { get; set; }
}
And, for eg. I have two classes like
public class Employee : CommonFields
{
public int Id { get; set; }
public string Name { get; set; }
//Other properties
}
public class User : CommonFields
{
public int Id { get; set; }
public string Name { get; set; }
//Other properties
}
How can I set relation from CreatedBy & ModifiedBy to User table. I just need only one directional mapping.
I need to get User information when I write objEmployee.CreatedUser
Thanks.
public class CommonFields
{
public int Status { get; set; }
public DateTime CreatedOn { get; set; }
public int? CreatedById { get; set; }
public virtual User CreatedBy { get; set; }
public DateTime ModifiedOn { get; set; }
public virtual User ModifiedBy { get; set; }
public int? ModifiedById { get; set; }
}
and you have to define relations for all derived entities in your DbContext
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<User>().HasOptional(x => x.CreatedBy).WithMany().HasForeignKey(x => x.CreatedById).WillCascadeOnDelete(false);
mb.Entity<User>().HasOptional(x => x.ModifiedBy).WithMany().HasForeignKey(x => x.ModifiedById).WillCascadeOnDelete(false);
mb.Entity<Employee>().HasOptional(x => x.CreatedBy).WithMany().HasForeignKey(x => x.CreatedById).WillCascadeOnDelete(false);
mb.Entity<Employee>().HasOptional(x => x.ModifiedBy).WithMany().HasForeignKey(x => x.ModifiedById).WillCascadeOnDelete(false);
}
EDIT:
Or you could use TPH. Then your model creating look like this
protected override void OnModelCreating(DbModelBuilder mb)
{
mb.Entity<CommonFields>().
.Map(x => x.ToTable("Users"))
.Map<User>(x => x.Requires("__type").HasValue(1)
.Map<Employee>(x => x.Requires("__type").HasValue(2);
mb.Entity<CommonFields>().HasOptional(x => x.CreatedBy).WithMany().HasForeignKey(x => x.CreatedById).WillCascadeOnDelete(false);
mb.Entity<CommonFields>().HasOptional(x => x.ModifiedBy).WithMany().HasForeignKey(x => x.ModifiedById).WillCascadeOnDelete(false);
}