Prevent Cascade delete when the client is deleted - entity-framework-core

I'm having following configurations and relationships. i.e. when a Meal is deleted, relevant MealSubMeals should get deleted but not SubMeal. This is working as expected with following configuration. However, I don't want any MealSubMeals get deleted when a SubMeal is deleted. Instead, EF should throw an error when attempted to delete a SubMeal while MealSubMeals exist.
How do I achieve above? I probably don't want any cyclic relationships within my db.
Thanks!
public class Meal
{
public int Id { get; set; }
public string? MealName { get; set; }
public ICollection<MealSubMeal>? MealSubMeals { get; set; }
}
public class MealSubMeal
{
public int Id { get; set; }
public int MealId { get; set; }
public Meal? Meal { get; set; }
public bool Optional { get; set; }
public decimal SubMealPrice { get; set; }
public int SubMealId { get; set; }
public SubMeal? SubMeal { get; set; }
}
public class SubMeal
{
public int Id { get; set; }
public string? SubMealName { get; set; }
public string? SubMealDescription { get; set; }
}
public class MealConfiguration : IEntityTypeConfiguration<Meal>
{
public void Configure(EntityTypeBuilder<Meal> builder)
{
builder.Property(t => t.MealName).HasMaxLength(40).IsRequired();
builder.HasIndex(t => t.MealName).IsUnique();
builder.Property(t => t.Price).HasPrecision(10, 2).IsRequired();
}
}
public class MealSubMealConfiguration : IEntityTypeConfiguration<MealSubMeal>
{
public void Configure(EntityTypeBuilder<MealSubMeal> builder)
{
builder.HasOne(t => t.Meal).WithMany(t => t.MealSubMeals).HasForeignKey(t => t.MealId).IsRequired();
builder.HasOne(t => t.SubMeal).WithMany().HasForeignKey(t => t.SubMealId).IsRequired();
builder.HasIndex(t => new { t.MealId, t.SubMealId }).IsUnique();
builder.Property(t => t.SubMealPrice).HasPrecision(10, 2).IsRequired();
}
}
public class SubMealConfiguration : IEntityTypeConfiguration<SubMeal>
{
public void Configure(EntityTypeBuilder<SubMeal> builder)
{
builder.Property(t => t.SubMealName).HasMaxLength(40).IsRequired();
builder.HasIndex(t => t.SubMealName).IsUnique();
builder.Property(t => t.SubMealDescription).HasMaxLength(200);
}
}

Related

EF Core can't include children in parent

I'm quite new to EF Core.
In my DB Context:
// STEP
modelBuilder.Entity<CorsoStepS>().HasKey(x => new { x.codCorso, x.codStep });
modelBuilder.Entity<CorsoStepS>()
.HasMany(step => step.CorsoStepLezioni)
.WithOne(lez => lez.CorsoStep)
.HasForeignKey(lez => new { lez.codCorso, lez.codStep });
modelBuilder.Entity<CorsoStepS>().Ignore(step => step.CorsoStepLezioni);
////
// LEZIONI
modelBuilder.Entity<CorsoStepLezione>().HasKey(x => new { x.codCorso, x.codStep, x.codLezione });
modelBuilder.Entity<CorsoStepLezione>()
.HasMany(lez => lez.Sessioni)
.WithOne(sess => sess.Lezione)
.HasForeignKey(sess => new { sess.codCorso, sess.codStep, sess.codLezione });
modelBuilder.Entity<CorsoStepLezione>().Ignore(lez => lez.Sessioni);
////
// SESSIONI
modelBuilder.Entity<CorsoStepLezioneSessione>().HasKey(x => new { x.codCorso, x.codStep, x.codLezione, x.codSessione });
modelBuilder.Entity<CorsoStepLezioneSessione>()
.HasMany(sess => sess.Iscrizioni)
.WithOne(iscr => iscr.Sessione)
.HasForeignKey(iscr => new { iscr.CodCorso, iscr.CodStep, iscr.CodLezione, iscr.CodSessione });
modelBuilder.Entity<CorsoStepLezioneSessione>().Ignore(sess => sess.Iscrizioni);
////
My entities:
public class CorsoStepS
{
public int codCorso { get; set; }
public int codStep { get; set; }
public string nome { get; set; }
public int maxPartecipanti { get; set; }
public int order { get; set; }
public virtual Corso Corso { get; set; }
public virtual ICollection<CorsoStepLezione> CorsoStepLezioni { get; set; }
}
public class CorsoStepLezione
{
public int codCorso { get; set; }
public int codStep { get; set; }
public int codLezione { get; set; }
public string nome { get; set; }
public CorsoStepS CorsoStep { get; set; }
public virtual ICollection<CorsoStepLezioneSessione> Sessioni { get; set; }
}
public class CorsoStepLezioneSessione
{
public int codCorso { get; set; }
public int codStep { get; set; }
public int codLezione { get; set; }
public int codSessione { get; set; }
public DateTime? data { get; set; }
public string ora { get; set; }
public int maxPartecipanti { get; set; }
public virtual CorsoStepLezione Lezione { get; set; }
public virtual ICollection<CorsoStepLezioniSessioniIscrizione> Iscrizioni { get; set; }
}
When I call:
var lezioniCorso = _clienteContext.CorsoStepLezioni
.Include(lezione => lezione.Sessioni);
it gives me:
The expression 'lezione.Sessioni' is invalid inside an 'Include' operation, since it does not represent a property access: 't => t.MyProperty'.
But if I call:
var lezioniCorso = _clienteContext.CorsoStepLezioni
.Include(lezione => lezione.CorsoStep);
it's ok.
what am I doing wrong? I'm going stupid
It is because of this line
modelBuilder.Entity<CorsoStepLezione>().Ignore(lez => lez.Sessioni);
First you are telling EF to build up the relationship and then immediately ignore it again so EF acts as if this property does not exist.

