What does entity.Items.Clear() do in EF? - entity-framework

In a project I'm working on, whenever I call SaveChanges() after clearing list of items, I get this error:
The operation failed: The relationship could not be changed because
one or more of the foreign-key properties is non-nullable. When a
change is made to a relationship, the related foreign-key property is
set to a null value. If the foreign-key does not support null values,
a new relationship must be defined, the foreign-key property must be
assigned another non-null value, or the unrelated object must be
deleted.
I have these two classes with one to many relationship (simplest implementation):
public class Company
{
public int Id {get; set;}
public string Name {get; set;}
public virtual List<Vehicle> Vehicles {get; set;}
}
public class Vehicle
{
public int Id {get; set;}
public string Model {get; set;}
}
I'm guessing that calling company.Vehicles.Clear() should update the auto generated column Company_Id in the Vehicle table, setting its value to null, but that not seems the case which the error above indicating.
So I'me wondering what exactly company.Vehicles.Clear() do in the database side?
EDIT
Thanks to the answer by #McKabue and the link he provided, I found this is the solution, this is the complete code:
var company = context.Set<GENCompany>().
Include(m => m.Vehicles).
Include(m => m.Quotes).
Include(m => m.Phones).
Include(m => m.Persons).
Include(m => m.Leads).
Include(m => m.Emails).
Include(m => m.Drivers).
Include(m => m.Deals).
Include(m => m.Comments).
Include(m => m.Branches).
Include(m => m.Addresses).
SingleOrDefault(m => m.Id == companyModel.Id);
context.Entry(company).State = EntityState.Modified;
company.Vehicles.Clear();
company.Drivers.Clear();
company.Quotes.Clear();
company.Persons.Clear();
company.Leads.Clear();
company.Deals.Clear();
company.Branches.Clear();
var phones = context.CompanyPhones.Where(x => x.CompanyId == companyModel.Id);
context.CompanyPhones.RemoveRange(phones);
var emails = context.CompanyEmails.Where(x => x.CompanyId == companyModel.Id);
context.CompanyEmails.RemoveRange(emails);
var comments = context.CompanyComments.Where(x => x.CompanyId == companyModel.Id);
context.CompanyComments.RemoveRange(comments);
var addresses = context.CompanyAddresses.Where(x => x.CompanyId == companyModel.Id);
context.CompanyAddresses.RemoveRange(addresses);
For some entities they are removed using .Clear() syntax, other entities are removed by fetching their records and deleteing them all (not only the relation). It makes perfect sense in the current design, for example in the create page of the company, the phone data is added manually, while vehicles are selected from drop down menu, while it makes this perfect sense I feel so confused to determine why this happen, for example here is the CompanyPhone class:
public class GENCompanyPhone
{
[Key]
public long PhoneId { get; set; }
public long CompanyId { get; set; }
public long PhoneTypeId { get; set; }
public DateTime DateCreated { get; set; } = DateTime.Now;
public DateTime DateUpdated { get; set; } = DateTime.Now;
public Guid? CreatedById { get; set; }
public bool IsPublish { get; set; } = false;
public bool IsActive { get; set; } = false;
[MaxLength(255)]
[Index(IsUnique = true)]
public string PhoneNumber { get; set; }
[ForeignKey("CompanyId")]
public virtual GENCompany Company { get; set; }
}
on the other hand this is the vehicle class:
public class GENVehicle
{
public long Id { get; set; }
public long? BrandId { get; set; }
[ForeignKey("BrandId")]
public GENBrand Brand { get; set; }
public string Model { get; set; }
public DateTime? LicenseIssueDate { get; set; }
public Guid? CreatedById { get; set; }
[ForeignKey("VehicleTypeId")]
public virtual GENVehicleType VehicleType { get; set; }
public long? VehicleTypeId { get; set; }
public long? LicenseIssuerId { get; set; }
[ForeignKey("LicenseIssuerId")]
public GENEmployee LicenseIssuer { get; set; }
}
I can't determine exactly why EF deal differently with Vehicle and CompanyPhone

