Disable CASCADE DELETE on a property to avoid different cascading paths - c#

I'm developing a library with Entity Framework 6.1.0 Code First, .NET Framework 4.5, C# and SQL Server.
I have these two classes:
public class User
{
public int UserId { get; set; }
public string UserName { get; set; }
public ICollection<Group> Groups { get; set; }
public ICollection<Message> MessagesSent { get; set; }
public ICollection<Message> MessagesReceived { get; set; }
}
public class Message
{
public int MessageId { get; set; }
public string Body { get; set; }
public DateTime DateSent { get; set; }
public int UserId { get; set; }
public User Sender { get; set; }
public ICollection<User> Recipients { get; set; }
}
And these configurations file:
public class UserConfiguration : EntityTypeConfiguration<User>
{
public UserConfiguration()
{
ToTable("User");
Property(u => u.UserName).IsRequired();
Property(u => u.UserName).HasMaxLength(50);
HasMany(u => u.Groups).
WithMany(g => g.Members).
Map(ug =>
{
ug.MapLeftKey("UserId");
ug.MapRightKey("GroupId");
ug.ToTable("UserGroup");
});
}
}
public class MessageConfiguration : EntityTypeConfiguration<Message>
{
public MessageConfiguration()
{
ToTable("Message");
Property(m => m.Body).IsRequired();
Property(m => m.Body).IsMaxLength();
Property(m => m.DateSent).IsRequired();
HasRequired(m => m.Sender).
WithMany(u => u.MessagesSent).
HasForeignKey(m => m.UserId);
HasMany(r => r.Recipients).
WithMany(m => m.MessagesReceived).
Map(mr =>
{
mr.ToTable("MessageRecipient");
mr.MapLeftKey("MessageId");
mr.MapRightKey("UserId");
});
}
}
The first time I run this project I get the following message:
Introducing FOREIGN KEY constraint 'FK_dbo.MessageRecipient_dbo.User_UserId' on table
'MessageRecipient' may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION
or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
Could not create constraint. See previous errors.
Users won't be deleted from my database, but I don't know how to fix this problem.
I think the problem is that a message has a sender (an user) and also has one or more recipients (user). This creates different cascade paths (which is not allowed with SQL Server).
How can I disable those cascade paths? Maybe on Message.Sender.

I have solved my problem.
Before I had on MessageConfiguration:
HasRequired(m => m.Sender).
WithMany(u => u.MessagesSent).
HasForeignKey(m => m.UserId);
Now I have:
HasRequired(m => m.Sender).
WithMany(u => u.MessagesSent).
HasForeignKey(m => m.UserId).
WillCascadeOnDelete(false);
The problem is that SQL Server doesn't allow multiple cascade paths. If you delete an user you could have two delete cascade on Message: one on Message.Sender and another one on Message.Recipients.

Related

Entity Framework does not load child table

