EF6 Many to many Insert - c#

My problem is to save existing objects that are part of a many-to-many relationship in a code-first database with EF6.
I am receiving objects from a web service and they look (simplified) like:
public class Car
{
[DataMember]
public virtual string CarId { get; set; }
[DataMember]
public virtual ICollection<Contract> Contracts { get; set; }
}
public class Contract
{
[DataMember]
public virtual string ContractId { get; set; }
[DataMember]
public virtual ICollection<Car> Cars { get; set; }
}
I have a code-first database and set the relationship with:
modelBuilder.Entity<Contract>().HasKey(t => new {t.ContractId});
modelBuilder.Entity<Car>().HasKey(t => new {t.CarId})
.HasMany(c => c.Contracts)
.WithMany(c => c.Cars)
.Map(x =>
{
x.ToTable("CarContracts");
x.MapLeftKey("CarId");
x.MapRightKey("ContractId");
});
When I get a list of cars I can save the first car and EF creates the relation table and the contracts successfully. On the second car the save fails saying "constraint failed UNIQUE constraint failed: Contracts.ContractId", since I'm trying to insert a Contract with the same Id as an already existing Contract.
The solutions I found to this problem is to set the Contract in the dbContext to Attached:
foreach (var contract in car.Contracts)
{
context.Contract.Attach(contract);
}
This throws the same exception as the previous save. When I try to modify the Contract-list of the second car I get a NotSupportedException, the only solution I can think of is recreating the car objects and attach the same Contract-object to them, which seems unnecessarily complicated.
Is there any way to tell EF that my two different Contract-objects actually are the same?

Is there any way to tell EF that my two different Contract-objects
actually are the same?
The only way is they actually are the same object. If they are different instances even having all the same data EF won't recognize them as same entity.
So the only solution is to replace all same id contract instances by a single one.
Or simpler: create an explicit entity representing the N:N relation and then simply build a list to insert as follows:
var toInsert = new List<CarContract>();
foreach(var car in cars)
{
toInsert.AddRange(car.Select(x=>new CarContract {CarId=car.Id,ContractId=x.Id}));
}

Related

Entity framework: Foreign entity without requiring foreign entity to actually exist entity to exist

I have an audit-tracking like system, that contains the following two entities:
The JobCreate entity:
public class JobCreate
{
[Key] public string JobId { get; set; }
public List<AffectedEntity> AffectedEntities { get; set; }
}
And the AffectedEntity entity:
public abstract class AffectedEntity
{
[Required]
public string JobId { get; set; }
public int Id { get; set; }
[CanBeNull] public JobCreate Job { get; set; }
}
So far this is just a normal foreign key relation:
modelBuilder.Entity<JobCreate>()
.HasMany(j => j.AffectedEntities)
.WithOne(a => a.Job)
.HasForeignKey(a => a.JobId)
.IsRequired(false)
.OnDelete(DeleteBehavior.Cascade);
Entity Framework generates a foreign key for this relationship. My problem with this is that this audit system is event driven, which means it receives the events that creates the AffectedEntity and the event that creates the JobCreate entries out of order. In other words, the JobCreate entity might not yet exist when the AffectedEntity is created. However as far as the domain goes, this is actually fine. So how do I model that in Entity Framework? I want to be able to "navigate" along that connection from JobCreate to AffectedEntity, however the other direction is not necessary.
the JobCreate entity might not yet exist when the AffectedEntity is created. However as far as the domain goes, this is actually fine. So how do I model that in Entity Framework?
Just have the relationship in the EF model, but omit it or set the FK to not be enforced in the back-end. EG in SQL Server you would set the Foreign Key Constraint to NOCHECK.
Just beware that EF may assume that the FK is enforced when it creates queries. EG if you query db.AffectedEntities.Inclue("JobCreate") it may use an INNER JOIN and not return any AffectedEntities without a JobCreate.
And if you need to deal with AffectedEntities with a null JobID, you'd have to change the data type to int?.

