Retrieving entity from context after Add() yet before SaveChanges() has been called
Let's assume I have a file with multiple rows each representing a Person
, the Person
entity has an identity column
that is also the primary key
. Assuming that a Person
can be repeated in the file, if it is, perhaps I want to do a last entry wins scenario. In the example below I开发者_开发知识库 use a repository to retrieve a person from the database by the social security number. The issue is that when the same SSN shows up again in the file, the repository still returns a null, even though technically that person with that SSN has already been added to the context (SaveChanges
hasn't been called yet). I realize I can work around this by tracking myself which Person
objects have already been added. I am wondering what is the best practice for this scenario.
Thanks.
foreach(row in fileRows)
{
Person person = personRepository.GetBySSN(row.SSN);
if(person == null)
{
//insert logic
}
else
{
//update logic
}
}
personRepository.SaveChanges();
I think I can give the same answer I gave here
To persist an entity you usually add it to it's DbSet
in the context.
For example
var bar = new Bar();
bar.Name = "foo";
var context = new Context();
context.Bars.Add(bar);
Surprisingly, querying context.Bars
, the just added entity cannot be found
var howMany = context.Bars.Count(b => b.Name == "foo");
// howMany == 0
After context.SaveChanges()
the same line will result 1
The DbSet
seems unaware to changes until they're persisted on db.
Fortunately, each DbSet
has a Local
property that acts like the DbSet
itself, but it reflect all in-memory operations
var howMany = context.Bars.Local.Count(b => b.Name == "foo");
// howMany == 1
You can also use Local
to add entities
context.Bars.Local.Add(bar);
and get rid of the weird behavior of Entity Framework.
Modify your GetBySSN
as follows:
public Person GetBySSN(string ssn)
{
Person p = context.ObjectStateManager
.GetObjectStateEntries(~EntityState.Deleted)
.Where(e => !e.IsRelationship)
.Select(e => e.Entity)
.OfType<Person>()
.SingleOrDefault(p => p.SSN = ssn);
if (p == null)
{
p = context.People.SingleOrDefault(p => p.SSN = ssn);
}
return p;
}
If you want to go Ladislav's route and query the ObjectStateManager then you may wish to use an extension method like the following:
public static IEnumerable<TEntity> LoadedEntities<TEntity>(this ObjectContext Context)
{
return Context.ObjectStateManager
.GetObjectStateEntries(EntityState.Added | EntityState.Modified | EntityState.Unchanged)
.Where(e => !e.IsRelationship).Select(e => e.Entity).OfType<TEntity>();
}
This would allow you to then do something like this:
Person p = context.LoadedEntities<Person>().SingleOrDefault(p => p.SSN = ssn);
if (p != null)
return p;
return context.People.SingleOrDefault(p => p.SSN = ssn);
I haven't found anything regarding best practices so I'll post my solution. I know many other posts refer to the fact that the EF context provides mechanisms for looking into it and seeing if a particular entity is in attached state. Being that I work through repositories (my business layer has no direct access to the EF context), my choice is either offloading this sort of logic into the repository, or attempt to solve it in the business layer. My feeling is that this task is really a business concern and not a data access layer concern.
Dictionary<string, Person> newPeople = ...
foreach(row in fileRows)
{
Person person;
//if the person is already tracked by the dictionary, work with it, if not use the repository
if(!newPeople.TryGetValue(row.SSN, out person))
{
person = personRepository.GetBySSN(row.SSN);
}
if(person == null)
{
//insert logic
//keep track that this person is already in line for inserting
newPeople.Add(row.SSN, person);
}
else
{
//update logic
}
}
personRepository.SaveChanges();
精彩评论