There is a EF project that is used by an ASP.NET MVC app. The EF project was built on a legacy database (using a database-first approach).
When I try to load child records for a Machine the collection Maschinentagessaetze stays empty, even though there are records in the database with the correct foreign key.
Is there any way to debug this? I have no idea what the problem could be? From what I found on the internet, this should work.
// Controller
Machine = db09.Maschinen
.Include(x => x.Maschinentagessaetze)
.FirstOrDefault(x => x.DMasId == id);
// after this line, Machine.Maschinentagessaetze is empty
var MachineTagessaetzeList = db09.Maschinentagessaetze.Where(x=> x.OMaschineFk == id).ToList();
//after this line, MachineTagessaetzeList is filled with Machine also beeing loaded
//when this line is active, Machine.Maschinentagessaetze is filled
//whitout this line, it stays empty
// Models
[Table("t_Maschinen")]
public partial class TMaschinen
{
public TMaschinen()
{
Maschinentagessaetze = new HashSet<TMaschinentagessaetze>();
}
[Key]
[Column("d_MAS_ID")]
public int DMasId { get; set; }
[Column("o_Bezeichnung")]
public string OBezeichnung { get; set; } = null!;
public virtual ICollection<TMaschinentagessaetze> Maschinentagessaetze { get; set; }
}
[Table("t_Maschinentagessaetze")]
public partial class TMaschinentagessaetze
{
[Key]
[Column("d_MTA_ID")]
public int DMtaId { get; set; }
[Column("o_Maschinentagessatz")]
public decimal OMaschinentagessatz { get; set; }
[Column("o_Maschine_FK")]
public int OMaschineFk { get; set; }
// with [ForeignKey("OMaschineFk")] the problem remains
public TMaschinen TMaschinen { get; set; }
}
// DbContext
using Microsoft.EntityFrameworkCore;
public virtual DbSet<TMaschinen> Maschinen { get; set; } = null!;
public virtual DbSet<TMaschinentagessaetze> Maschinentagessaetze { get; set; } = null!;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TMaschinen>()
.HasMany(m => m.Maschinentagessaetze)
.WithOne(t => t.TMaschinen)
.HasForeignKey(m => m.OMaschineFk);
}
// In the database, there is a foreign key constraint on the child table:
ALTER TABLE [dbo].[t_Maschinentagessaetze] WITH CHECK
ADD CONSTRAINT [FK_t_Maschinentagessaetze_t_Maschinen]
FOREIGN KEY([o_Maschine_FK]) REFERENCES [dbo].[t_Maschinen] ([d_MAS_ID])
ON DELETE CASCADE
I consider you should refact OnModelCreating method:
Parent Entity
modelBuilder.Entity<TMaschinen>()
builder.ToTable("TMaschines");
builder.HasKey(i => i.Id);
builder.Property(i => i.Id)
.ValueGeneratedOnAdd();//you decide how to generate id
builder.Property(i => i.OBezeichnung )
.HasMaxLength(150)
.IsRequired();
Child Entity (Add propertites that are missing)
modelBuilder.Entity<TMaschinentagessaetze>()
builder.ToTable("TMaschinentagessaetzes");
builder.HasKey(i => i.Id);
builder.Property(i => i.Id)
.ValueGeneratedOnAdd(); //you decide how to generate id
.HasOne(m => m.TMaschinen )
.WithMany(t => t.Maschinentagessaetze )
.HasForeignKey(m => m.OMaschineFk);

Automapper - how to apply ForMember configuration at a deep level

I am trying to learn asp.NetCore 2.2. I am trying to setup a simple one page site. I have run into a problem with Automapper where manual Mappinng using forMember() is working at a top level for CreateMap<Listing, ListingSearchResultsDto>().ForMember(ListingPhotosUrl) but not at a lower level. I have another mapping CreateMap<User, UserDetailsDto>() where user contains an object Mylistings of type Listing. Mylistings is correctly auto mapped to ListingSearchResultsDto but manual configuration CreateMap<Listing, ListingSearchResultsDto>().ForMember(ListingPhotosUrl) is not applied.
I Have tried CreateMap<User, UserDetailsDto>().Formember(dest.Mylistings.ListingPhotosUrl,src.Mylistings.Photos.Url) but it seems that is not possible.
I Also tried this-> But no luck
var config = new MapperConfiguration(cfg =>
{
cfg.CreateMap<User, UserDetailsDto>();
cfg.CreateMap<Listing, ListingSearchResultsDto>()
.ForMember(dest => dest.ListingPhotosUrl, opt =>
{
opt.MapFrom(src => src.Photos.FirstOrDefault(p => p.IsMain).Url);
});
});
var mapper = config.CreateMapper();
The Code:
AutoMappperProfiles
public AutoMapperProfiles()
{
CreateMap<Listing, ListingSearchResultsDto>()
.ForMember(dest => dest.ListingPhotosUrl, opt =>
{
opt.MapFrom(src => src.Photos.FirstOrDefault(p => p.IsMain).Url);
});
CreateMap<User, UserDetailsDto>();
CreateMap<ListingPhoto, ListingPhotosDetailedDto>();
}
User
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public ICollection<Listing> MyListings { get; set; }
}
UserDetailsDto
public class UserDetailsDto
{
public int Id { get; set; }
public string Username { get; set; }
public ICollection<ListingSearchResultsDto> MyListings { get; set;}
}
Listing
public int id { get; set; }
public string Category { get; set; }
public string Title { get; set; }
public ICollection<ListingPhoto> Photos { get; set; }
ListingSearchResultsDto
public class ListingSearchResultsDto
{
public int id { get; set; }
public string Title { get; set; }
public string ListingPhotosUrl { get; set; }
}
I am using CreateMap<Listing, ListingSearchResultsDto>().Formember(des,src) to manually map a destination property ListingPhotosUrl. I have another mapping CreateMap<User, UserDetailsDto>(). Inside User & UsedetailsDto classes i have a objects called MyListings of types ICollection<Listing> and ICollection<ListingSearchResultsDto> respectively. MyListings object is auto mapped correctly but ListingPhotosUrl manual mapping is not being applied. CreateMap<Listing,ListingSearchResultsDto>.Formember(des,src)) manual mapping is working at top level, but not at deeper level inside CreateMap<User, UserDetailsDto>(), is there anyway to fix this? thanks
FIXED - Automapper was working fine. Issue in Entity Framework DbContext. I did not include the photos as related data in the EF Core method for loading USER data GETUSER(). It was working with EF Core method for loading LISTING GetListing() because i had an include for photos Include(p => p.Photos).
After adding .ThenInclude(p => p.Photos) in GetUser(), the photos were returned with USER data and automapper successfully mapped User data and ListingPhotosUrl manual mapping was applied successfully.
Entity Framework Core DbContext:
public async Task<User> GetUser(int id)
{
var user = await _context.Users
.Include(a => a.Avatar)
.Include(l => l.MyListings)
.ThenInclude(p => p.Photos)
.FirstOrDefaultAsync(u => u.Id == id);
return user;
}
public async Task<Listing> GetListing(int id)
{
var listing = await _context.Listings
.Include(p => p.Photos)
.FirstOrDefaultAsync(l => l.id == id);
return listing;
}