EntityTypeConfiguration for base classes with One-To-Many

I am unable to configure EntityFramework migration to create DB structure by the hierarchy provided.
Consider the following:
public class EntityBase
{
public EntityBase()
{
UId = Guid.NewGuid();
CreateTime = DateTime.UtcNow;
}
[Key]
public Guid UId { get; set; }
}
public class Matrix : EntityBase
{
public Guid PassportUId { get; set; }
public virtual Contracts.Entities.Passport Passport { get; set; }
public DeviceBlockType Type { get; set; }
public virtual ICollection<DeviceBlockBase> Blocks { get; set; }
}
public class DeviceBlockBase: EntityBase
{
public string State { get; set; }
public Guid? MatrixUId { get; set; }
public virtual Matrix Matrix { get; set; }
}
public class ArrayBlock: DeviceBlockBase
{
public string LotNumber { get; set; }
public string TransNum { get; set; }
public Guid? LockoutByUId { get; set; }
public virtual User LockoutBy { get; set; }
public DateTime? LockoutFrom { get; set; }
public DateTime? LockoutTill { get; set; }
public Guid? LockoutReasonUId { get; set; }
public virtual LockoutReason LockoutReason { get; set; }
}
public class Container : ArrayBlock
{
public DateTime FillDate { get; set; }
public bool? IsLearned { get; set; }
}
public class Cartridge: ArrayBlock
{
public DateTime LoadDate { get; set; }
public Guid LoaderUid { get; set; }
public User Loader { get; set; }
public bool IsOpen { get; set; }
}
public class Vault: DeviceBlockBase
{
public DateTime FillDate { get; set; }
}
Edited (Configuration class):
public abstract class BaseBlockDeviceConfiguration<T> : EntityTypeConfiguration<T> where T : DeviceBlockBase
{
protected BaseBlockDeviceConfiguration()
{
HasOptional(x => x.MedicationMatrix)
.WithMany()
.HasForeignKey(x => x.MedicationMatrixUId);
}
}
public abstract class BaseArrayBlockConfiguration<T> : BaseBlockDeviceConfiguration<T> where T : ArrayBlock
{
protected BaseArrayBlockConfiguration()
{
HasOptional(x => x.LockoutBy)
.WithMany()
.HasForeignKey(x => x.LockoutByUId)
.WillCascadeOnDelete(false);
HasOptional(x => x.LockoutReason)
.WithMany()
.HasForeignKey(x => x.LockoutReasonUId)
.WillCascadeOnDelete(false);
}
}
public class CartridgeConfiguration : BaseArrayBlockConfiguration<Cartridge>
{
public CartridgeConfiguration()
{
HasOptional(p => p.Loader)
.WithMany()
.HasForeignKey(p => p.LoaderUid)
.WillCascadeOnDelete(false);
}
}
public class ContainerConfiguration : BaseArrayBlockConfiguration<Container>
{
public ContainerConfiguration()
{
}
}
public class VaultConfiguration : BaseBlockDeviceConfiguration<Vault>
{
public VaultConfiguration()
{
}
}
The EntityTypeConfiguration builds the DB in somewhat weird structure:
RenameTable(name: "dbo.Cartridges", newName: "DeviceBlockBases");
AddColumn("dbo.DeviceBlockBases", "FillDate", c => c.DateTime());
AddColumn("dbo.DeviceBlockBases", "IsLearned", c => c.Boolean());
AddColumn("dbo.DeviceBlockBases", "FillDate1", c => c.DateTime());
AddColumn("dbo.DeviceBlockBases", "FillDate2", c => c.DateTime());
AddColumn("dbo.DeviceBlockBases", "Discriminator", c => c.String(nullable: false, maxLength: 128));
DropTable("dbo.Containers");
It looks like EF CodeFirst refuses to generate polymorphism and is going wild.
Any ideas?
Thanks ahead