Accessing related tables with [ForeignKey] without actually having a foreign key in the DB, while using EF Core

I'm reverse-engineering a third-party database that I can't change. There are no foreign keys, but FK relationship have been enforced by the application that was using it, so the data is OK.
I've run scaffolding of tables via Scaffold-DbContext and got classes created. Now due to lack of FKs I have a problem. Here are simplified version of generated classes:
public class Person
{
public int EmployeeType {get; set; }
// other fields
}
public class EmployeeType
{
public int EmployeeType { get; set; } // this is the key in this table
// other fields
}
So an Employee has a type assigned to it, where types are stored in a lookup table. And now I'd like to query employee together with their type. So I'm trying:
public class Person
{
public int EmployeeType {get; set; }
[ForeignKey(nameof(EmployeeType))]
public EmployeeType EmployeeTypeReference { get; set; }
// other fields
}
var people = dbContext.Person
.Include(p => p.EmployeeTypeReference)
.Where(p => p.EmployeeType != null)
.Take(10)
.ToList();
var k = people.Select(c => new { c.EmployeeType, c.EmployeeTypeReference}).ToList();
And then I see this:
I.e. the related object is not retrieved.
I've also tried this:
modelBuilder.Entity<Person>(entity =>
{
entity.HasOne<EmployType>(e => e.EmployeeTypeReference)
.WithMany(t => t.People);
}
But the result is the same.
Anything else I can try doing to get the related object through with .Include?
Problem is with the user as per usual. However some people might be caught out by this as well, so I'll spell it out.
Issue was with .Include(). I was using EF6 version of from namespace System.Data.Entity. But I needed EF Core version that came from Microsoft.EntityFrameworkCore.
That was brought by dual-running EF6 (because dependencies) and EF Core in the same project.

Entity framework 6 Concept clear regarding Remove, RemoveRange, EntityState.Deleted

I use Entity framework 6 in my projects and I always have doubts regarding some of the concepts which are used to delete objects using EF.
I still don't know which one works in which scenario. I just try all and if one works I leave it until the code is working. But no wi need to understand this concept once and for all. I did my research my unable to understand the concept clearly.
I have a domain class in EF which have multiple referencing entities. For example. I have a domain class called Course and It has multiple referencing objects mentioned below in the code.
public class Course
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public int CompanyId { get; set; }
public virtual Company Company { get; set; }
public virtual PricingSchedule PricingSchedule { get; set; }
public virtual ICollection<CustomerCourse> AssignedCustomers { get; set; }
public virtual ICollection<License> Licenses { get; set; }
public virtual ICollection<GroupLicense> GroupLicenses { get; set; }
public virtual ICollection<GroupCourse> GroupCourses { get; set; }
public virtual ICollection<Learner> Learners { get; set; }
}
Now I have to delete the course from the DB with all of its referencing entities. For example, If the course is deleting then its properties like AssignedCustomers, Licenses etc all must be deleted.
But I don't understand one thing using Entity framework.
For deleting an entity from DB we have multiple options like.
Remove
RemoveRange
EntityState.Deleted
Sometimes Remove works but sometime RemoveRange Works and sometime Entitystate.Deleted works. Why?
My code is for deleting a Course
var courses = _context.Courses
.Include("AssignedCustomers")
.Include("PricingSchedule")
.Include("Licenses")
.Include("GroupCourses")
.Include("GroupLicenses")
.Where(e => courseIds.Contains(e.Id)).ToList();
if (courses != null && courses.Count > 0)
{
courses.ForEach(currentCourse =>
{
_context.Entry(currentCourse.PricingSchedule).State = EntityState.Deleted;
Sometime remove range works and code run successfully
_context.CustomerCourses.RemoveRange(currentCourse.AssignedCustomers);
Below line of code gives me error but in other scenario it works why?
//currentCourse.AssignedCustomers.ToList().ForEach(ac =>
//{
// //currentCourse.AssignedCustomers.Remove(ac);
// _context.Entry(ac).State = EntityState.Deleted;
//});
_context.Entry(currentCourse).State = EntityState.Deleted;
});
}
_context.SaveChanges();
Can anyone explain to me the difference in which situation I should use what?
The error I receive most of the time is
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.
This error comes up when I use this piece of code
currentCourse.AssignedCustomers.ToList().ForEach(ac =>
{
_context.Entry(ac).State = EntityState.Deleted;
});
OR
currentCourse.AssignedCustomers.ToList().ForEach(ac =>
{
currentCourse.AssignedCustomers.Remove(ac):
});
after that when I hit SaveChanges The error comes up.
You need to set up the cascade rules in your schema and within Entity Framework so that it knows which related entities will be deleted when you go to delete a course. For instance you will want to cascade delete while others like Learner would likely have a null-able key which can be cleared if a course is removed.
Provided it is set up correctly, you should just need to use: context.Courses.Remove(course); and the related entities will be removed or disassociated automatically. Start with a simpler example of your parent-child relationships, one child to cascade delete, another to disassociate with a nullable FK. Your current example looks to also have many-to-many associations (GroupCourses) so depending on the mapping/relationships the approach will vary.

