we are using Entity Framework 5.0 and have a use case for creating a preview.
So I though I'd see if I could use EF to build my preview in EF data model classes and not persist (no SaveChanges).
(I could of course do this without EF, but I'd thought I'd experiment.)
Now I see some strange things when adding to my context.
In the code snippet below, the Add calls do not effect the collections added to (the DbSets). before = after = 0.
When I do Child.Add, the relation between parent and child is set (parent.Child now contains child). It did not do that before calling Add.
private void Insert(Parent parent, Child child)
{
// Context.Parent does not get any items:
int before = this.Context.Parent.Count();
this.Context.Parent.Add(parent);
int after = this.Context.Parent.Count();
// This affects Parent.Child:
this.Context.Child.Add(child);
}
Here is the vital part from the Context class (removed a lot for clarity):
modelBuilder.Entity<Child>(entity =>
{
// Removed code here...
entity.HasOne(d => d.Parent)
.WithMany(p => p.Child)
.HasForeignKey(d => d.ChildId)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("FK_Child_Parent");
});
So what does Add really do?
Related
I'm getting this error, but I can't seem to find a proper resolution.
The instance of entity type 'Assignment' cannot be tracked because
another instance with the same key value for {'OwnerUserId'} is
already being tracked. When attaching existing entities, ensure that
only one entity instance with a given key value is attached.
Functionally all I'm trying to do is insert some records with a composite primary key. Basically I just need to assign users to each other. I'm telling the context (afaik) to not do tracking, yet the error still seems to happen.
using (var context = new MyDbContext())
{
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
//Get students with no assignments
var missingAssignments = model.Students.Where(x => x.AssignedToRead.Count < 2);
foreach (var s in missingAssignments)
{
var need = 2 - s.AssignedToRead.Count();
List<Guid> assigned = new List<Guid>();
assigned.AddRange(s.AssignedToRead.Select(x => x.UserId));
try
{
for (var i = 0; i < need; i++)
{
//Get a random reader (who's not them)
var shuffle = model.Students.Where(x => x.UserId != s.UserId).Where(x => !assigned.Contains(x.UserId)).ToList();
shuffle.Shuffle();
var rando = shuffle.FirstOrDefault();
var newAssignment = new Assignment() {
OwnerUserId = s.UserId,
ReaderUserId = rando.UserId
};
assigned.Add(rando.UserId);
context.Add(newAssignment);
context.SaveChanges();
}
}catch(Exception ex)
{
//Handle
}
}
}
I've validated I'm not inserting duplicate records (like 2 objects with the same PKs)
Is the problem in the mapping maybe?
modelBuilder.Entity<Assignment>(entity =>
{
entity.HasKey(e => new { e.OwnerUserId, e.ReaderUserId });
entity.ToTable("assignments");
entity.Property(e => e.OwnerUserId).HasColumnName("ownerUserId");
entity.Property(e => e.ReaderUserId).HasColumnName("readerUserId");
entity.Property(e => e.ModifiedDate)
.HasColumnName("modifiedDate")
.HasDefaultValueSql("(getdate())");
entity.HasOne(d => d.Owner)
.WithOne()
.HasPrincipalKey<Assignment>(d => d.OwnerUserId)
.HasForeignKey<AspnetUsers>(d => d.UserId)
.HasConstraintName("FK_users_owner");
entity.HasOne(d => d.Reader)
.WithOne()
.HasPrincipalKey<Assignment>(d => d.ReaderUserId)
.HasForeignKey<AspnetUsers>(d => d.UserId)
.HasConstraintName("FK_users_reader");
});
I don't understand why it would be tracking when I tell it not to
EDIT It appears the issue is with my Navigation Property mapping on the context? If i remove that entirely the Insert works fine, and I still have functioning Nav Properties on the object... I'm so confused, I thought it had to be told what to do?
I believe any entity added using Add will be tracked, the tracking behaviour is for entities that are queried from the DB. Does the error happen on the first instance you add, or on the second, or later?
I suspect the issue is probably with the HasOne.WithOne mapping between Assignment and User. I think this should be a HasOne.WithMany as a One-to-One typically defaults to being between PKs. (Though /w EF Core I believe you can nominate a non-PK 1-to-1) This mapping may be overruling the PK definition or otherwise adding a unique requirement that the tracking is considering. One User can have many assignments, but each assignment has one owner.
Another thing to check: In your entity class definition for Assignment, do you happen to have any [Key] attributes defined that might be tripping up the EF mapping? Your modelBuilder key mapping otherwise looks fine.
I have this value object
public class ProductReference : ValueObject
{
protected ProductReference(){}
public ProductReference(string value){}
public string Value{get; protected set;}
}
I use it in my entity as :
public class Product : Entity<long>
{
protected Product(){}
public ProductReference Reference{get; protected set;}
}
In the OnModelCreating of my DbContext I defined :
modelBuilder.Entity<Product>(entity => {
entity.Property(a => a.Reference)
.HasColumnName("Reference")
.HasConversion(
a => a.Value,
s => new ProductReference (s);
});
When I do :
await dbcontext.Products.Where(p=>p.Reference.Value.Contains("some text")).toArrayAsync();
I get an exception
Expression cannot be converted to a valid SQL statement
I know for sure there is a way to create a custom expression converter, but I cannot find a good, simple and EF Core 3.1 compatible example to deal with my issue and that explain me clearly the concepts I miss.
I found this very interesting project
https://github.com/StevenRasmussen/EFCore.SqlServer.NodaTime
but it is too advanced for me to reproduce it for only my use case.
[EDIT] the ValueObject ans Entity are from
CSharpFunctionalExtensions nuget package, I dont think they are really relevant in my question.
I am not completely sure if i understand correctly what you want to accomplish, but you could try to configure your ProductReference as an Owned Entity Type.
Here you would transform the following code from:
modelBuilder.Entity<Product>(entity => {
entity.Property(a => a.Reference)
.HasColumnName("Reference")
.HasConversion(
a => a.Value,
s => new ProductReference (s);
});
to
modelBuilder.Entity<Product>(entity => {
entity.OwnsOne(a => a.Reference, referenceBuilder => {
referenceBuilder.Property(p => p.Value).HasColumnName("Reference");
});
});
With that your select statement should work.
It could be that you have to play around with the properties of your class ProductReference, or use some modifiers of the fluent API.
So first for some context on what is happening here behind the scenes and why its not gonna work even for build in simple converters like BoolToZeroOneConverter.
The problem here is that you are calling when converting the new ProductReference(s). This is method where you can do whatever you want in it. For example if use it in a Select statement it will again fail. For example:
await dbcontext.Products
.Select(x=>new ProductReference(x.Value))
.toArrayAsync();
The reason is obvious, it won't be able to translate. But why it cant transform it to a query?
Because you are passing a constructor. Inside this constructor you could be doing API calls or using Reflections to set the variables to your object, pretty much anything. That of course is not able to be translated in an SQL query.
Converters are generally used for in memory but they can be used for databse operations as well. This would mean that you will need something like this:
await dbcontext.Products
.Select(x=>new ProductReference() // empty constructor that does nothing
{
Property1 = x.Property1 // I don't know how the constructor maps them
})
.toArrayAsync();
Using this type of expression allow you to actually transalte the expression to an SQL statement and not making the conversion on the SQL DB and not in memory.
Now in your specific case using:
.HasConversion(
a => a.Value,
s => new ProductReference (){};
});
Should fix your issues but I fail to understand why would you want to initialize or convert a ProductReference to a ProductReference.
I have six classes as shown below -
Class A{
int ValA;
ICollection<B> AllBs;
}
Class B{
int ValB;
ICollection<C> AllCs;
}
Class C{
int ValC;
ICollection<D> AllDs;
}
Class D{
int ValD;
ICollection<E> AllEs;
}
Class E{
int ValE;
ICollection<F> AllFs;
}
Class F{
int ValF;
}
I have to eager load an A entity with specific id and I want the result must load all the nested collections eagerly.
I wrote the following code to achieve the same -
var entity = _context.A.Where(a => a.Id == 1)
.Include(a => a.AllBs
.Select(b => b.AllCs
.Select(c => c.AllDs
.Select(d => d.AllEs
.Select(e => e.AllFs)))))
.FirstOrDefault();
Have I written the include statements correctly ? All the nested collections are not getting loaded properly. They are getting loaded upto AllBs, AllCs and further collections are not loaded.
Kindly help.
If this is EF Core you will want to use
_context.A
.Include(a => a.AllBs)
.ThenInclude(b => b.AllCs)
.ThenInclude(c => c.AllDs)
.ThenInclude(d => d.AllEs)
.ThenInclude(e => e.AllFs)
.Single(a => a.Id == 1)
Intellisense may give you a bit of grief diving through collections but it does work through.
For EF6, your statement should work, so if not I would look specifically at your mappings to ensure your FKs are resolving correctly. I'd also remove the .Where() & .FirstOrDefault() and instead use a .Single() at the end. It should not matter in the query generation, but Include statements are best placed before any filtering. Use an SQL Profiler to capture the SQL being sent to the server to see what tables are being requested and how they are joining. This might highlight some FK resolution errors.
I'm having problems with duplicate data during migration with Code First.
A new foreign key record is duplicated each time the migration creates the master record.
The schemas in the database are being created correctly. Namely the Primary Keys and Foreign Key values (the latter being automatically generated)
Can someone please advise thanks about how I detach the foreign key record during migration to prevent it recreating the record or any configuration I need to implement? I've tried updating the state of the foreign key obects before inserting master data. to both modified and detached.
For example I see multi records for the same priority where there should only be 3.
I'm using Entity Framework 6.0.
public class VeloPointDbConfiguration : DbMigrationsConfiguration<VeloPointDbContext>
{
public VeloPointDbConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(VeloPointDbContext context)
{
context.TaskPriorities.AddOrUpdate(EventTaskPriority.Migrations.All());
context.TaskStatuses.AddOrUpdate(TaskStatus.Migrations.All());
EventOrganiserTask.Migrations.All().Select(x => context.Entry(x.Priority).State == EntityState.Modified);
EventOrganiserTask.Migrations.All().Select(x => context.Entry(x.TaskStatus).State == EntityState.Modified);
context.Tasks.AddOrUpdate(EventOrganiserTask.Migrations.All());
}
}
The following examples of the instances i'm using for the data.
I create the following methods for the foreign key objects
public static EventTaskType[] All()
{
return new[]
{
GetDeadline(),
GetEmail(),
GetTelephone(),
GetAppointment(),
GetSnailMail(),
};
}
internal static EventTaskType GetDeadline()
{
return new EventTaskType("09974722-D03E-4CA3-BF3A-0AF7F6CA1B67", 1, "Deadline")
{
Icon = ""
}
}
I call the following methods the create the master data.
public static EventOrganiserTask[] All()
{
return new EventOrganiserTask[]
{
GetBookHQ(1, new DateTime(Event.Migrations.EventDate.Year - 1, 10, 1)),
GetFindSponsor(2, new DateTime(Event.Migrations.EventDate.Year - 1, 10, 1)),
GetRegisterEvent(3, new DateTime(Event.Migrations.EventDate.Year - 1, 10, 1)),
GetBookFirstAid(4, Event.Migrations.EventDate.AddMonths(-6))
};
}
NOTE: When creating the master record, I call the method in the foreign key classes each time - which is the crux of the problem where I need to instruct the migration to detach this item.
public static EventOrganiserTask GetRegisterEvent(int id, DateTime date)
{
return new EventOrganiserTask
{
id = id,
Title = "Register event",
Summary = "Register the road race with the region",
DueDate = date,
Priority = EventTaskPriority.Migrations.GetHighPriority(),
Person = Person.Migrations.GetRaceOrganiser(1),
TaskType = EventTaskType.Migrations.GetDefault(),
TaskStatus = TaskStatus.Migrations.GetDefault(),
};
}
NOTE: When I do make changes to the data from the application, the foreign keys are not being updated. This must be related and indicates my entities are not configured correctly.
LATEST:
I'm still tearing my hair out. I've investigated this further and read about the migrations being multi threaded (It was another thread on stackoverflow but I can't find it again). Indeed running the Seed method I supposed is what it says on the tin and is purley for seeding data, so the data is only being added (regardless of AddOrUpdate - what's that all about then) So I've looked at the behaviour regarding the records being created. First of all I called context.SaveChanges() after creating the look up tables. At this point it doesn't created any duplicates as the items are only referenced once. I then let the seed method run the master data - but argggh - I see duplicates (when the instances are called on the master data). But this did flag something up with regard to the order in which it creates the records.
My next step was to create two migrations, but without any success.
I'm hoping somebody picks up this thread soon. I'm tearing my hair out.
Ok so i've finally found my answer. It was clever enough to create the foreign key relationships from the model, but i needed to be explicitly set the foreign key id field. I chose the Fluent API to explicitly set the relationships and I set the value of the id field in the mapping of the object.
modelBuilder.Entity<Task>()
.HasRequired(x => x.Priority)
.WithMany(x => x.Tasks)
.HasForeignKey(x => x.Priority_id);
Here it is in the seed method
public class VeloPointDbConfiguration : DbMigrationsConfiguration<VeloPointDbContext>
{
public VeloPointDbConfiguration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
}
protected override void Seed(VeloPointDbContext context)
{
context.TaskPriorities.AddOrUpdate(EventTaskPriority.Migrations.All());
context.TaskStatuses.AddOrUpdate(TaskStatus.Migrations.All());
EventOrganiserTask.Migrations.All().Select(x => context.Entry(x.Priority).State == EntityState.Modified);
EventOrganiserTask.Migrations.All().Select(x => context.Entry(x.TaskStatus).State == EntityState.Modified);
context.Tasks.AddOrUpdate(EventOrganiserTask.Migrations.All());
// Foreign Key relationships
modelBuilder.Entity<EventOrganiserTask>()
.HasRequired(x => x.TaskStatus)
.WithMany(x => x.Tasks)
.HasForeignKey(x => x.TaskStatus_id);
modelBuilder.Entity<Task>()
.HasRequired(x => x.TaskType)
.WithMany(x => x.Tasks)
.HasForeignKey(x => x.TaskType_id);
modelBuilder.Entity<Task>()
.HasRequired(x => x.Priority)
.WithMany(x => x.Tasks)
.HasForeignKey(x => x.Priority_id);
}
}
If I try to delete a "child" row I always get an exception. Here is a snipset:
using (var context = new CompanyContext())
{
ItemType itemType = context.ItemTypes.FirstOrDefault(i => i.Name == "ServerType");
ItemTypeItem itemTypeItem = itemType.Items.FirstOrDefault(i => i.Name == "DatabaseServer");
itemType.Items.Remove(itemTypeItem);
context.SaveChanges(); <=== exception!
}
The following exception is thrown on the SaveChanges() method.
"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."
Entity Configuration
public class ItemTypeConfiguration : NamedEntityConfiguration<ItemType>
{
public ConfigurationColumn ParentIDColumn;
public ConfigurationColumn ValidationPatternColumn;
public ItemTypeConfiguration() : base()
{
ParentIDColumn = new ConfigurationColumn() { Name = "ParentID", Ordinal = base.LastOrdinalPosition + 1 };
ValidationPatternColumn = new ConfigurationColumn() { Name = "ValidationPattern", Length = 1024, Ordinal=base.LastOrdinalPosition + 2};
this.Property(t => t.ParentID)
.HasColumnName(ParentIDColumn.Name)
.HasColumnOrder(ParentIDColumn.Ordinal);
this.HasOptional(t => t.Parent).WithMany().HasForeignKey(u => u.ParentID).WillCascadeOnDelete(false);
this.Property(t => t.ValidationPattern)
.HasColumnName(ValidationPatternColumn.Name)
.HasColumnOrder(ValidationPatternColumn.Ordinal)
.HasMaxLength(ValidationPatternColumn.Length);
}
...
public class ItemTypeItemConfiguration : NamedEntityConfiguration<ItemTypeItem>
{
public ConfigurationColumn ItemTypeIDColumn;
public ItemTypeItemConfiguration() : base()
{
ItemTypeIDColumn = new ConfigurationColumn(){Name="ItemTypeID", IsRequired=true, Ordinal= base.LastOrdinalPosition+1};
this.Property(t => t.ItemTypeID)
.HasColumnName(ItemTypeIDColumn.Name)
.HasColumnOrder(ItemTypeIDColumn.Ordinal);
this.HasRequired(t => t.ItemType).WithMany(t=>t.Items).HasForeignKey(u => u.ItemTypeID).WillCascadeOnDelete(true);
}
...
I found the blog but I don't have the "DeleteObject" method.
http://blog.clicdata.com/2013/07/04/the-operation-failed-the-relationship-could-not-be-changed-because-one-or-more-of-the-foreign-key-properties-is-non-nullable/
Any ideas? Thank you.
You need to delete the ItemTypeItem.
It is not possible to just remove it from the Items list as it cannot exist by itself, because it has a non-nullable foreign key referencing ItemType (ItemTypeID).
To delete the ItemTypeItem add
context.Entry(itemTypeItem).State = EntityState.Deleted;
In the entity framework 6.0 if you remove the entity from the main context set it will work. For example to remove an investment entity you would do the following:
context.Investments.Remove(entity);
context.SaveChanges();
This is different than attempting to remove the entity from its parent/owner, as the following:
bankAccount.Investments.Remove(entity);
context.SaveChanges();
This will throw the relationship could not be changed exception listed above. Hope this helps.
In entity 6.0 there is a difference between:
context.Investments.Remove(entity);
and
context.Entry(entity).State = EntityState.Deleted;
When using the first and cascading deletes are enabled, EF will internally perform the necessary deletes of child collections.
When using the second option, EF will not handle the necessary deletes, but let you handle the rebinding/deletion of these child objects.
This issue arise because we try to delete the parent table still child table data is present. We solve the problem with help of cascade delete.
In model Create method in dbcontext class.
modelBuilder.Entity<Job>()
.HasMany<JobSportsMapping>(C => C.JobSportsMappings)
.WithRequired(C => C.Job)
.HasForeignKey(C => C.JobId).WillCascadeOnDelete(true);
modelBuilder.Entity<Sport>()
.HasMany<JobSportsMapping>(C => C.JobSportsMappings)
.WithRequired(C => C.Sport)
.HasForeignKey(C => C.SportId).WillCascadeOnDelete(true);
After that,In our API Call
var JobList = Context.Job
.Include(x => x.JobSportsMappings) .ToList();
Context.Job.RemoveRange(JobList);
Context.SaveChanges();
Cascade delete option delete the parent as well parent related child table with this simple code. Make it try in this simple way.
Remove Range which used for delete the list of records in the database Thanks
The other answers describing why the error occurs are correct, but it is actually possible to get EF to fully delete the child when .Remove is called on the parent object's collection of children, you don't need to go directly to the child entity's table in the DB context and delete it from that.
To get that to work you need to set up an Identifying Relationship, as described in this answer.