EF code first - one-to-one-or-zero

I have 2 entities:
public partial class GPSdevice
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public GPSdevice()
{
}
public int ID { get; set; }
public string UserName { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual Truck Truck { get; set; }
}
public partial class Truck
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Truck()
{
}
public int TruckID { get; set; }
public string TruckNo { get; set; }
public string Make { get; set; }
[ForeignKey("GPSdevice")]
public int? GPSdeviceID { get; set; }
public virtual GPSdevice GPSdevice { get; set; }
}
I want to create a relationship one-to-one-or-zero (each GPSdevice can be linked to any truck (but to one and only one) or not linked at all)
I write the following code:
modelBuilder.Entity<GPSdevice>()
.HasOptional(e => e.Truck)
.WithOptionalPrincipal()
.WillCascadeOnDelete(false);
but it creates the following migration:
public override void Up()
{
AddColumn("dbo.Truck", "GPSdevice_ID", c => c.Int());
CreateIndex("dbo.Truck", "GPSdeviceID");
CreateIndex("dbo.Truck", "GPSdevice_ID");
AddForeignKey("dbo.Truck", "GPSdeviceID", "dbo.GPSdevices", "ID");
AddForeignKey("dbo.Truck", "GPSdevice_ID", "dbo.GPSdevices", "ID");
}
why it creates one more field and how to use the current field GPSdeviceID instead?
ADDED:
If I remove
public int? GPSdeviceID { get; set; }
and add MapKey:
modelBuilder.Entity<GPSdevice>()
.HasOptional(e => e.Truck)
.WithOptionalPrincipal(e=>e.GPSdevice).Map(p=>p.MapKey("GPSdeviceID"))
.WillCascadeOnDelete(false);
in result I get the following migration code:
public override void Up()
{
CreateIndex("dbo.Truck", "GPSdeviceID");
AddForeignKey("dbo.Truck", "GPSdeviceID", "dbo.GPSdevices", "ID");
}
then I get the following error:
There are no primary or candidate keys in the referenced table
'dbo.GPSdevices' that match the referencing column list in the foreign
key 'FK_dbo.Truck_dbo.GPSdevices_GPSdeviceID'. Could not create
constraint or index. See previous errors.
Use this :
public partial class GPSdevice
{
public GPSdevice()
{
}
public int ID { get; set; }
public string UserName { get; set; }
public virtual Truck Truck { get; set; }
}
public partial class Truck
{
public Truck()
{
}
public int TruckID { get; set; }
public string TruckNo { get; set; }
public string Make { get; set; }
public virtual GPSdevice GPSdevice { get; set; }
}
modelBuilder.Entity<GPSdevice>()
.HasOptional(o => o.Truck)
.WithRequired(ad => ad.GPSdevice);
modelBuilder.Entity<Truck>()
.HasKey(e => e.TruckID);

Json response does not contain all the navigation properties EntityFramework Core and ASP .NETCore Web API

