Can you narrow down entity framework one to many - c#

I have three lists in an object that all use the same variable, right now to build the one to many relationship I'm using:
modelBuilder
.Entity<Object>()
.HasMany(u => u.ListA)
.WithRequired()
.HasForeignKey(s => s.ObjectId);
modelBuilder
.Entity<Object>()
.HasMany(u => u.ListB)
.WithRequired()
.HasForeignKey(s => s.ObjectId);
modelBuilder
.Entity<Object>()
.HasMany(u => u.ListC)
.WithRequired()
.HasForeignKey(s => s.ObjectId);
The list item has a type property that corresponds to list A B and C, Is there a way to further refine those statements to add something similar to :
where (t=>t.type == A)
Object has 3 lists that all hold the same object type the only difference between the list items is their type property.
list object properties:
public int Id { get; set; }
public EntryType Type { get; set; }
public string Value { get; set; }
public int SequenceNumber { get; set; }
public virtual StatusReport StatusReport { get; set; }
public int StatusReportId { get; set; }

Related

EF Core – Complicated Relationships

I have complicated relationships between these entities:
Country
Airport
Airline
Flight
Country has many Airlines and many Airports.
Airline has one Countries, the same about Airport.
Airline has many Flights,
Airport has many DepartureFlights and ArrivalFlights (both are Flight type).
Flight has one Airline and one DepartureAirport and one ArrivalAirport (both are Airport type).
Country can have no airlines and airports,
Airline can have no Flights,
Airport can have neither DepartureFlights nor ArrivalFlights.
What I am trying to do is when Country is deleted, then all related Airlines and Airports are deleted, also when Airline or DepartureAirport or ArrivalAirport are deleted, all related Flights are deleted also,
but when updating my db after the migration is created I'm getting an error:
Introducing FOREIGN KEY constraint "FK_Flights_Airports_DepartureAirport" on table "Flights" may cause cycles or multiple cascade paths. Specify ON DELETE NO ACTION or ON UPDATE NO ACTION, or modify other FOREIGN KEY constraints.
How to implement this behavior and prevent an error?
Here are my models:
Country:
public class Country
{
public int Id { get; set; }
/* other properties */
public virtual ICollection<Airport>? Airports { get; set; }
public virtual ICollection<Airline>? Airlines { get; set; }
}
Airline:
public class Airline
{
public int Id { get; set; }
/* other properties */
public int CountryId { get; set; }
public virtual Country Country { get; set; }
public virtual ICollection<Flight>? Flights { get; set; }
}
Airport:
public class Airport
{
public int Id { get; set; }
public string Name { get; set; }
/* other properties */
public int CountryId { get; set; }
public virtual Country Country { get; set; }
public virtual ICollection<Flight>? DepartureFlights { get; set; }
public virtual ICollection<Flight>? ArrivalFlights { get; set; }
}
Flight:
public class Flight
{
public int Id { get; set; }
/* other properties */
public int AirlineId { get; set; }
public int DepartureAirportId { get; set; }
public int ArrivalAirportId { get; set; }
public virtual Airline Airline { get; set; }
public virtual Airport DepartureAirport { get; set; }
public virtual Airport ArrivalAirport { get; set; }
}
After all the DBContext file:
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Airline> Airlines { get; set; }
public DbSet<Airport> Airports { get; set; }
public DbSet<Country> Countries { get; set; }
public DbSet<Flight> Flights { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Airline>(x =>
{
x.HasKey(a => a.Id);
x.HasOne(c => c.Country)
.WithMany(a => a.Airlines)
.HasForeignKey(a => a.CountryId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity<Airport>(x =>
{
x.HasKey(a => a.Id);
x.HasOne(c => c.Country)
.WithMany(a => a.Airports)
.HasForeignKey(a => a.CountryId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
modelBuilder.Entity<Country>(x =>
{
x.HasKey(c => c.Id);
});
modelBuilder.Entity<Flight>(x =>
{
x.HasKey(f => f.Id);
x.HasOne(a => a.Airline)
.WithMany(f => f.Flights)
.HasForeignKey(f => f.AirlineId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
x.HasOne(a => a.DepartureAirport)
.WithMany(f => f.DepartureFlights)
.HasForeignKey(f => f.DepartureAirportId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
x.HasOne(a => a.ArrivalAirport)
.WithMany(f => f.ArrivalFlights)
.HasForeignKey(f => f.ArrivalAirportId)
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
});
}
}
How to implement this behavior and prevent an error?
This is officially known issue. Therefore, We have two ways to handle this scenario thus the error:
1. Change one or more of the relationships to not cascade delete.
In this scenario, we could make the Country relationship with Airport, Flight and Airlines optional by giving it a nullable foreign key property: for instance we can do something like:
.IsRequired(false);
Note: You can check our official document for more details.
2. Configure the database without one or more of these cascade deletes,
then ensure all dependent entities are loaded so that EF Core can
perform the cascading behavior.
Considering this appreach we can keep the Airport, Flight and Airlines relationship required and configured for cascade delete, but make this configuration only apply to tracked entities, not the database: So we can do somethng like below:
modelBuilder.Entity<Airline>(x =>
{
x.HasKey(a => a.Id);
x.HasOne(c => c.Country)
.WithMany(a => a.Airlines)
.HasForeignKey(a => a.CountryId)
.OnDelete(DeleteBehavior.ClientCascade);
.IsRequired(false);
});
modelBuilder.Entity<Airport>(x =>
{
x.HasKey(a => a.Id);
x.HasOne(c => c.Country)
.WithMany(a => a.Airports)
.HasForeignKey(a => a.CountryId)
.OnDelete(DeleteBehavior.ClientCascade);
.IsRequired(false);
});
Note: You can apply same for Flight as well. In addition, As you may know OnDelete(DeleteBehavior.ClientCascade); or ClientCascade allows the DBContext to delete entities even if there is a cyclic ref or LOCK on it. Please read the official guideline for more details here
add these lines in "OnModelCreating"
var cascades = modelBuilder.Model.GetEntityTypes()
.SelectMany(t => t.GetForeignKeys())
.Where(fk => !fk.IsOwnership && fk.DeleteBehavior == DeleteBehavior.Cascade);
foreach (var fk in cascades)
fk.DeleteBehavior = DeleteBehavior.Restrict;

Why is Entity creating a key field when it is already defined?

I have several related domain models that are triggering the exception SqlException: Invalid column name 'ChecklistTemplate_Id'.
My domain model looks like:
public class Assignment
{
public long Id { get; set; }
public long ChecklistId { get; set; }
public DateTime InspectionDate { get; set; }
public long JobId { get; set; }
public Guid? EmployeeId { get; set; }
// TODO: Make the completion a nullable date time in the database and here
public DateTime CompletionDate { get; set; }
public virtual Job Job { get; set; }
public virtual Checklist Checklist { get; set; }
public virtual IList<Image> Images { get; set; }
public virtual IList<Attachment> Attachments { get; set; }
public virtual IList<Equipment> Equipments { get; set; }
}
My EntityTypeConfiguration class looks like:
internal class AssignmentConfiguration : EntityTypeConfiguration<Assignment>
{
public AssignmentConfiguration()
{
ToTable("Assignment");
HasKey(k => k.Id);
Property(a => a.ChecklistId)
.IsRequired();
Property(a => a.CompletionDate)
.IsOptional();
Property(a => a.EmployeeId)
.IsOptional();
Property(a => a.Id)
.IsRequired();
Property(a => a.InspectionDate)
.IsRequired();
Property(a => a.JobId)
.IsRequired();
HasRequired(a => a.Job)
.WithMany(a => a.Assignments)
.HasForeignKey(a => a.JobId);
HasRequired(a => a.Checklist)
.WithOptional(a => a.Assignment);
HasMany(a => a.Images)
.WithRequired(a => a.Assignment)
.HasForeignKey(a => a.InspectionId);
}
}
The Checklist domain model has a ChecklistTemplate navigation property with the join:
HasMany(a => a.CheckLists)
.WithRequired(a => a.ChecklistTemplate)
.HasForeignKey(a => a.ChecklistTemplateId);
There is a one to one between Assignment and Checklist as seen in the Assignment entity configuration.
And yes, we are including the configuration in the DBContext.
Also, I looked at Entity Framework 6 creates Id column even though other primary key is defined and that doesn't seem to apply.
I dont have a satisfactory answer to that but I have had a lot of trouble with ef6. This is because there is a navigation which is not defined or wrongly defined. So ef6 creates it on-the-fly on proxy classes and you cry for hours. I hope you find out the problem soon.
And the navigation you stated is one-to-many. Be careful.

Entity Framework 6 code-first relations with Fluent API

I have 3 related tables in an existing database.
Many-to-Many (Many Person Many Group) and a table between - Event, witch ralate these two tables.
public class Person
{
public int PersonID { get; set; }
public string No { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public virtual ICollection<Event> Events { get; set; }
}
public class Event
{
public int EventID { get; set; }
public string EventData { get; set; }
public int PersonID { get; set; }
public int GroupID { get; set; }
public virtual Person Person { get; set; }
public virtual Group Group { get; set; }
}
public class Group
{
public int GroupID { get; set; }
public string GroupName { get; set; }
public virtual ICollection<Event> PersonGroup { get; set; }
}
I ddescribed relations using Fluent API.
First I declared PK's
modelBuilder.Entity<Person>()
.HasKey(k => k.PersonID });
modelBuilder.Entity<Event>()
.HasKey(k => k.EventID);
modelBuilder.Entity<Group>()
.HasKey(k => k.GroupID });
Now foreign keys:
Person has many Events
modelBuilder.Entity<Person>()
.HasKey(k => k.PersonID })
.HasMany(k => k.Events)
.WithRequired()
.HasForeignKey(f => f.PersonID);
In Event class I have Person (and all its parameters)
modelBuilder.Entity<Event>()
.HasRequired(s => s.Person)
.WithMany()
.HasForeignKey(fk => fk.PersonID);
Also I need a Group with all data:
modelBuilder.Entity<Event>()
.HasOptional(s => s.Group)
.WithMany()
.HasForeignKey(fk => fk.GroupID });
At least I need a Person group participating in an Event
modelBuilder.Entity<Group>()
.HasKey(k =>k.GroupID })
.HasMany(k => k.PersonGroup)
.WithOptional()
.HasForeignKey(fk => fk.GroupID);
It seems I have everything I need, but I need one mo collection (Group of persons with their names)
What I get through PersonGroup relation I have all events but also need to get Persons. Could You help?
Edit
I just realize it's not a typical many-to-many, since your Event-Group relation is optional. Is this intended?
Maybe your model should be changed to reflect a more natural event structure:
Group-Person: many-to-many
Event-Group: many-to-many
Event-Person: no direct relation, only via groups; alternative many-to-many
The way your model is currently designed, a single event entry can't be related to more than one group and one person and a person can't be part of a group unless they are associated in the context of an event entry.
Basically, the thing you ask for is not directly available, because you decided to explicitely create the many-to-many table with additional properties.
However, in queries you can always write a select to get the persons collection
db.Groups.Select(g => new {Group = g, PeopleInGroup = g.PersonGroup.Select(ev => ev.Person)})
Few side-notes regarding your model:
Consider removing EventID and instead use modelBuilder.Entity<Event>().HasKey(k => new { k.PersonID, k.GroupID }), like a typical many-to-many table would be designed.
Mention reverse properties in fluent api:
.
modelBuilder.Entity<Person>()
.HasKey(k => k.PersonID })
.HasMany(k => k.Events)
.WithRequired(e => e.Person)
.HasForeignKey(f => f.PersonID);
// redundant with the previous configuration
modelBuilder.Entity<Event>()
.HasRequired(s => s.Person)
.WithMany(p => p.Events)
.HasForeignKey(fk => fk.PersonID);
// same to be done for groups
In order to have a convenient access to the associated people of a group, you could create a not-mapped property getter which wraps the necessary query:
public class Group
{
public int GroupID { get; set; }
public string GroupName { get; set; }
public virtual ICollection<Event> PersonGroup { get; set; }
[NotMapped]
public IEnumerable<Person> PersonsInGroupEvents
{
return PersonGroup.Select(ev => ev.Person);
}
}
// or fluent api way of NotMapped:
modelBuilder.Entity<Group>()
.Ignore(x => x.PersonsInGroupEvents);

Trouble when trying to create a database with Entity Framework and migrations

I want to create database EF migrations via the developer command prompt for VS2015. When I try to use this command line:
dotnet ef migrations add v1
I get this error:
The property 'partCategoriess' cannot be added to the entity type
'PartCategoryPart' because a navigation property with the same name
already exists on entity type 'PartCategoryPart'.
Is anything wrong with the DbContext? I am trying to create a many-to-many table between categoryParts and parts.
public class ShoppingDbContext : IdentityDbContext<User>
{
public ShoppingDbContext(DbContextOptions options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
}
public DbSet<PartCategory> PartCategories { get; set; }
public DbSet<Part> Parts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<PartCategoryPart>()
.HasKey(t => new { t.partCategoriess, t.Part });
modelBuilder.Entity<PartCategoryPart>()
.HasOne(pt => pt.partCategoriess)
.WithMany(p => p.PartCategoryPart)
.HasForeignKey(pt => pt.PartCategoryId);
modelBuilder.Entity<PartCategoryPart>()
.HasOne(pt => pt.Part)
.WithMany(t => t.PartCategoryPart)
.HasForeignKey(pt => pt.PartId);
}
}
public class PartCategoryPart
{
public int Id { get; set; }
public int PartCategoryId { get; set; }
public PartCategory partCategoriess { get; set; }
public int PartId { get; set; }
public Part Part { get; set; }
}
public class PartCategory
{
public int PartCategoryId { get; set; }
public string Category { get; set; }
public List<ProductPartCategory> ProductPartCategories { get; set; }
public List<PartCategoryPart> PartCategoryPart { get; set; }
}
public class Part
{
public int PartId { get; set; }
public string Code { get; set; }
public string Name { get; set; }
public double? Price { get; set; }
public List<PartCategoryPart> PartCategoryPart { get; set; }
}
The problem here is how you are defining the primary key for the PartCategoryPart intermediate entity. You are using the navigation properties to define the PK and you have to use the FKs like this:
modelBuilder.Entity().HasKey(t => new { t.PartCategoryId, t.PartId});
Referring to my own assignment, here's how you properly create an entity. You did not define a key..
modelBuilder.Entity<Price>()
.HasKey(input => input.PriceId)
.HasName("PrimaryKey_Price_PriceId");
// Provide the properties of the PriceId column
modelBuilder.Entity<Price>()
.Property(input => input.PriceId)
.HasColumnName("PriceId")
.HasColumnType("int")
.UseSqlServerIdentityColumn()
.ValueGeneratedOnAdd()
.IsRequired();
//modelBuilder.Entity<Price>()
// .Property(input => input.MetricId)
// .HasColumnName("MetricId")
// .HasColumnType("int")
// .IsRequired();
modelBuilder.Entity<Price>()
.Property(input => input.Value)
.HasColumnName("Value")
.HasColumnType("DECIMAL(19,4)")
.IsRequired();
modelBuilder.Entity<Price>()
.Property(input => input.RRP)
.HasColumnName("RRP")
.HasColumnType("DECIMAL(19,4)")
.IsRequired(false);
modelBuilder.Entity<Price>()
.Property(input => input.CreatedAt)
.HasDefaultValueSql("GetDate()");
modelBuilder.Entity<Price>()
.Property(input => input.DeletedAt)
.IsRequired(false);
// Two sets of Many to One relationship between User and ApplicationUser entity (Start)
modelBuilder.Entity<Price>()
.HasOne(userClass => userClass.CreatedBy)
.WithMany()
.HasForeignKey(userClass => userClass.CreatedById)
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
modelBuilder.Entity<Price>()
.HasOne(userClass => userClass.DeletedBy)
.WithMany()
.HasForeignKey(userClass => userClass.DeletedById)
.OnDelete(DeleteBehavior.Restrict);
Notice that after identifying which is the key you still need to declare its properties before you declare any relationships.
modelBuilder.Entity<Price>()
.HasKey(input => input.PriceId)
.HasName("PrimaryKey_Price_PriceId");
// Provide the properties of the PriceId column
modelBuilder.Entity<Price>()
.Property(input => input.PriceId)
.HasColumnName("PriceId")
.HasColumnType("int")
.UseSqlServerIdentityColumn()
.ValueGeneratedOnAdd()
.IsRequired();

Add a one-to-many connection to Many-to-Many

I have a Many To Many relationship with some additional fields. But as there are Photos added to the many to many relationship which might apply to other relations I wanted to seperate it so I can change it by just altering the One to many relation. This is the model
public class Segment
{
public int SegmentId { get; set; }
public int ConnectionPointIdEnd { get; set; }
public string ConnectionName { get; set; }
public string ConnectionInformation { get; set; }
public string Image { get; set; }
public string Direction { get; set; }
public ICollection<ConnectionPointRoute> ConnectionPointRoutes { get; set; }
}
public class ConnectionPointRoute
{
public int ConnectionPointId { get; set; }
public int RouteId { get; set; }
public int SegmentId { get; set; }
public int Position { get; set; }
public ConnectionPoint ConnectionPoint { get; set; }
public Route Route { get; set; }
public Segment Segment { get; set; }
}
And the modelbuilder looks like this :
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<ConnectionPointRoute>()
.HasKey(c => new { c.ConnectionPointId, c.RouteId, c.SegmentId });
modelBuilder.Entity<ConnectionPoint>()
.HasMany(c => c.ConnectionPointRoutes)
.WithRequired(x => x.ConnectionPoint)
.HasForeignKey(c => c.ConnectionPointId);
modelBuilder.Entity<Route>()
.HasMany(c => c.ConnectionPointRoutes)
.WithRequired(x => x.Route)
.HasForeignKey(c => c.RouteId);
modelBuilder.Entity<Segment>()
.HasMany(c => c.ConnectionPointRoutes)
.WithRequired(x => x.Segment)
.HasForeignKey(c => c.SegmentId);
}
And this all works well for getting the items, but for some reason it doesn't allow me to post a new Route for instance, it gets me the error:
"Multiplicity constraint violated. The role
'Segment_ConnectionPointRoutes_Source' of the relationship
'InBuildingNavigator.Data.Models.Segment_ConnectionPointRoutes' has
multiplicity 1 or 0..1."
Any thoughts?
Fixed this! I had an error in my Post code, I added the full child objects which doesn't make a whole lot of sense in my case.
Ask me if you want a more detailed fix!
Just two more things to this:
I would recommend you to use an extra object for the many-to-many relationship (if you don't already do this). This will give you more control over the table name and over selections you may want to do.
use the virtual keyword for your properties, which you do not need directly (for your collections) - this will allow ef to implement lazy loading on them.

Categories