It means that you are clearing a record that is referenced in another table. So, EF warns you...there are ways you can avoid this by setting Cascade delete
Edit
To remove child elements from a parent table, the children entities need to be loaded.
Edit 2
var company = context.Set<GENCompany>().Where(m => m.Id == companyModel.Id);
Include(m => m.Vehicles).
Include(m => m.Quotes).
Include(m => m.Phones).
Include(m => m.Persons).
Include(m => m.Leads).
Include(m => m.Emails).
Include(m => m.Drivers).
Include(m => m.Deals).
Include(m => m.Comments).
Include(m => m.Branches).
Include(m => m.Addresses).SingleOrDefault();
company.Vehicles.Clear();
company.Drivers.Clear();
company.Quotes.Clear();
company.Persons.Clear();
company.Leads.Clear();
company.Deals.Clear();
company.Branches.Clear();
THAT WILL WORK
Query before Including
Include All entities you want to remove;
Clear / RemoveRange / Remove one by one from the parent (company object)

Related

EF 6 Code First relationships using Fluent API. How to set relation between first and third tables or get grouped collection

I have three related entities, here is a structure and relations declared using Fluent API
An event, participating many persons (actually a group of persons) so there is a possibility to collect them all by GroupID. So how to do this?
public class Event
{
public int EventID { get; set; }
public string DocID { get; set; }
public string GroupID { get; set; }
public virtual Person Person { get; set; }
public virtual Group Group { get; set; }
public virtual ICollection<Person> GroupPerson {get; set}
}
Person entity, here I have all information about person, such as name, surname, birthdate...
public class Person
{
public string PersonID { get; set; }
public string PersonName { get; set; }
public string PersonSurName { get; set; }
public string PersonCode { get; set; }
}
Group entity, here is an information about the group
public class Group
{
public string GroupID { get; set; }
public string GroupName { get; set; }
public virtual ICollection<Event> EventGroup { get; set; }
}
Now I describe relations using Fluent API. Primary keys first of all:
modelBuilder.Entity<Event>().HasKey(e => e.EventID);
modelBuilder.Entity<Person>().HasKey(e => e.PersonID);
modelBuilder.Entity<Group>().HasKey(e => e.GroupID);
Here I will have person related to event
modelBuilder.Entity<Event>()
.HasRequired(s => s.Person)
.WithMany()
.HasForeignKey(fk=> fk.PersonID);
Here I will have PersonGroup
modelBuilder.Entity<Group>()
.HasKey(e => e.GroupID)
.HasMany(e => e.EventGroup)
.WithOptional()
.HasForeignKey(f => f.GroupID);
And my question is how to set a relation to get that list of persons in group?
PersonGroup is an Event type and I need list of persons type: Person => ICollection<Person> GroupPerson in Event class.
Given that your relationships are like this:
One event has (is related to) exactly one group (required)
One group has (is related to) zero to many events
One group has (is related to) zero to many people
One person has (is related to) zero to many groups
That is, your relationship Events-Groups is one-to-many, and your relationship Groups-People is many-to-many (I'm assuming that the same person can be in more than one group). There is no direct relationship between Events and People, but a transitive relationship Event -> Group -> People.
Then it can be modelled like this:
public class Event
{
public int EventID { get; set; }
public string DocID { get; set; }
public string GroupID { get; set; }
public virtual Group Group { get; set; }
public virtual ICollection<Person> People { get { return Group.People; } }
}
public class Person
{
public string PersonID { get; set; }
public string PersonName { get; set; }
public string PersonSurName { get; set; }
public string PersonCode { get; set; }
}
public class Group
{
public string GroupID { get; set; }
public string GroupName { get; set; }
public virtual ICollection<Event> Events { get; set; }
public virtual ICollection<Person> People { get; set; }
}
With these DbSets in the DbContext:
public DbSet<Person> People { get; set; }
public DbSet<Group> Groups { get; set; }
public DbSet<Event> Events { get; set; }
And this EF configuration:
modelBuilder.Entity<Event>()
.HasKey(e => e.EventID)
.Ignore(e => e.People)
.HasRequired(e => e.Group)
.WithMany(g => g.Events);
modelBuilder.Entity<Group>()
.HasKey(g => g.GroupID)
.HasMany(g => g.People)
.WithMany();
modelBuilder.Entity<Person>()
.HasKey(p => p.PersonID);
Note that there is an explicit Ignore() for Event.People. This is because the relationship between Event and Person is transitive, you don't need extra columns in your database for it. If you don't see why, try commenting out the Ignore() line and regenerating the migration, and see that an extra column for the Event ID is generated in the People table (this column doesn't make much sense).
As a consequence the People property in Events is not populated by EF, you have to do it yourself:
public virtual ICollection<Person> People { get { return Group.People; } }
To add people to an Event you should use the Group navigation property, something like this:
public class Event
{
...
public void AddPerson(Person p)
{
this.Group.People.Add(p);
}
}
With this code the migration is generated like this, with four tables: Events, Groups, People and and extra table PeopleGroups for the many-to-many relationship between Person and Group.
public override void Up()
{
CreateTable(
"dbo.Events",
c => new
{
EventID = c.Int(nullable: false, identity: true),
DocID = c.String(),
GroupID = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => t.EventID)
.ForeignKey("dbo.Groups", t => t.GroupID, cascadeDelete: true)
.Index(t => t.GroupID);
CreateTable(
"dbo.Groups",
c => new
{
GroupID = c.String(nullable: false, maxLength: 128),
GroupName = c.String(),
})
.PrimaryKey(t => t.GroupID);
CreateTable(
"dbo.People",
c => new
{
PersonID = c.String(nullable: false, maxLength: 128),
PersonName = c.String(),
PersonSurName = c.String(),
PersonCode = c.String(),
})
.PrimaryKey(t => t.PersonID);
CreateTable(
"dbo.GroupPersons",
c => new
{
Group_GroupID = c.String(nullable: false, maxLength: 128),
Person_PersonID = c.String(nullable: false, maxLength: 128),
})
.PrimaryKey(t => new { t.Group_GroupID, t.Person_PersonID })
.ForeignKey("dbo.Groups", t => t.Group_GroupID, cascadeDelete: true)
.ForeignKey("dbo.People", t => t.Person_PersonID, cascadeDelete: true)
.Index(t => t.Group_GroupID)
.Index(t => t.Person_PersonID);
}
If you don't like the names of the columns in the relationship table GroupPersons you can add a .Map() configuration (but you don't really need to do this, as this table isn't directly used, there is no model entity for it, and it doesn't even have a DbSet property in the DbContext).

Foreign keys to same Entity with one required and another optional

I'm trying to create two entities as below and add referential constraints to them using Fluent API.
The design it to enforce required Primary Contact with optional Secondary Contact with added requirement that Primary and Secondary may be referring to two different contacts in the ContactInfo entity.
public class Employee
{
public int EmployeeId { get; set; }
public int PrimaryContactId { get; set; }
public int SecondaryContactId { get; set; }
public virtual ContactInfo PrimaryContact { get; set; }
public virtual ContactInfo SecondaryContact { get; set; }
}
public class ContactInfo
{
public int ContactId { get; set; }
public string PhoneNumer { get; set; }
public string Email { get; set; }
}
public class EmployeeConfiguration : EntityTypeConfiguration<Employee>
{
public EmployeeConfiguration ()
{
var employeeEntity = this;
employeeEntity.HasKey(e => e.EmployeeId).ToTable("Employees");
employeeEntity.Property(e => e.EmployeeId).HasDatabaseGeneratedOption(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity);
employeeEntity.HasRequired(e => e.PrimaryContact).WithMany().HasForeignKey(e => e.PrimaryContactId).WillCascadeOnDelete(true);
employeeEntity.HasRequired(e => e.SecondaryContact ).WithMany().HasForeignKey(e => e.SecondaryContact Id).WillCascadeOnDelete(false);
}
}
It seems to create with required constraints as expected but when I try to add an employee with SecondaryContact set to null, the row created for this new Employee has SecondaryContactId set to same as PrimaryContactId which is not my intention.
I'm not able to understand whether the design is correct in the first place, or the configuration needs to be tweaked to get the right results.
Looks like you set both contacts to be required for your employee object.
If you want to have SecondaryContract optional change:
employeeEntity
.HasRequired(e => e.SecondaryContact)
.WithMany()
.HasForeignKey(e => e.SecondaryContactId)
.WillCascadeOnDelete(false);
to
employeeEntity
.HasOptional(e => e.SecondaryContact)
.WithMany()
.HasForeignKey(e => e.SecondaryContactId)
.WillCascadeOnDelete(false);
Addtionally change:
public int SecondaryContactId { get; set; }
to
public int? SecondaryContactId { get; set; }
since your SecondaryContactId is optional.
You can add foreign Key annotations and also set the keys to be nullable if you expect either to be empty.
public class Employee
{
public int EmployeeId { get; set; }
public int? PrimaryContactId { get; set; }
public int? SecondaryContactId { get; set; }
[ForeignKey("PrimaryContactId")]
public virtual ContactInfo PrimaryContact { get; set; }
[ForeignKey("SecondaryContactId")]
public virtual ContactInfo SecondaryContact { get; set; }
}
Make the secondary contact id to be a nullable type.
public int? SecondaryContactId { get; set; }

Compare property of model in the list with the constant value

Its a simple question but I am starting with EF and dont know:
I have two classes (db objects):
Company (simplified)
public class Company
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<UserGroup> UserGroups { get; set; }
}
and userGroup (there are many users in the group):
public class UserGroup
{
public int ID { get; set; }
public string Name { get; set; }
public virtual ICollection<ApplicationUser> Users { get; set; }
public virtual ICollection<Company> Companies { get; set; }
}
In controller I need to select the companies which have a specific UserGroupID. I dont know how to write the select condition. I mean something like:
var currentUser = db.Users.Find(User.Identity.GetUserId());
var companies = db.Companies
.Include(c => c.Address)
.Include(c => c.User)
.Where(c => c.UserGroups == currentUser.UserGroup)
;
It would be helpful to see your ApplicationUser class, but I suppose it has a navigation property to UserGroup? If it does, you could do it like this:
db.Users.Where(u => u.Id == User.Identity.GetUserId())
.SelectMany(u => u.UserGroups)
.SelectMany(g => g.Companies)
.Distinct();
I like to write the where clause over entity I want to return. In your case:
db.Companies.Where(c => c.UserGroups.Any(g => g.ID == currentUser.UserGroup.ID));
This way you don't have to add the Distinct().

One to Many with two Properties from the same class

I have the following classes that I would really like to map correctly in EF:
internal class Wallet : EntityFrameworkEntity
{
public Wallet()
{
this.Requests = new List<FinancialRequest>();
}
public string Name { get; set; }
public string Description { get; set; }
public decimal CurrentBalance { get; set; }
public decimal BlockedBalance { get; set; }
public virtual ICollection<Paper> Papers { get; set; }
public virtual ICollection<FinancialRequest> Requests { get; set; }
public virtual User Manager { get; set; }
}
internal class Request : EntityFrameworkEntity
{
public Int64 UserId { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public RequestStatus Status { get; set; }
public virtual User User { get; set; }
}
internal class FinancialRequest : Request
{
public DateTime ValidUntil { get; set; }
public FinancialRequestType RequestType { get; set; }
public int Quantity { get; set; }
public bool UseMarketValue { get; set; }
public decimal? Value { get; set; }
public virtual Wallet Source { get; set; }
public virtual Wallet Destination { get; set; }
public virtual Team Team { get; set; }
}
I'm using Code First, so this is my method that maps those classes:
modelBuilder.Entity<Wallet>()
.HasMany(x => x.Requests)
.WithOptional();
modelBuilder.Entity<Wallet>()
.HasMany(x => x.Papers)
.WithOptional(x => x.Owner)
.Map(configuration => configuration.MapKey("OwnerId"));
modelBuilder.Entity<Wallet>()
.HasMany(x => x.Requests)
.WithOptional();
modelBuilder.Entity<Request>().ToTable("Requests");
modelBuilder.Entity<FinancialRequest>().ToTable("FinancialRequests");
modelBuilder.Entity<FinancialRequest>()
.HasRequired(x => x.Team)
.WithOptional()
.Map(configuration => configuration.MapKey("TeamId"));
modelBuilder.Entity<FinancialRequest>()
.HasOptional(x => x.Destination)
.WithOptionalDependent()
.Map(configuration => configuration.MapKey("DestinationWalletId"));
modelBuilder.Entity<FinancialRequest>()
.HasRequired(x => x.Source)
.WithRequiredDependent()
.Map(configuration => configuration.MapKey("SourceWalletId"));
If I leave this mapping the way it's now, my database schema looks like this:
If you look carefully, you'll see that there's a column called "Wallet_Id" that it's not suposed to be there. This column only exists because the Wallet class has the "Requests" collection.
If I remove the collection from the the columns goes away, but I need this collection! It representes a importante relation between the classes. What I don't need is the 3rd column in the database wrongly generated.
Does anybody knows how can I avoid this? What am I doing wrong here?
The problem that causes the redundant foreign key column Wallet_Id is that EF doesn't know if the Wallet.Requests collection is the inverse navigation property of FinancialRequest.Source or FinancialRequest.Destination. Because it cannot decide between the two EF assumes that Wallet.Requests doesn't have an inverse navigation property at all. The result is a third redundant one-to-many relationship with the third FK.
Basically you have three options:
Remove the Wallet.Requests collection and the third relationship will disappear (as you already have noticed). But you don't want that.
Tell EF explicitly if Wallet.Requests has Source or Destination as inverse navigation property:
// Remove the modelBuilder.Entity<Wallet>().HasMany(x => x.Requests) mapping
modelBuilder.Entity<FinancialRequest>()
.HasOptional(x => x.Destination)
.WithMany(x => x.Requests)
.Map(config => config.MapKey("DestinationWalletId"));
modelBuilder.Entity<FinancialRequest>()
.HasRequired(x => x.Source)
.WithMany()
.Map(config => config.MapKey("SourceWalletId"));
Use WithMany(x => x.Requests) in one of the two (Destination in the example, it could also be Source), but not in both.
Introduce a second collection in Wallet and map the two collections to Source and Destination respectively:
internal class Wallet : EntityFrameworkEntity
{
public Wallet()
{
this.SourceRequests = new List<FinancialRequest>();
this.DestinationRequests = new List<FinancialRequest>();
}
// ...
public virtual ICollection<FinancialRequest> SourceRequests { get; set; }
public virtual ICollection<FinancialRequest> DestinationRequests { get; set; }
}
Mapping:
// Remove the modelBuilder.Entity<Wallet>().HasMany(x => x.Requests) mapping
modelBuilder.Entity<FinancialRequest>()
.HasOptional(x => x.Destination)
.WithMany(x => x.DestinationRequests)
.Map(config => config.MapKey("DestinationWalletId"));
modelBuilder.Entity<FinancialRequest>()
.HasRequired(x => x.Source)
.WithMany(x => x.SourceRequests)
.Map(config => config.MapKey("SourceWalletId"));
BTW: Shouldn't both Source and Destination be required? If yes, you can replace the HasOptional by HasRequired but you must append WillCascadeOnDelete(false) to at least one of the two mappings to avoid a multiple cascading delete path exception.

Entity Framework Code First 5 Cascade Delete on many to many tables error

I've this model
public class State
{
public State()
{
this.Promotions = new List<Promotion>();
this.Branches = new List<Branch>();
this.Stores = new List<Store>();
}
public int Id { get; set; }
public string Description { get; set; }
public virtual ICollection<Promotion> Promotions { get; set; }
public virtual ICollection<Store> Stores { get; set; }
public virtual ICollection<Branch> Branches { get; set; }
}
public class Store
{
public Store()
{
this.Promotions = new List<Promotion>();
this.Branches = new List<Branch>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Promotion> Promotions { get; set; }
public virtual ICollection<Branch> Branches { get; set; }
public int StateId { get; set; } // Foreign key
public virtual State State { get; set; } // Navigation Property
}
public class Branch
{
public Branch()
{
this.Promotions = new List<Promotion>();
}
public int Id { get; set; }
public string Name { get; set; }
public int StoreId { get; set; } // Foreign key
public int StateId { get; set; } // Foreign key
public virtual Store Store { get; set; } // Navigation Property
public virtual State State { get; set; } // Navigation Property
public virtual ICollection<Promotion> Promotions { get; set; }
}
public class Promotion
{
public Promotion()
{
this.Stores = new List<Store>();
this.Branches = new List<Branch>();
this.Productos = new List<Producto>();
}
public int Id { get; set; }
public string Name { get; set; }
public int StateId { get; set; }
public virtual ICollection<Store> Stores { get; set; }
public virtual ICollection<Branch> Branches { get; set; }
public virtual ICollection<Product> Products { get; set; }
public virtual State State { get; set; }
}
And this in my context:
// State
modelBuilder.Entity<State>()
.HasMany(p => p.Promotions)
.WithRequired(e => e.State)
.WillCascadeOnDelete(false);
modelBuilder.Entity<State>()
.HasMany(s => s.Branches)
.WithRequired(e => e.State)
.WillCascadeOnDelete(false);
modelBuilder.Entity<State>()
.HasMany(e => e.Stores)
.WithRequired(e => e.State)
.WillCascadeOnDelete(true);
// Store
modelBuilder.Entity<Store>()
.HasMany(b => b.Branches)
.WithRequired(s => s.Store)
.WillCascadeOnDelete(true);
// Many to many
modelBuilder.Entity<Store>().
HasMany(p => p.Promotions).
WithMany(s => s.Stores).
Map(
m =>
{
m.MapLeftKey("StoreId");
m.MapRightKey("PromotionId");
m.ToTable("Store_Promotion");
});
modelBuilder.Entity<Promotion>().
HasMany(e => e.Products).
WithMany(p => p.Promotiones).
Map(
m =>
{
m.MapLeftKey("PromotionId");
m.MapRightKey("ProductoId");
m.ToTable("Promotion_Producto");
});
modelBuilder.Entity<Branch>().
HasMany(p => p.Promotiones).
WithMany(b => b.Branches).
Map(
m =>
{
m.MapLeftKey("BranchId");
m.MapRightKey("PromotionId");
m.ToTable("Branch_Promotion");
});
Now if I turn on more than one WillCascadeOnDelete of the State (first three in the fluent mapping) I get the error
Test method Proj.Data.Tests.UnitTest1.TestPromotion threw exception:
System.Data.SqlClient.SqlException: Introducing FOREIGN KEY constraint 'FK_dbo.Branch_dbo.Store_StoreId' on table 'Branch' 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.
I know that, and I've read from Julie Lerman's book:
Some databases (including SQL Server) don’t support multiple relationships that specify
cascade delete pointing to the same table
As it happens since the many to many relationship table has cascade delete coming from both related tables.
So, my question is: The only choice here is to turn off cascade delete on parent tables and handle the deletion on relationship table manually? Isn't there any workaround from Entity Framework 5 for this?
Ok, I understood the problem. It is not to have a many to many relationship, the problem is this
State -> Promotion -> PromotionStore
State -> Branch -> BranchPromotion
State -> Store -> StorePromotion
and then Store, Branch and Store have FK to State. So if I delete a State PromotionStore can be reached by 1st and 3rd possibilities.
What I ended up doing is turning off cascade delete for State and deleting the related records manually like this:
public override void Delete(State state)
{
DbContext.Entry(state).Collection(x => x.Promotions).Load();
DbContext.Entry(state).Collection(x => x.Stores).Load();
DbContext.Entry(state).Collection(x => x.Branches).Load();
var associatedPromotions = state.Promotions.Where(p => p.StateId == state.Id);
associatedPromotions.ToList().ForEach(r => DbContext.Set<Promotion>().Remove(r));
var associatedStores = state.Stores.Where(e => e.StateId == state.Id);
associatedStores.ToList().ForEach(e => DbContext.Set<Store>().Remove(e));
var associatedBranches = state.Branches.Where(s => s.StateId == state.Id);
associatedBranches.ToList().ForEach(s => DbContext.Set<Branch>().Remove(s));
base.Delete(state);
}