I have migrated from Entity Framework 6 to EF Core and also Web Api .net framework to .net core.
I have many to many relationship that I have set up as follows
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var instrumentsToPlaces = modelBuilder.Entity<InstrumentPlace>();
instrumentsToPlaces.ToTable("InstrumentsToPlaces");
instrumentsToPlaces.HasKey(x => new { x.PlaceId, x.InstrumentId });
instrumentsToPlaces.HasOne(i => i.Instrument)
.WithMany(p => p.InstrumentsPlaces)
.HasForeignKey(ip => ip.InstrumentId);
instrumentsToPlaces.HasOne(p => p.Place)
.WithMany(i => i.InstrumentsPlaces)
.HasForeignKey(ip => ip.PlaceId);
var instrumentsToStyle = modelBuilder.Entity<InstrumentStyle>();
instrumentsToStyle.ToTable("InstrumentsToStyles");
instrumentsToStyle.HasKey(x => new { x.StyleId, x.InstrumentId });
instrumentsToStyle.HasOne(i => i.Instrument)
.WithMany(s => s.InstrumentStyles)
.HasForeignKey(si => si.InstrumentId);
instrumentsToStyle.HasOne(s => s.Style)
.WithMany(i => i.InstrumentStyles)
.HasForeignKey(si => si.StyleId);
}
I have included the navigation properties in the repository method as follows
public Instrument GetInstrumentByName(string name)
{
using (var starsAndCatzDbContext = new StarsAndCatzDbContext())
{
var instrument = _starsAndCatzDbContext.Instruments
.Include(a=>a.InstrumentsPlaces)
.ThenInclude(a=>a.Place)
.Include(a=>a.InstrumentStyles)
.ThenInclude(a=>a.Style)
.FirstOrDefault(i => i.Name == name);
return instrument;
}
}
Here are the classes
public class Instrument {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<InstrumentPlace> InstrumentsPlaces { get; set; }
public virtual ICollection<InstrumentStyle> InstrumentStyles { get; set; }
}
public class InstrumentPlace
{
public int InstrumentId { get; set; }
public Instrument Instrument { get; set; }
public int PlaceId { get; set; }
public Place Place { get; set; }
}
public class InstrumentStyle
{
public int InstrumentId { get; set; }
public Instrument Instrument { get; set; }
public int StyleId { get; set; }
public Style Style { get; set; }
}
public class Style {
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<InstrumentStyle> InstrumentStyles { get; set; }
}
public class Place {
public int Id { get; set; }
public string Name { get; set; }
public string Division { get; set; }
public int Tier { get; set; }
public string State { get; set; }
public string Postcode { get; set; }
public float? Latitude { get; set; }
public float? Longitude { get; set; }
public virtual ICollection<InstrumentPlace> InstrumentsPlaces { get; set; }
}
The WebAPI method to be called is
[HttpGet("GetInstrumentByName/{suburb}/{instrument}"), Produces("application/json")]
public Instrument GetInstrumentByName(string suburb, string instrument)
{
try
{
var result = _instrumentRepository.GetInstrumentByName(instrument);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return new Instrument();
}
}
When I send the request to "/api/instruments/west-end/guitar" I get the expected result when I place a breakpoint before sending the response as follows
As you notice, the Navigation properties are loaded (when I expand the collections I can see all the properties being loaded as well).
However the json response I receive is the following
Any suggestions or am I missing something here?
Thank you all in advanced
Thanks #H. Herzl for giving me a hint.
The solution was found in this other question
services.AddMvc().AddJsonOptions(x => x.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore);
https://stackoverflow.com/a/40501464/1513346

Entity Framework Many To Many returns null

I have Many To Many relationship defined and when I try to query for the records that should be in the map I get null.
public class Record
{
public int RecordId { get; set; }
public DateTime DateRecordCreated { get; set; }
public ICollection<Street> Streets { get; set; }
public ICollection<Street> CrossStreets { get; set; }
}
public class RecordMap : EntityTypeConfiguration<Record>
{
public RecordMap()
{
// Primary Key
this.HasKey(t => t.RecordId);
this.HasMany(r => r.Streets)
.WithMany(c => c.Records)
.Map(sl =>
{
sl.ToTable("StreetRecordMap", "dbo");
sl.MapLeftKey("RecordId");
sl.MapRightKey("StreetId");
});
this.HasMany(r => r.CrossStreets)
.WithMany(c => c.AnotherRecord)
.Map(sl =>
{
sl.ToTable("AnotherStreetRecordMap", "dbo");
sl.MapLeftKey("RecordId");
sl.MapRightKey("StreetId");
});
this.Property(t => t.DateRecordCreated).IsRequired();
}
}
public class House : Record
{
public string HouseNumber { get; set; }
public string StreeName { get; set; }
public int ZipCode { get; set; }
}
public class Street
{
public int StreetId { get; set; }
public string StreetName { get; set; }
public ICollection<Record> Records { get; set; }
public ICollection<Record> AnotherRecord { get; set; }
}
Now when I run the following query below I get houses.CrossStreets as null, I tried adding enabling lazy loading and had the same out come.
public static void GetRecords()
{
using (var context = new SandboxContext())
{
var entities = context.Houses.Include(r => r.CrossStreets);
var houses = entities.ToList();
}
}