Given the following code, how does EF/DbContext knows about the change made to the customer object:
class Program
{
static void Main()
{
using(var shopContext = new ShopContext())
{
var customer = shopContext.Customers.Find(7);
customer.City = "Marion";
customer.State = "Indiana";
shopContext.SaveChanges();
}
}
}
public class ShopContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
}
public class Customer
{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string City { get; set; }
public string State { get; set; }
}
Thank you
When you load the entity from the context it keeps an additional data structure - let's call it entry. The entry contains two set of values - original values and current values. When you execute the SaveChanges operation EF goes through your customer entities and updates current values in the entry so that they match with the real state of your entity - this operation is called detecting changes. During SQL command generation EF will compare current and original values and build an SQL update statement to modify changed values in the database. This operation is called snapshot change tracking - EF keeps a snap shot in the entry.
There is an alternative called dynamic change tracking which will modify the current value in the entry at the same time you assign the value to your entity's property. Dynamic change tracking has specific requirements (like all of your properties in the entity must be virtual) because it must wrap your class to a dynamic proxy at runtime. This used to be the preferred way but due to some performance issues in complex scenarios, snapshot change tracking is currently supposed to be used as default.
Related
I'm learning to use EFCore with DDD and Clean Architecture and while it is nice to that I don't have separate data models I am currently experiencing that properties added to the models result in automatically being added to the migrations.
I know I can ignore them by using attributes, but I would like to avoid having an entity framework library being referenced in the library with the models. So I would have to explicitly remove them in the DbContext or IEntityTypeConfiguration<TType>.
But I was wondering if it is possible to disable the default behavior completely and that I have to define everything in the DbContext or IEntityTypeConfiguration<TType>so I can safely create features so I won't pollute my database and my migrations with properties that should not be stored.
I would rather have that behavior turned off and be more explicit than having magically decided for me. (Been bitten with uncontrolled behavior like that before)
Example:
Lets say I have a class Sample which is set in the DbContext in a DbSet<Sample> Samples { get; set; }:
public class Sample
{
public Guid Id { get; set; } = new Guid();
public long Value { get; set; }
}
Now when I add the following two things they get automatically added in migrations
public class Sample
{
public Guid Id { get; set; } = new Guid();
public long Value { get; set; }
public string Remark { get; set; }
// Assume this is lazy computed for business logic in domain and application layer
public long LazyComputed { get { return Value * 100 ; } set { Value = Value * 100; } }
// Set of foos but should not be stored in data yet due to privacy for example
public IEnumerable<Foo> LogicFoos { get; set; } = new List<Foo>();
}
public class Foo
{
public Guid Id { get; set; } = new Guid();
}
Now when I add a migration it adds the Remark like I want to, but it sadly also adds the LazyComputed and a new relation with a new table Foos.
When it is a just one, or when I have a few, it is easy to manage fine. But when I have many and change several due a feature change there can be a lot of automatically adding/removing/changing, it seems easy to accidentally add fields to the migrations and repository when don't want.
Because of this I would like to be the default behavior to include to be reversed.
I am making an app using the ASP.Net Boilerplate framework and in my Domain layer I have a simple "Boss" entity. Creating and retrieving these entities from the database works fine but I can't get the "Update" to work. When map my "UpdateBossDto" to a Boss object and try to update it I get this error:
$exception {System.InvalidOperationException: The instance of entity
type 'Boss' cannot be tracked because another instance with the same
key value for {'Id'} is already being tracked. When attaching existing
entities, ensure that only one entity instance with a given key value
is attached. Consider using
'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the
conflicting key values.
This error gets thrown in the BossManager class (I have removed the other methods for readability.
public class BossManager : DomainService, IBossManager
{
private readonly IRepository<Boss> _repositoryBoss;
public BossManager(IRepository<Boss> repositoryBoss)
{
_repositoryBoss = repositoryBoss;
}
public void Update(Boss entity)
{
_repositoryBoss.UpdateAsync(entity);
}
}
Here is my Update method in the BossAppService (i know getting the Id this way probably isn't great but right now I'm just desperate):
public void Update(UpdateBossDto updatedBoss)
{
var boss = new Boss();
updatedBoss.Id = _bossManager.GetBossIdByName(updatedBoss.Name);
boss = ObjectMapper.Map<Boss>(updatedBoss);
_bossManager.Update(boss);
}
And my UpdateDto class which holds the same attributes as the Boss class itself:
public class UpdateBossDto
{
public int Id { get; set; }
public string Name { get; set; }
public int Hp { get; set; }
public int CombatLvl { get; set; }
public int MaxHit { get; set; }
public string AttackStyle { get; set; }
public string Weakness { get; set; }
public string ImageUrl { get; set; }
}
How can I update the Boss object either with or without the Id? Any help would be greatly appreciated!
There's a number of issues here. First, the id should be coming from the request URL, since it uniquely identifies the resource that's being modified. This also saves you from having to do silly things like GetBossIdByName. Not only does that require an unnecessary query, but it's prone to error. The id is your key for a reason: it's unique. Names are not. You could have multiple bosses with the same name. Additionally, your name columns are likely not indexed, which means such a query is vastly more inefficient. Then, with your id, you should be querying the corresponding Boss out of your database, and mapping onto this instance, not creating a new instance. Finally, save that same instance back to the database. Then, you will have no issues.
I have a problem trying to correctly configure a relationship in EF. I have used EF code first to generate classes from an existing database. The first table holds a list of instructions, the second holds a record of the state that each instruction is in.
Tables (simplified):
Instruction
-----------
InstructionID
CurrentInstructionStateHistoryID
InstructionStateHistory
-----------------------
InstructionStateHistoryID
InstructionID
State
So you can see that there are two relationships between the tables - a 1-many relationship based on InstructionID, which I'm not interested in, and have therefore deleted the properties for. The second relationship is based on the CurrentInstructionStateHistoryID property, which points to the "current" state of the instruction.
The classes are as follows:
public partial class Instruction
{
[Key]
public int InstructionID { get; set; }
public int? CurrentInstructionStateHistoryID { get; set; }
public virtual CurrentInstructionStateHistory InstructionStateHistory { get; set; }
}
public partial class InstructionStateHistory
{
[Key]
public int InstructionStateHistoryID { get; set; }
public int InstructionID { get; set; }
public string State { get; set; }
public virtual Instruction tblInstruction { get; set; }
}
Here's the fluent API setup to define the relationship:
modelBuilder.Entity<InstructionStateHistory>()
.HasRequired(e => e.tblInstruction)
.WithOptional(e => e.CurrentInstructionStateHistory);
So, it all compiles and runs. But when I get to a bit of code like this:
Instruction instruction = await _dal.InstructionRepository.Find(claimID);
InstructionStateHistory history = i.CurrentInstructionStateHistory;
I can see that the instruction is populated correctly, let's say the Id is 1234. When I examine the InstructionStateHistory object, what I want to see is that it's InstructionID is 1234, but instead what I see is that it's InstructionStateHistoryID, i.e. is's primary key, is 1234 and that it's related to a completely different instruction.
Somehow I need to tell EF that Instruction.CurrentInstructionStateHistoryID links to InstructionStateHistory.InstructionStateHistoryID.
I've tried many combinations of data annotations and fluent setup but have been unable to find a combination that actually works, either I get the above result or a runtime error. Any help gratefully accepted!
It seems like EF just can't handle this case, so the solution was to forget the concept of a "current" InstructionStateHistory. Instead I added a date field to the InstructionStateHistory table, and then changed the Instruction class to have a regular collection property as follows:
public virtual ICollection<InstructionStateHistory> InstructionStateHistories{ get; set; }
Then when I need the "current" state I just query the colection, sort by date and take the latest one.
I am using EF5 with SQL Server 2012 in a web application. I have two classes:
public partial class Topic {
public Topic()
{
this.SubTopics = new List<SubTopic>();
}
public int TopicId { get; set; }
public string Name { get; set; }
public int Number { get; set; }
public virtual ICollection<SubTopic> SubTopics { get; set; }
}
public partial class SubTopic
{
public int SubTopicId { get; set; }
public int TopicId { get; set; }
public string Name { get; set; }
public string Notes { get; set; }
public virtual byte[] Version { get; set; }
public virtual Topic Topic { get; set; }
}
In our front-end we make a change to SubTopic Notes and then when a Save button is pressed the Topic object together with its SubTopic Objects are sent back as JSON to the Web API Controller. When we check what is being sent back we see the new data for the Notes. When we check the parameter topic we also see the new Notes data.
public HttpResponseMessage PutTopic(int id, Topic topic)
{
_uow.Topics.Update(topic);
_uow.Commit();
}
However checking with SQL Profiler we cannot see anything happening to change the Sub Topic. When data is retrieved the old SubTopic data is returned and the edit to notes is lost.
For the case of a Web update like this. How does EF determine what has changed and is there some way we can make it check / compare what's there with the new object so that it can detect a change and also update the Subtopic ?
Configuration:
DbContext.Configuration.ProxyCreationEnabled = false;
DbContext.Configuration.LazyLoadingEnabled = false;
DbContext.Configuration.ValidateOnSaveEnabled = false;
DbContext.Configuration.ValidateOnSaveEnabled = true;
DbContext.Configuration.AutoDetectChangesEnabled = false;
Update Method:
public virtual void Update(T entity)
{
DbEntityEntry dbEntityEntry = DbContext.Entry(entity);
if (dbEntityEntry.State == EntityState.Detached)
{
DbSet.Attach(entity);
}
dbEntityEntry.State = EntityState.Modified;
}
Since ef4.x? EF has a detect changes capability. And it can be made to work for you in an offline object mode.
I assume your UoW handling isn't doing what you would like because of how objects are placed in the context and how the state is managed.
Your main object is attaching an object and then setting it as state changed. But what about all the sub objects? Did they get loaded into the context? Does autodetect changes have something to check?
If it is not in the context, then EF cant see the change.
it is important to know when and how EF tracks changes
tracking changes in Poco Objects
Check the auto detect settings.
this.Configuration.AutoDetectChangesEnabled = false; ////<<<<<<<<< Default true
A pattern that works with EF is:
var mypoco = Context.Set<TPoco>.Find(1); // find and LOAD your poco or sub object poco
// repeat for graph or use include etc...
// now in context and changes can be tracked. (see link)
// map json fields to poco entity....
myPoco.propertyXyz = json.ValuesSent; // some mapping approach to move your values, I use this package
// <package id="ValueInjecter" version="2.3.3" targetFramework="net45" />
// now tell EF things might have changed.
// normally not required by default, But incase your are not using tracking proxies , tell ef check for changes
// Context.Context.ChangeTracker.DetectChanges(); // uncomment when needed
Context.SaveChanged(); // will trigger detect changes in normal scenarios
EF checks those entities loaded for any changes, it has an original state when first attached.
So it can now see which properties have changed.
It will only update those changed entities and it will Only set the changed properties.
It seems that the problem is similar to a questions I answered yesterday. Please take a look at my answer
I have two classes:
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
public class User
{
public int Id { get; set; }
public string Email { get; set; }
public virtual ICollection<Company> Companies { get; set; }
}
In my MVC application controller get new Company from post. I want to add current user to created Company in something like this.
User user = GetCurrentLoggedUser();
//company.Users = new ICollection<User>(); // Users is null :/
company.Users.Add(user); // NullReferenceException
companyRepository.InsertOrUpdate(company);
companyRepository.Save();
How it should look like to work properly? I don't know it yet but after adding user to collection I expect problems with saving it to database. Any tips on how it should look like would be appreciated.
Use this approach:
public class Company
{
public int Id { get; set; }
public string Name { get; set;}
private ICollection<User> _users;
public ICollection<User> Users
{
get
{
return _users ?? (_users = new HashSet<User>());
}
set
{
_users = value;
}
}
}
HashSet is better then other collections if you also override Equals and GetHashCode in your entities. It will handle duplicities for you. Also lazy collection initialization is better. I don't remember it exactly, but I think I had some problems in one of my first EF test applications when I initialized the collection in the constructor and also used dynamic proxies for lazy loading and change tracking.
There are two types of entities: detached and attached. An attached entity is already tracked by the context. You usually get the attached entity from linq-to-entities query or by calling Create on DbSet. A detached entity is not tracked by context but once you call Attach or Add on the set to attach this entity all related entities will be attached / added as well. The only problem you have to deal with when working with detached entities is if related entity already exists in database and you only want to create new relation.
The main rule which you must understand is difference between Add and Attach method:
Add will attach all detached entities in graph as Added => all related entities will be inserted as new ones.
Attach will attach all detached entities in graph as Unchanged => you must manually say what has been modified.
You can manually set state of any attached entity by using:
context.Entry<TEntity>(entity).State = EntityState....;
When working with detached many-to-many you usually must use these techniques to build only relations instead of inserting duplicit entities to database.
By my own experience working with detached entity graphs is very hard especially after deleting relations and because of that I always load entity graphs from database and manually merge changes into attached graphs wich are able to fully track all changes for me.
Be aware that you can't mix entities from different contexts. If you want to attach entity from one context to another you must first explicitly detach entity from the first one. I hope you can do it by setting its state to Detached in the first context.
In your constructor for the Company entity you can create an empty collection on the Users property.
public class Company
{
public Company() {
Users = new Collection<User>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<User> Users { get; set; }
}
As far as saving to the database is concerned, I asked a related question a few days ago and was assured that Entity Framework is able to track the changes made to related entities. Read up on that here:
Are child entities automatically tracked when added to a parent?