This question is a continuation of:
EntityFramework adding new object to a collection
Now I understand that when using DbSet EF won't load the entire collection into memory
But what if I have something like the following code:
public class User
{
public int UserID { get; set; }
public string UserName { get; set; }
public ICollection<Role> Roles { get; set; }
}
public class Role
{
public int RoleID { get; set; }
public string RoleName { get; set; }
public User User { get; set; }
}
public class MyContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Role> Roles { get; set; }
}
public class SomeClass
{
public void AssignRoleToUser(int userID, Role role)
{
var ctx = new MyContext();
var user = ctx.Users.First(x => x.UserID.Equals(userID));
user.Roles.Add(role);
ctx.SaveChanges();
}
}
In this case, I'm not using the DbSet object to add a new object to the Role collection, instead, I'm adding a new role to a specific user using an ICollection collection
So what happens in this case?
Does EntityFramewrk have to load into memory all the user's roles in order to perform the insert?
In provide code above you are not adding new role as your ctx.Users is just used to retrieve data. Somewhat similar issue is addressed in this SE post - Linq To Entities - how to filter on child entities.
I would advice to look at this short and useful article - Entity Framework 4.0 FAQ – Getting Started Guide.
No. EF does not need to know what 'Roles' the User has.
You need to learn what happens in regards to change tracking:
Once the query is run the change tracker receives an object entry for 'user'. The state of this user is 'Unchanged'
You add a new role to the user's Roles collection. This simply adds a change tracker entry for this new role and marks it as 'Added'
On SaveChanges() EF will look at your change tracker and see that the user object has not changed so nothing needs to be done there. There is also an entry for the new role which states that it needs to be added. So an SQL query will be written to insert this role.
Simple as that. You can always debug and add a watch for the state of the change tracker. The entries can be found by calling DbContext.ChangeTracker.Entries().
EF will blindly send off the 'add' to the DB.
Related
In my code, I am pulling objects to essentially build a parent object. However, part of this parent object will contain a property that is a list of the child objects used to build the parent object. See the following:
public class Order
{
public long Id { get; set; }
public long RequestId{ get; set; }
public virtual Request Request { get; set; }
public double? Lbs { get; set; }
public string Name { get; set; }
public virtual ICollection<OrderDetail> OrderDetails{ get; set; }
}
public class OrderDetail
{
public long Id { get; set; }
public string OrderDetailId { get; set; }
public long? RequestId { get; set; }
public virtual Request Request { get; set; }
}
I am building an Order object from a list of OrderDetail objects, and the list of objects are a virtual part of my Order object. However, when I attempt to run SaveChanges() to my Order object, I get primary key errors from trying to insert the collection of OrderDetails that are a property on the Order object. They are already there! How can I stop this?
Ensure that the OrderDetails are associated with the context that the order is being created. The typical cause is the order details are loaded by a different context, perhaps de-serialized from a web request and associated to the new order. The problem is that the context instance saving the orders does not recognize those OrderDetails and treats them as new (added) entities.
Given an example method like this:
public Order CreateOrder(IEnumerable<OrderDetail> orderDetails)
{
using (var context = new AppContext())
{
var order = new Order();
order.OrderDetails.AddRange(orderDetails);
context.Orders.Add(order);
context.SaveChanges();
return order;
}
}
The error occurs because context above does not know about the OrderDetails. Adding the objects to the new order are treated like adding new OrderDetails leading to PK violations or inserting duplicates with new IDs if PKs are generated.
Option 1: When creating a new entity, be sure to load all associated entities
Rather than passing entities for associations, you can save bandwidth by passing IDs:
public Order CreateOrder(IEnumerable<int> orderDetailIds)
{
using (var context = new AppContext())
{
var order = new Order();
var orderDetails = context.OrderDetails.Where(x => orderDetailIds.Contains(x.OrderDetailId)).ToList();
order.OrderDetails.AddRange(orderDetails);
context.Orders.Add(order);
context.SaveChanges();
return order;
}
}
This ensures the order details are known by the context, and associated to the new order rather than treated as new records.
Option 2: Associate entities to the context
public Order CreateOrder(IEnumerable<OrderDetail> orderDetails)
{
using (var context = new AppContext())
{
var order = new Order();
foreach(var orderDetail in orderDetails)
{
context.OrderDetails.Attach(orderDetail);
}
order.OrderDetails.AddRange(orderDetails);
context.Orders.Add(order);
context.SaveChanges();
return order;
}
}
This option associates the order details with the context by attaching them. This can work ok when the DbContext is scoped to the method, and assuming all references being attached are unique (no double-ups). For longer-lived calls you need to be careful if there is a possibility of an entity already having been attached or otherwise loaded by the context where the Attach call can fail. Attaching entities coming from a web client should also not be trusted, so you should ensure that the entity state never gets set to modified or this opens the door for tampered data being persisted with a SaveChanges call.
There are hybrid options that can check the local cache for references and create stubs which I was about to outline but these are really only suited to bulk operations and come with significant risks.
I've created a custom user inheriting from IdentityUser called Contacts, and my applications dbcontext inherits from IdentityDbContext like so:
public class Contact : IdentityUser<int, ContactLogin, ContactRole, ContactClaim>
{
public Contact()
{
}
}
public class dbcontext : IdentityDbContext<Contact, Role, int, ContactLogin, ContactRole, ContactClaim>
{
public dbcontext()
: base("dbcontext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// IdentityDbContext base - must be called prior to changing identity configuration
base.OnModelCreating(modelBuilder);
// custom identity table names and primary key column Id names
modelBuilder.Entity<Contact>().ToTable("Contacts").Property(p => p.Id).HasColumnName("ContactId").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
modelBuilder.Entity<ContactRole>().ToTable("ContactRoles");
modelBuilder.Entity<ContactLogin>().ToTable("ContactLogins");
modelBuilder.Entity<ContactClaim>().ToTable("ContactClaims").Property(p => p.Id).HasColumnName("ContactClaimId");
modelBuilder.Entity<Role>().ToTable("Roles").Property(p => p.Id).HasColumnName("RoleId").HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
}
}
By default IdentityDbContext contains a Users DbSet. Is it possible to change the name of this DbSet to match the type that it's implementing, e.g Contacts?
It's not a big deal, but it would just be nice to refer to the DbSet using dbcontext.Contacts instead of dbcontext.Users.
Thanks.
The base IdentityDbContext uses: public virtual IDbSet<TUser> Users { get; set; } to expose the Users DbSet.
You'll need a similar property for your own implementation, e.g: public IDbSet<Contacts> Contacts { get; set; }
Update
Question was regarding renaming the existing DbSet of Contacts from Users to Contacts.
No, you can't do this out of the box. You could attempt to wrap it and expose it again, but this isn't really the right thing to do. See this question for an in depth discussion.
Just a note that if you decide to overwrite anything or add your own, the default EF implementation of UserStore will use the DbSet named Users. Just something to keep an eye on if you get unexpected behavior.
Generally what I tend to do is have a big separation of concerns right.
So I have:
public IDbSet<User> Users { get; set; }
This represents anyone who wants to log into my system. So now I want to model actual concepts into my database, concepts that relate to real world things. So I have a system administrator for example, I will create an entity for this.
public class SystemAdministrator
{
public string Name { get; set; }
public int LocationId { get; set; } // a complex representation of where this administrator works from
public int UserId { get; set; } // this is now a reference to their log in
}
Now my context will look like this:
public IDbSet<User> Users { get; set; }
public DbSet<SystemAdministrator> SystemAdministrators { get; set; } // I use DbSet because it exposes more methods to use like AddRange.
This means now my database has proper representations of real world concepts which is easy for everyone to develop against. I do the same for Clients or Employees.
This also means that I can move away from primitive obsession
I got an error using ASP.NET Identity in my app.
Multiple object sets per type are not supported. The object sets
'Identity Users' and 'Users' can both contain instances of type
'Recommendation Platform.Models.ApplicationUser'.
I saw a few questions about this error in StackOverflow. All indicate on two DbSet objects of the same type. But in my DbContext there aren't the same types of DbSets. Exception is thrown on FindAsync() method during logging in.
if (ModelState.IsValid)
var user = await UserManager.FindAsync(model.UserName, model.Password);
if (user != null && user.IsConfirmed)
{
The problem is I don't have two DbSets of the same type. My Contexts look like this:
public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
public ApplicationDbContext()
: base("DefaultConnection")
{
}
public System.Data.Entity.DbSet<RecommendationPlatform.Models.ApplicationUser> IdentityUsers { get; set; }
}
and
public class RecContext : DbContext
{
public RecContext()
: base("RecConnection")
{
Database.SetInitializer<RecContext>(new DropCreateDatabaseIfModelChanges<RecContext>());
}
public DbSet<Recommendation> Recommendations { get; set; }
public DbSet<Geolocation> Geolocations { get; set; }
public DbSet<Faq> Faqs { get; set; }
public DbSet<IndexText> IndexTexts { get; set; }
}
What could cause this problem? Maybe something connected with in-built ASP.NET Identity functionalities? Anyway, what is Users type? I don't have it in my app...
You do have two DbSets` of the same type.
IdentityDbContext<T> itself contains Users property declared as:
public DbSet<T> Users { get; set; }
You're declaring second one in your class.
review this file "ApplicationDbContext.cs", remove the line, generated automatically by scaffold last, should be like this:
public System.Data.Entity.DbSet<Manager.Models.ApplicationUser> IdentityUsers { get; set; }
This issue can arise from using scaffolding to create a View. You probably did something like this: View > Add > New Scaffold Item... > MVC 5 View > [Model class: ApplicationUser].
The scaffolding wizard added a new line of code in your ApplicationDbContext class.
public System.Data.Entity.DbSet<RecommendationPlatform.Models.ApplicationUser> IdentityUsers { get; set; }
Now you have two DbSet properties of the same type which not only causes an exeptions to be thrown in the FindAsync() method but also when you try to use code-first migrations.
Be very careful when using scaffolding or even better don't use it.
Comment the new generated Dbset from identity model class like below
// public System.Data.Entity.DbSet<SurveyTool.Models.ApplicationUser> ApplicationUsers { get; set; }
Whenever I see this problem, I always double check the DbSet. - ESPECIALLY if you are using another language for Visual Studio.
For us who use other language on VS, always double check because the program doesn´t create controllers or models with the exact name. perhaps this should be a thread.. or there is one already and I missed it.
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.
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?