EntiyFramework-Update composite key value in join table with TryGetObjectByKey

I‘m using Entity framework code first ,the following model is a join table with composite key
Model code
public class Schedule
{
public int BabyId { get; set; }
public int VaccineId { get; set; }
public Baby Baby { get; set; }
public Vaccine Vaccine { get; set; }
public DateTime Time { get; set; }
}
Entity framework config code
public sealed class ScheduleConfig : EntityTypeConfiguration<Schedule>
{
public ScheduleConfig()
{
HasKey(q =>
new
{
q.BabyId,
q.VaccineId
});
HasRequired(t => t.Baby)
.WithMany(t => t.Schedules)
.HasForeignKey(t => t.BabyId);
HasRequired(t => t.Vaccine)
.WithMany(t => t.Schedules)
.HasForeignKey(t => t.VaccineId);
ToTable("Schedule", "dbo");
}
}
the following code update tables in database
public bool Update(T t)
{
var entityName = GetEntityName<T>();
object originalItem;
var key = ((IObjectContextAdapter)Context).ObjectContext.CreateEntityKey(entityName, t);
if (((IObjectContextAdapter)Context).ObjectContext.TryGetObjectByKey(key, out originalItem))
{
((IObjectContextAdapter)Context).ObjectContext.ApplyCurrentValues(key.EntitySetName, t);
}
Context.SaveChanges();
return true;
}
The problem is when I change one of key values ,the code inside If block in update method never execute and the table never updates.
My question is should I change my Model or is there any better code that can handle this problem.
Any idea?

How to fix: The number of properties in the Dependent and Principal Roles in a relationship constraint must be identical?