Mapping viewModel object to ICollection entity

I have a basic table with a few FK references. So when I retrieve an entity for an update operation; that entity contains ICollections of related entites. My main viewModel contains Lists which correspond to these ICollections. However, since some other models represent 1-1 mapping, I have object instead of List. But inside the Entity they continue to be represented as ICollections.
This is giving me some problems when trying to map between viewModel and Entity. I am using Automapper for the mapping. I have
mapper.Map(viewModel, entity);
Currently I am leaving out the problematic models from this mapping and adding them separately. Is there a way to handle everything in one mapping? Is there a way to deal with the ICollections which ideally should be a single object?
EDIT
public class MainViewModel
{
public EntityVM1 vm1 { get; set; }
public List<EntityVM2> vm2 { get; set; }
public List<EntityVM3> vm3 { get; set; }
}
public class MainEntity
{
... some scalar props...
public virtual ICollection<Entity1> e1 { get; set; }
public virtual ICollection<Entity2> e2 { get; set; }
public virtual ICollection<Entity3> e3 { get; set; }
}
Entity1 and EntityVM1 are causing the problem.
Thanks
You can always override the default mapping system in the mapping config of AutoMapper, you should have a peek at the runtime polymorphism in the mapping inheritance section of the documentation.
If what you want on the entity is a straight object, why not take Automapper out of the equation and just force EF to map it using a one to one system... i.e
modelBuilder.Entity<MainEntity>()
.HasOne(p => p.Entity1)
.WithOne(i => i.MainEntity)
.HasForeignKey<Entity1>(b => b.MainEntityForignKey);
HAve a peek at the EF docs, under section one-to-one for more info

Unhandled Exception after Upgrading to Entity Framework 4.3.1

