I am using EF 4.1 and lazy loading. I have below entities:
public abstract class PersonBase
{
[Key(), Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
....
[ForeignKey("Quantity")]
public virtual int? QuantityId { get; set; }
public virtual Quantity Quantity { get; set; }
....
}
public class ConcretePerson : PersonBase
{
....
}
public class Quantity
{
[Key(), Required]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public virtual float QuantityA { get; set; }
[Required]
public virtual float QuantityB { get; set; }
[Required]
public virtual float QuantityC { get; set; }
}
IDbSet<Quantity> Quantities;
IDbSet<ConcretePerson> ConcretePersons;
IDbSet<PersonBase> Persons;
so in my code I perform below:
using (DataBaseContext context = new DataBaseContext())
{
IQueryable<ConcretePerson> concretePersonCollection = context.ConcretePersons.Where(<condition>);
foreach (ConcretePerson concretePerson in concretePersonCollection)
{
...
concretePerson.Quantity.QuantityA = new_quantity_A;
concretePerson.Quantity.QuantityB = new_quantity_B;
concretePerson.Quantity.QuantityC = new_quantity_C;
...
}
...
DbEntityEntry<ConcretePerson> entityEntry;
Quantity quantity;
foreach (ConcretePerson concretePerson in concretePersonCollection)
{
entityEntry = context.Entry<ConcretePerson>(concretePerson);
if (entityEntry.State == System.Data.EntityState.Modified)
{
quantity = ((ConcretePerson)entityEntry.CurrentValues.ToObject()).Quantity;
}
else
{
quantity = concretePerson.Quantity;
}
...
}
...
context.SaveChanges();
}
Note that I only perform SaveChanges at the end so database is not updated until this point is reached.
I have problems within the second foreach:
1.- When entityEntry.State is modified it happens that ((ConcretePerson)entityEntry.CurrentValues.ToObject()).Quantity is null but
((ConcretePerson)entityEntry.CurrentValues.ToObject()).QuantityId is correct (contains the correct value)
Why? How to get this different from null with the current values (neither original values nor database values), just current values?
2.- If I check directly the Quantity by performing concretePerson.Quantity it is not null but
concretePerson.Quantity contains the current values (the ones updated in the first foreach),
not the original ones (the values before updating in the first foreach). Should not concretePerson.Quantity
contain the original values (before updating in the first foreach) instead? because I have not
performed any context.savechanges between the two foreach loops.
3.-Context.SaveChanges is not saving the changes done to the database and is not raising any error. <---- This point is solved, I was pointing to a different context, no using the same (now I am using the same).
That is an odd way of trying to get values... try this.
concretePerson.Quantity will be the local copy of the entity so it will have whichever value you assigned to it.
In the first foreach you are actually modifying each of the items in the collection (even if it is not saved to the database yet it is still in memory, otherwise how would EF know what to save to the database?).
In the second you are actually checking the same collection to see if entities have been modified (which they have) and then getting the current value. However the current value for quantity will be the same as .quantity because you have modified the entity. If you check the original value for modified entries you will see that it is different.
Basically CurrentValue is the value of the in memory entity (if you change the property CurrentValue changes). OriginalValue is "usually the entity's property values as they were when last queried from the database"
Nevermind ;p
Related
I have a database first model.
My application UI provides a group of checkboxes, one for each value in Data_Type.
When the user checks one, I expect a row to be added in BUS_APPL_DATA_TYPE,
however I'm getting an error about Cannot insert explicit value for identity column in DATA_TYPE (And I absolutely do not actually want to insert data in this table)
My EF Model class for BUS_APPL has this property
public ICollection<BusApplDataType> BusApplDataType { get; set; }
And that EF Model class looks like
public partial class BusApplDataType
{
public int BusApplId { get; set; }
public int DataTypeId { get; set; }
[Newtonsoft.Json.JsonIgnore]
public BusAppl BusAppl { get; set; }
public DataType DataType { get; set; }
}
What exactly do I need to add to the BusApplDataType collection to get a record to be inserted in BUS_APPL_DATA_TYPE?
Edit:
At a breakpoint right before SaveChanges.
The item at index 2 is an existing one and causes no issues.
The item at index 3 is new. Without this everything updates fine. There is a DATA_TYPE with id 5 in the database.
The surrounding code, if it helps.
[HttpPut("{id}")]
public IActionResult Update(int id, [FromBody] BusAppl item)
{
...
var existing = _context.BusAppl.FirstOrDefault(t => t.Id == id);
...
existing.BusApplDataType = item.BusApplDataType; //A bunch of lines like this, only this one causes any issue.
...
_context.BusAppl.Update(existing);
_context.SaveChanges();
return new NoContentResult();
}
My issue was that I needed to use my context to look up the actual entity, using info passed, instead of using the one with all the same values that was passed into my api directly.
I'm trying to fetch (in disconnected way) an entity with its all related entities and then trying to update the entity. But I'm getting the following error:
Attaching an entity of type 'Feature' failed because another entity of the same type already has the same primary key value.
public class Person
{
public int PersonId { get; set; }
public string Personname { get; set }
public ICollection Addresses { get; set; }
}
public class Address
{
public int AddressId { get; set; }
public int PersonId { get; set; }
public string Line1 { get; set; }
public string City { get; set; }
public string State { get; set; }
public Person Person { get; set; }
public ICollection<Feature> Features { get; set; }
}
// Many to Many: Represented in database as AddressFeature (e.g Air Conditioning, Central Heating; User could select multiple features of a single address)
public class Feature
{
public int FeatureId { get; set; }
public string Featurename { get; set; }
public ICollection<Address> Addresses { get; set; } // Many-To-Many with Addresses
}
public Person GetCandidate(int id)
{
using (MyDbContext dbContext = new MyDbContext())
{
var person = dbContext.People.AsNoTracking().Where(x => x.PersonId == id);
person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
return person.FirstOrDefault();
}
}
public void UpdateCandidate(Person newPerson)
{
Person existingPerson = GetPerson(person.Id); // Loading the existing candidate from database with ASNOTRACKING
dbContext.People.Attach(existingPerson); // This line is giving error
.....
.....
.....
}
Error:
Additional information: Attaching an entity of type 'Feature' failed because another entity of the same type already has the same primary key value.
It seems like (I may be wrong) GetCandidate is assigning every Feature within Person.Addresses a new instance. So, how could I modify the GetCandidate to make sure that the same instance (for same values) is bing assisgned to Person.Addresses --> Features.
Kindly suggest.
It seems like (I may be wrong) GetCandidate is assigning every Feature within Person.Addresses a new instance. So, how could I modify the GetCandidate to make sure that the same instance (for same values) is bing assisgned to Person.Addresses --> Features.
Since you are using a short lived DbContext for retrieving the data, all you need is to remove AsNoTracking(), thus allowing EF to use the context cache and consolidate the Feature entities. EF tracking serves different purposes. One is to allow consolidating the entity instances with the same PK which you are interested in this case, and the second is to detect the modifications in case you modify the entities and call SaveChanges(), which apparently you are not interested when using the context simply to retrieve the data. When you disable the tracking for a query, EF cannot use the cache, thus generates separate object instances.
What you really not want is to let EF create proxies which hold reference to the context used to obtain them and will cause issues when trying to attach to another context. I don't see virtual navigation properties in your models, so most likely EF will not create proxies, but in order to be absolutely sure, I would turn ProxyCreationEnabled off:
public Person GetCandidate(int id)
{
using (MyDbContext dbContext = new MyDbContext())
{
dbContext.Configuration.ProxyCreationEnabled = false;
var person = dbContext.People.Where(x => x.PersonId == id);
person = person.Include(prop => prop.Addresses.Select(x => x.Country)).Include(prop => prop.Addresses.Select(x => x.Features));
return person.FirstOrDefault();
}
}
I have a trouble with EF (6.1.3)
I have created next classes (with many-to-many relationship):
public class Record
{
[Key]
public int RecordId { get; set; }
[Required]
public string Text { get; set; }
public virtual ICollection<Tag> Tags { get; set; }
}
public class Tag
{
[Key]
public int TagId { get; set; }
[Required]
public string Name { get; set; }
public virtual ICollection<Record> Records{ get; set; }
}
And method:
void AddTags()
{
Record[] records;
Tag[] tags;
using (var context = new AppDbContext())
{
records = context.Records.ToArray();
}//remove line to fix
tags = Enumerable.Range(0, 5).Select(x => new Tag()
{
Name = string.Format("Tag_{0}", x),
Records= records.Skip(x * 5).Take(5).ToArray()
}).ToArray();
using (var context = new AppDbContext()){ //remove line to fix
context.Tags.AddRange(tags);
context.SaveChanges();
}
}
If I use two contexts, the records (which were added to created tags) will be duplicated. If I remove marked rows - problem disappears.
Is there any way to fix this problem without using the same context?
If you can, better reload entities or not detach them at all. Using multiple context instances in application is overall making things much more complicated.
The problem for you comes from the Entity Framework entity change tracker. When you load entitites from your DbContext and dispose that context, entities get detached from entity change tracker, and Entity Framework has no knowledge of any changes made to it.
After you reference detached entity by an attached entity, it (detached entity) immediately gets into entity change tracker, and it has no idea that this entity was loaded before. To give Entity Framework an idea that this detached entity comes from the database, you have to reattach it:
foreach (var record in records) {
dbContext.Entry(record).State = EntityState.Unchanged;
}
This way you will be able to use records to reference in other objects, but if you have any changes made to these records, then all these changes will go away. To make changes apply to database you have to change state to Added:
dbContext.Entry(record).State = EntityState.Modified;
Entity Framework uses your mappings to determine row in database to apply changes to, specifically using your Primary Key settings.
A couple examples:
public class Bird
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public string Color { get; set; }
}
public class Tree
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class BirdOnATree
{
[Column(Order = 0), Key, ForeignKey("Bird")]
public int BirdId { get; set; }
public Bird Bird { get; set; }
[Column(Order = 1), Key, ForeignKey("Tree")]
public int TreeId { get; set; }
public Tree Tree { get; set; }
public DateTime SittingSessionStartedAt { get; set; }
}
Here's a small entity structure so that you could see how it works. You can see that Bird and Tree have simple Key - Id. BirdOnATree is a many-to-many table for Bird-Tree pair with additional column SittingSessionStartedAt.
Here's the code for multiple contexts:
Bird bird;
using (var context = new TestDbContext())
{
bird = context.Birds.First();
}
using (var context = new TestDbContext())
{
var tree = context.Trees.First();
var newBirdOnAtree = context.BirdsOnTrees.Create();
newBirdOnAtree.Bird = bird;
newBirdOnAtree.Tree = tree;
newBirdOnAtree.SittingSessionStartedAt = DateTime.UtcNow;
context.BirdsOnTrees.Add(newBirdOnAtree);
context.SaveChanges();
}
In this case, bird was detached from the DB and not attached again. Entity Framework will account this entity as a new entity, which never existed in DB, even though Id property is set to point to existing row to database. To change this you just add this line to second DbContext right in the beginning:
context.Entry(bird).State = EntityState.Unchanged;
If this code is executed, it will not create new Bird entity in DB, but use existing instead.
Second example: instead of getting bird from the database, we create it by ourselves:
bird = new Bird
{
Id = 1,
Name = "Nightingale",
Color = "Gray"
}; // these data are different in DB
When executed, this code will also not create another bird entity, will make a reference to bird with Id = 1 in BirdOnATree table, and will not update bird entity with Id = 1. In fact you can put any data here, just use correct Id.
If we change our code here to make this detached entity update existing row in DB:
context.Entry(bird).State = EntityState.Modified;
This way, correct data will be inserted to table BirdOnATree, but also row with Id = 1 will be updated in table Bird to fit the data you provided in the application.
You can check this article about object state tracking:
https://msdn.microsoft.com/en-US/library/dd456848(v=vs.100).aspx
Overall, if you can avoid this, don't use object state tracking and related code. It might come to unwanted changes that are hard to find source for - fields are updated for entity when you don't expect them to, or are not updated when you expect it.
I'm using Entity Framework 4.3.1 Code First.
I have a pretty simple expression and entity model.
using (var PMCtx = new PMContext("PMEntities"))
{
var results =
PMCtx.Fetch<vwSDHOriginalMW>()
.Where(x => x.DT >= StartDate && x.DT < EndDate)
.ToList();
return results;
}
public class vwSDHOriginalMW : IEntityObject, IPMContext
{
public int Schedule { get; set; }
public DateTime DT { get; set; }
public int HE { get; set; }
public Decimal OrgMW { get; set; }
public Decimal DELIVERMW { get; set; }
public string NERCCode { get; set; }
public string NERCCodeStatus { get; set; }
public int SDHSDHID { get; set; }
}
This was taking 15 seconds every time, not just the first time. The model is mapped to a view in a Sql Server 2008 database. I output the query that EF was sending, and ran it in SSMS and it took a fraction of a second.
Why is this so slow in Entity Framework?
IEntityObject appears to be a marker interface so that the original programmer could be sure these were the only that get put into the generic.
EDIT 1
Fetch ends up going through some layer wrappers to get to the data layer where it does this:
private DbSet<TEntity> FetchSet<TEntity>()
where TEntity : class, IEntityObject
{
Type PassedType = typeof(TEntity);
if (!CheckedTypes.Any(x => x.FullName == PassedType.FullName))
if (!PassedType.GetInterfaces().Any(x => CtxInterfaces.Contains(x)))
throw new ArgumentException("Type passed is not a DbSet type of constructed context.");
else
CheckedTypes.Add(PassedType);
return privateContext.Set<TEntity>();
}
Cleaned up example of the query EF is sending
SELECT [Schedule],
[DT],
[HE],
[OrgMW],
[DELIVERMW],
[NERCCode],
[NERCCodeStatus],
[SDHSDHID],
[ScheduleDeliveryHourHistoryID]
FROM [vwSDHOriginalMW]
WHERE ([DT] >= '2/17/2013') AND ([DT] < '2/21/2013')
EDIT 2
The view in the database actually had one more column than my entity model had properties.
I added the property to the model.
public class vwSDHOriginalMW : IEntityObject, IPMContext
{
public int Schedule { get; set; }
public DateTime DT { get; set; }
public int HE { get; set; }
public Decimal OrgMW { get; set; }
public Decimal DELIVERMW { get; set; }
public string NERCCode { get; set; }
public string NERCCodeStatus { get; set; }
public int SDHSDHID { get; set; }
//missing property
public int ScheduleDeliveryHourHistoryID { get; set; }
}
After adding the property yesterday, it sped up tremendously for a while, ran in 4 seconds instead of 15. But today it's slow again, and nothing has changed.
UPDATE:
I have narrowed it down a little further. There are two methods that I can use that end up using the same FetchSet. The one that I am using returns an IQueryable instead of an IEnumerable. This seems normal, and since I am filtering afterward, most desirable. However the method that returns IQueryable takes 15 seconds while the IEnumerable takes less than a second. (I am calling ToList() on both) FetchAll turns out just to be a wrapper that calls Fetch and returns IEnumerable instead of IQueryable
public IQueryable<TEntity> Fetch<TEntity>() where TEntity : class, Common.IEntityObject
{
return privateContext.Fetch<TEntity>();
}
public IEnumerable<TEntity> FetchAll<TEntity>() where TEntity : class, Common.IEntityObject
{
return privateContext.FetchAll<TEntity>();
}
If I change
IEnumerable<vwSDHOriginalMW> results =
PMCtx.Fetch<vwSDHOriginalMW>()
.Where(x => x.DT >= StartDate && x.DT < EndDate)
.ToList();
to
IEnumerable<vwSDHOriginalMW> results =
PMCtx.Fetch<vwSDHOriginalMW>()
.ToList()
.Where(x => x.DT >= StartDate && x.DT < EndDate);
it is fast. But this isn't acceptable, because it seems like I would want my where clause to be passed to the database. In this case on a dev environment the view is only 180 rows, but it has potential to be millions, so I definitely don't want to return all my results into memory before I filter them.
After much digging and many headaches, I figured out that the view was referencing a view on a different database instance that referenced a table that was missing a non-clustered index. This caused the execution plan to get cached incorrectly. After adding the index on the other database:
USE [OTHERDATABASE]
GO
CREATE NONCLUSTERED INDEX [IX_ScheduleEnergyProfileJoin]
ON [dbo].[WTXS_ScheduleEnergyProfile] ([SEQSDR])
INCLUDE ([SEQSEPI],[StartDate],[EndDate])
GO
Then clearing the execution plan cache on the database with the view I'm using:
USE [MYDATABASE]
DBCC FREEPROCCACHE
DBCC DROPCLEANBUFFERS
The query is running quickly. So it turns out that the SQL that EF said it was using was probably not the sql that was getting sent to the database. Moral of the story is I should have gone through whatever hoops to get profiling permissions on this database instead of relying on the following to output the SQL that would actually be sent.
var sql = ((System.Data.Entity.Infrastructure.DbQuery<vwSDHOriginalMW>)results).ToString();
Is it possible in EF Code-First to update without querying the entire row in db by using stub objects,...
e.g.
public class Dinner
{
public int DinnerID { get; set; }
public string Title { get; set; }
public DateTime EventDate { get; set; }
public string Address { get; set; }
public string Country { get; set; }
public string HostedBy { get; set; }
}
Dinner dinner = dinner.DinnerID = 1;
dinner.HostedBy = "NameChanged"
nerdDinners.SaveChanges();
will the code above create an Update Statement which will make the following columns null for the row of DinnerID 1 ?
Title, EventDate, Address, Country
Is there a way or method like "PropertyModified" = true, then the rest make them = false, so that HostedBy is the only one that will be updated?
I think you are looking for ApplyCurrentValues
public void UpdateDinner(Dinner existingDinner)
{
var stub = new Dinner { DinnerId = existingDinner.DinnerId };
ctx.Dinners.Attach(stub);
ctx.ApplyCurrentValues(existingDinner);
ctx.SaveChanges();
}
ApplyCurrentValues copies the scalar values from the existing object to the object in the graph (in the above case - the stub entity).
From the Remarks section on MSDN:
Any values that differ from the original values of the object are marked as modified.
Is that what your after?
To build on Paul's answer, the following will work when you are using EF Model or Database First:
context.ObjectStateManager.GetObjectStateEntry(dinner).SetModifiedProperty("HostedBy");
I think you are looking for the Attach() method.
Attaching and Detaching Objects
Try this maybe, it is specific to EF Code First which seems to do it differently than just EF.
var dinner = context.Dinners.Find(1);
context.Entry(dinner).Property(d => d.HostedBy).IsModified = true;
context.SaveChanges();
From ADO.NET team blog
"Marking a property as modified forces an update to be send to the database for the property when SaveChanges is called even if the current value of the property is the same as its original value."