I am using Entity Framework 4.3.1 against a SQL Server 2012 database and I am using the POCO approach. I am getting the following error and I am wondering if anyone can explain how to fix it:
ModelValidationException
One or more validation errors were detected during model generation:
\tSystem.Data.Entity.Edm.EdmAssociationConstraint: : The number of properties in the Dependent and Principal Roles in a relationship constraint must be identical.
There is no InnerException available for any further information.
I cannot change the database schema and it is a little odd, but here it is...
** are the primary key (notice I have composite primary keys)
(FK) Denotes a foreign key
Here are the tables (if it helps I can post the SQL to generate them but I do not think the tables are actually the problem as the exception is in the validation of the model):
One
-
**OneId int not null
**TwoId int not null (FK)
**ThreeId int not null (FK)
Name nvarchar(50) not null
Two
-
**TwoId int not null
**ThreeId int not null (FK)
Name nvarchar(50) not null
Three
-
**ThreeId not null
Name nvarchar(50) not null
Here are the entities (notice that I am including the foreign keys in the model but other than that pretty standard):
public class Three
{
public int ThreeId { get; set; }
public string Name { get; set; }
public virtual ICollection<Two> Twos { get; private set; }
public virtual ICollection<One> Ones { get; private set; }
public void AddOne(One one)
{
if (one == null)
throw new ArgumentNullException("two");
if (Ones == null)
Ones = new List<One>();
if (!Ones.Contains(one))
Ones.Add(one);
one.Three = this;
}
public void AddTwo(Two two)
{
if (two == null)
throw new ArgumentNullException("two");
if (Twos == null)
Twos = new List<Two>();
if (!Twos.Contains(two))
Twos.Add(two);
two.Three = this;
}
}
public class Two
{
public int TwoId { get; set; }
public int ThreeId { get; set; }
public string Name { get; set; }
public virtual Three Three { get; set; }
public virtual ICollection<One> Ones { get; private set; }
public void AddOne(One one)
{
if (one == null)
throw new ArgumentNullException("two");
if (Ones == null)
Ones = new List<One>();
if (!Ones.Contains(one))
Ones.Add(one);
one.Two = this;
}
}
public class One
{
public int OneId { get; set; }
public int TwoId { get; set; }
public int ThreeId { get; set; }
public virtual Two Two { get; set; }
public virtual Three Three { get; set; }
}
And here is the data context:
public class DbCtx : DbContext
{
public DbCtx(string connectionString)
: base(connectionString)
{
Ones = Set<One>();
Twos = Set<Two>();
Threes = Set<Three>();
}
public DbSet<One> Ones { get; private set; }
public DbSet<Two> Twos { get; private set; }
public DbSet<Three> Threes { get; private set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
var one = modelBuilder.Entity<One>();
one.ToTable("One");
one.HasKey(d => new
{
d.OneId,
d.TwoId,
d.ThreeId
});
one.Property(d => d.OneId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
one.HasRequired(t => t.Two)
.WithMany(s => s.Ones)
.HasForeignKey(t => t.TwoId);
one.HasRequired(t => t.Three)
.WithMany(s => s.Ones)
.HasForeignKey(t => t.ThreeId);
var two = modelBuilder.Entity<Two>();
two.ToTable("Two");
two.HasKey(d => new
{
d.TwoId,
d.ThreeId
});
two.Property(p => p.TwoId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
two.HasRequired(t => t.Three)
.WithMany(s => s.Twos)
.HasForeignKey(t => t.ThreeId);
var three = modelBuilder.Entity<Three>();
three.ToTable("Three");
three.HasKey(s => s.ThreeId);
three.Property(p => p.ThreeId)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
base.OnModelCreating(modelBuilder);
}
}
Finally, this is a snippet of code to cause the exception:
using (var ctx = new DbCtx(#"....."))
{
Console.WriteLine(ctx.Twos.Count());
}
The reason for the error are incorrectly configured relations in your model. This is not correct:
one.HasRequired(t => t.Two)
.WithMany(s => s.Ones)
.HasForeignKey(t => t.TwoId);
one.HasRequired(t => t.Three)
.WithMany(s => s.Ones)
.HasForeignKey(t => t.ThreeId);
It should be:
one.HasRequired(t => t.Two)
.WithMany(s => s.Ones)
.HasForeignKey(t => new { t.TwoId, t.ThreeId });
Because dependent's FK must contain all columns of principal PK. You must also remove navigation property from Three to One.
This can also be caused by Code first from Database.
I had several views that I brought in that did not have an obvious key field according to Entity Framework conventions. The code generated put the [Key] attribute on the wrong field. In fact, it could not detect any uniqueness, so it put the [Key] attribute on all the fields.
I was able to remove all of the extra Key attributes to make the error go away.
Note for EF5+:
.HasForeignKey has been deprecated from EF 5: List of available methods (https://msdn.microsoft.com/en-us/library/system.data.entity.modelconfiguration.configuration.manytomanyassociationmappingconfiguration_methods(v=vs.103).aspx)
- MapLeftKey
- MapRightKey
- ToTable
If one were to need Many to Many where one 'Many' is to an Entity with a CompositeKey is:
one.HasKey(t => new { t.TwoId, t.ThreeId });
one.HasRequired(t => t.Two)
.WithMany(s => s.Ones)
.Map(m=>m.MapLeftKey("OneId").MapRIghtKey(new string[]{"TwoId", "ThreeId"}))

Difficulty Concerning EF Code First Fluent API, TPH, and Foreign Keys

I have two tables in my database. One is called Users, and the other is called Widgets. The Widgets table represents 3 entities in my code model. One of the entities, Widget, is a parent class for the other two entities, WidgetTypeA and WidgetTypeB. Both WidgetTypeA and WidgetTypeB have navigation properties to the User entity, which is persisted to the Users table in the database. I'm having trouble getting Code First to use the same foreign key for both the WidgetTypeA and WidgetTypeB entities (UserId). Does anyone know how to do this? It seems like it should be a common problem with Table Per Hierarchy mapping.
My entity classes are as follows:
public class Widget
{
public int Id { get; set; }
public string Name { get; set; }
}
class WidgetMap : EntityTypeConfiguration<Widget>
{
public WidgetMap()
{
ToTable("Widgets");
HasKey(w => w.Id);
Property(w => w.Id)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
Property(w => w.Name)
.IsRequired()
.HasMaxLength(75)
.IsUnicode(true);
}
}
public class WidgetTypeA : Widget
{
public int UserId { get; set; }
public virtual User User { get; set; }
public string Color { get; set; }
public int DepthLevel { get; set; }
}
class WidgetTypeAMap : EntityTypeConfiguration<WidgetTypeA>
{
public WidgetTypeAMap()
{
Map(w => w.Requires("WidgetTypeId").HasValue(1));
HasRequired(w => w.User)
.WithMany(u => u.WidgetTypeAs)
.HasForeignKey(w => w.UserId)
.WillCascadeOnDelete(false);
Property(w => w.Color)
.IsOptional()
.IsUnicode(true)
.HasMaxLength(75);
Property(w => w.DepthLevel)
.IsOptional();
}
}
public class WidgetTypeB : Widget
{
public int UserId { get; set; }
public virtual User User { get; set; }
}
class WidgetTypeBMap : EntityTypeConfiguration<WidgetTypeB>
{
public WidgetTypeBMap()
{
Map(w => w.Requires("WidgetTypeId").HasValue(2));
HasRequired(w => w.User)
.WithMany(u => u.WidgetTypeBs)
.HasForeignKey(w => w.UserId)
.WillCascadeOnDelete(false);
}
}
public class User
{
public int Id { get; set; }
public string Username { get; set; }
public int Age { get; set; }
public virtual ICollection<WidgetTypeA> WidgetTypeAs { get; set; }
public virtual ICollection<WidgetTypeB> WidgetTypeBs { get; set; }
}
class UserMap : EntityTypeConfiguration<User>
{
public UserMap()
{
ToTable("Users");
HasKey(u => u.Id);
Property(u => u.Username)
.IsRequired()
.HasMaxLength(75)
.IsUnicode(true);
Property(u => u.Age)
.IsRequired();
}
}
At any rate, I keep getting the error
Invalid column name 'UserId1'
when I try to perform the following operations:
using (var entities = new MyEntities())
{
User u = new User
{
Username = "Frank",
Age = 14
};
entities.Users.Add(u);
entities.SaveChanges();
WidgetTypeA wa1 = new WidgetTypeA
{
Name = "0SDF81",
UserId = u.Id,
DepthLevel = 6
};
entities.WidgetTypeAs.Add(wa1);
entities.SaveChanges();
}
Not sure if this can be fixed or not. I can always specify a second UserId foreign key for the Widgets table, but that seems pointless. Perhaps there's a way to do this using Fluent API?
You cannot map properties defined in different derived entities to the same column. That is limitation in EF. If your WidgetTypeA has UserId property and your WidgetTypeB has UserId property they must be different columns in the database. It should work if you move both UserId and User properties from derived types to the parent Widget type.
I know its a long way late, but hopefully may help other readers.
Although Ladislav was correct that using a mapped Foreign Key is not supported in EF6, I did find a useful workaround.
It is possible to define a computed column specification whose expression simply refers to the original column. Userid in the description above. This can be used as the discriminator for the TPH mapping. With this approach, the column need not be persisted, but can be used for TPH, with the original column being available for use as a foreign key.

Categories