Error:
Unhandled Exception: System.Data.SqlClient.SqlException: The operation failed because an index or statistics with name 'IX_ID' already exists on table 'PrivateMakeUpLessons'.
Model (Simplified, building in a separate test project for debugging):
public abstract class Lesson
{
public Guid ID { get; set; }
public string Room { get; set; }
public TimeSpan Time { get; set; }
public int Duration { get; set; }
}
public abstract class RecurringLesson : Lesson
{
public int DayOfWeek { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public string Frequency { get; set; }
}
public class PrivateLesson : RecurringLesson
{
public string Student { get; set; }
public string Teacher { get; set; }
public virtual ICollection<Cancellation> Cancellations { get; set; }
}
public class Cancellation
{
public Guid ID { get; set; }
public DateTime Date { get; set; }
public virtual PrivateLesson Lesson { get; set; }
public virtual MakeUpLesson MakeUpLesson { get; set; }
}
public class MakeUpLesson : Lesson
{
public DateTime Date { get; set; }
public string Teacher { get; set; }
public virtual Cancellation Cancellation { get; set; }
}
Configuration:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Lesson>().ToTable("Lessons");
modelBuilder.Entity<RecurringLesson>().ToTable("RecurringLessons");
modelBuilder.Entity<PrivateLesson>().ToTable("PrivateLessons");
modelBuilder.Entity<MakeUpLesson>().ToTable("PrivateMakeUpLessons");
modelBuilder.Entity<Cancellation>()
.HasOptional(x => x.MakeUpLesson)
.WithRequired(x => x.Cancellation);
base.OnModelCreating(modelBuilder);
}
Notes:
This worked fine in EF 4.2. Is there something wrong with my model? The actual model is much more complicated which is why I have all the classes abstracted out. Also, I am working against an existing database so I need to use Table-Per-Type inheritance.
If I change the relationship of Cancellation to PrivateMakeUpLesson from 1 to 0..1 to 0..1 to 0..1 it works. This is undesirable because you can't have a PrivateMakeUpLesson without a Cancellation.
Also, if I make PrivateMakeUpLesson NOT inherit from Lesson then it also works, but it IS a lesson and needs to remain so for existing business logic.
I'd appreciate any guidance. Thank you!
Edit:
Starting a bounty. I can't find any documentation on what changed between EF 4.2 and EF 4.3 with regard to the index generation for code first. It's clear that EF 4.3 is creating more indexes and that the naming scheme has changed but I want to know if there's a bug in EF or if there is something fundamentally wrong with my model or fluent API configuration.
As of EF 4.3, indexes are added for freign key columns during database creation. There is a bug that can cause an index to be created more than once. This will be fixed in a future EF release.
Until then, you can work around the issue by creating your database using Migrations instead of database initializers (or the Database.Create() method).
After generating the initial migration, you will need to delete the redundant call to Index().
CreateTable(
"dbo.PrivateMakeUpLessons",
c => new
{
ID = c.Guid(nullable: false),
...
})
.PrimaryKey(t => t.ID)
.ForeignKey("dbo.Lessons", t => t.ID)
.ForeignKey("dbo.Cancellations", t => t.ID)
.Index(t => t.ID)
.Index(t => t.ID); // <-- Remove this
To continue creating your database at run-time, you can use the MigrateDatabaseToLatestVersion initializer.
In my opinion this is clearly a bug.
The problem starts with the observation that EF creates an index IX_ID at all. If you strip down the model to the following...
public abstract class Lesson
{
public Guid ID { get; set; }
}
public class RecurringLesson : Lesson
{
}
public class MyContext : DbContext
{
public DbSet<Lesson> Lessons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<RecurringLesson>().ToTable("RecurringLessons");
}
}
... and let EF create the database schema you get two tables Lessons and RecurringLessons as expected for a TPT inheritance mapping. But I am wondering why it creates two indices for the table RecurringLessons:
Index PK_RecurringLessons (clustered, unique) with Index column ID
Index IX_ID (not clustered, not unique) with Index column ID again
I don't know if there is any benefit for the database to have a second index on the same column. But for my understanding it doesn't make sense 1) to create an index on the same column that is already covered in the PK clustered index, and 2) to create a not unique index on a column which is the primary key and therefore necessarily unique.
Moreover due to the one-to-one relationship EF tries to create an index on the table of the dependent of this association which is PrivateMakeUpLessons. (It's the dependent (and not the principal) because Cancellation is required in entity MakeUpLesson.)
ID is the foreign key in this association (and primary key at the same time because one-to-one relationships are always shared primary key associations in Entity Framework). EF apparently always creates a index on the foreign key of a relationship. But for one-to-many relationships this is not a problem because the FK column is different from the PK column. Not so for one-to-one relatonships: The FK and PK are the same (that is ID), hence EF tries to create an index IX_ID for this one-to-one relationship which already exists due to the TPT inheritance mapping (which leads to a one-to-one relationship as well from database perspective).
The same consideration as above applies here: The table PrivateMakeUpLessons has a clustered PK index on column ID. Why is a second index IX_ID on the same column required at all?
In addition EF doesn't seem to check that it already wants to create an Index with name IX_ID for the TPT inheritance, leading finally to the exception in the database when the DDL is sent to create the database schema.
EF 4.2 (and before) didn't create any indices (except PK indices) at all, this was introduced in EF 4.3, especially indices for FK columns.
I didn't find a workaround. In the worst case you have to create the database schema manually and avoid that EF tries to create it (= disable database initialization). In the best case there is a way to disable automatic FK index creation, but I don't know if it's possible.
You can submit a bug report here: http://connect.microsoft.com/VisualStudio
Or maybe someone from EF development team will see your question here and provide a solution.
I got a very similar error to this one in my code a while back. Try putting the cancellation list inside the Lesson class. That's what solved my problem.
Below I describe 2 scenarios what is probably going wrong. Please read in depth by clicking the links I provided to know more about my explanation.
First
Lesson and RecurringLesson are abstract classes (so you want to have it as the base classes).
You are creating a table of the Lesson and the RecurringLesson entities which will result in a Table per hierarchy structure.
brief description
Creating a class of the base table will result in one big table which contains the columns of all inherited tables. So all properties of PrivateLesson, MakeUpLesson and all others inherited entities will be stored in the Lessons table. EF will add also a Discriminator column. The value of this column defaults to the persistent class name (like "PrivateLesson" or "MakeUpLesson") only the column matching to that particular entity (matching the Discriminator value) will be used in that particular row.
BUT
You are also mapping the inherited classes like PrivateLesson and MakeUpLesson. This will force EF to use the Table per Type structure which results in one table per class. This can cause conflicts you are facing right now.
Second
Your example shows you have an one-to-one relationship (Cancellation -> MakeUpLesson) and a one-to-many relationship (Cancellation -> PrivateLesson) because PrivateLesson and MakeUpLessonare both (indirect) inherited from Lesson in combination with the first described scenario can cause problems because it will result in 2 foreign key relationships in the database per entity. (one using Table per hierarchy structure and one using the Table per Type structure).
Also this post can help you defining a correct one-to-one definition.
Please verify by performing the following steps:
I assume you have your own test environment so you can create new test databases
1.
Delete the relationships to the Cancellation by commenting out all properties to this class:
public class PrivateLesson : RecurringLesson
{
public string Student { get; set; }
public string Teacher { get; set; }
//public virtual ICollection<Cancellation> Cancellations { get; set; }
}
public class Cancellation
{
public Guid ID { get; set; }
public DateTime Date { get; set; }
//public virtual PrivateLesson Lesson { get; set; }
//public virtual MakeUpLesson MakeUpLesson { get; set; }
}
public class MakeUpLesson : Lesson
{
public DateTime Date { get; set; }
public string Teacher { get; set; }
//public virtual Cancellation Cancellation { get; set; }
}
And remove the configuration to it:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Lesson>().ToTable("Lessons");
modelBuilder.Entity<RecurringLesson>().ToTable("RecurringLessons");
modelBuilder.Entity<PrivateLesson>().ToTable("PrivateLessons");
modelBuilder.Entity<MakeUpLesson>().ToTable("PrivateMakeUpLessons");
//modelBuilder.Entity<Cancellation>()
// .HasOptional(x => x.MakeUpLesson)
// .WithRequired(x => x.Cancellation);
base.OnModelCreating(modelBuilder);
}
2.
Create a new empty database
3.
Let EF generate the table structure for you in this empty database.
4.
Verify the first scenario. If that's true this need to be fixed first by using the Table per hierarchy structure OR the Table per Type structure. Probably you want to use the Table per hierarchy structure because (if I understand your question well) there is already an production environment.
When my project was updated from EF 6.0.2 to EF 6.1.1, I had such a problem, then back to 6.0.2, after the return of an older version, the error disappeared

Categories