Entity Framework lazy loading incorrect entity - c#

Consider the following (very simplified) entities:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
}
public class Answer
{
public int Id { get;set; }
public virtual User User { get; set; }
public string Text { get;set }
}
public class TeamMember
{
public int Id { get;set; }
public virtual User User { get; set; }
public string Role { get; set; }
}
In my mapper I can set the User fine, but as soon as the following code is executed (before any changes are persisted in the DB)
if (teamMembers.Select(x => x.User).Contains(currentUser))
where teamMembers is a list of TeamMembers and currentUser is a User entity loaded from the Db, then the User property of Answer is set to the previous value from the database.
My understanding is that since I haven't accessed the User property of Answer before it hasn't been loaded from the database yet, and this is what happening (it's been lazy loaded?).
I could fix it by reading the User before even setting it in the mapper but what I cannot understand is why when I access the User property of TeamMember the User property of Answer is loaded and set? Is this expected behavior since both entities are associated with the same User (i.e. in the database they have the same User_Id as a foreign key) and when loading it for TeamMembers, EF tries to be clever and populate other entities that references it and haven't been loaded yet?

The entities aren't stored/cached by the elements that have references to them but are stored with their own respective collections.
Once you loaded that User through the teamMembers reference, it was loaded... period. When you went to reference it from another element/object, it would be silly for it to go and load it again when it already has that object in memory.
This is all by design and it makes sense... if, for example, you wanted to save all these objects at the same time, it would first create the user, get an identity/key for it, then save the other objects that reference it with that key that was generated.
ref: https://msdn.microsoft.com/en-us/data/hh949853.aspx#3
"...the ObjectContext will check if an entity with the same key has
already been loaded into its ObjectStateManager. If an entity with the
same keys is already present EF will include it in the results of the
query. Although EF will still issue the query against the database,
this behavior can bypass much of the cost of materializing the entity
multiple times."

Related

How to change the state of an entity if the same entity is contained in different collections of an entity

I'm trying to insert a new entity called Document:
public class Document
{
public int Id { get; set; }
public virtual ICollection<User> UsersOne { get; set; }
public virtual ICollection<User> UsersTwo { get; set; }
}
The entity Document has two relationships many-to-many with the entity User:
public class User
{
public int Id { get; set; }
[Required]
public string Email { get; set; }
[Required]
public string Password { get; set; }
}
In my request, i send the following Json:
{
"UsersOne": [{"Id": 1},{"Id": 2},{"Id": 3}],
"UsersTwo": [{"Id": 1},{"Id": 2},{"Id": 3}],
"Id": 0
}
The problem is that i alredy have the users in the context, and when i add the entity document in the context _context.Document.Add(entity);, the entity insert the users in the context with the added state.
Maybe one solution is to change the state for unchanged:
foreach (var user in entity.UsersOne)
_context.Entry(user).State = EntityState.Unchanged;
foreach (var user in entity.UsersTwo)
_context.Entry(user).State = EntityState.Unchanged;
But when i do this, i have the following error
Saving or accepting changes failed because more than one entity of type 'Entity.User' have the same primary key value. Ensure that explicitly set primary key values are unique.
I also tried to join the two collections into one, and change their states, but the context still have users with added state.
I also tried change users state to detached, before add the document to the context, but on _context.SaveChangesAsync();, a exception of type 'System.Data.Entity.Validation.DbEntityValidationException' is thrown, because the properties email and password are required to User.
You have two options:
If the User entity is being created and added to two different lists, you have to create and save the User entity by itself first, then go back and add that newly created User to the two lists.
When you deserialize the JSON, you have to create a single User entity and add the same instance to both collections. Otherwise, EF will think they are two different User entities with the same PK and throw that exception. If you add the same instance to both, EF should recognize it is the same User and save correctly.
Finally, if you are saving this User entity with only an Id, you will get the required errors on Email and Password because you have the [Required] annotation on there.

EntityFramework 6 DatabaseFirst disable lazy loading

How disable all lazy loading
After generation model from database first I have
public partial class company
{
public int id { get; set; }
public string name { get; set; }
public virtual ICollection<user> user { get; set; }
}
public partial class user
{
public int id { get; set; }
public int company_id { get; set; }
public virtual company company { get; set; }
}
I want load only user and their company
db = new Entities();
db.Configuration.ProxyCreationEnabled = false;
db.Configuration.LazyLoadingEnabled = false;
var result = db.user.Include(x => x.company).ToList();
I don`t want load result[0].company.user
Now collection filled with all users of company
result[0].company.user must be null
if i want load result[0].company.user I want use .Include(x => x.company.user)
This is not lazy-loading but a different concept called relationship fix-up. In short it just keeps navigation properties in sync with each other. In your case you have user.company navigation property and company.user collection navigation property. You load both user and company and they are attached to the context. Now at certain points (list of such points you can find here) EF performs relationship fix-up. In this case it happens after query is performed against DbSet. EF ensures that if user.company is set - company.user collection should contain that user also, because those are related navigation properties. This user is already attached to the context (you originally loaded it with your query) so no additional queries are made to database, so it's not lazy loading.
With lazy loading, if you have 100 users for company A then companyA.user will contain 100 entries (loaded from database when you access this property). In your case, even if company A has 100 users - companyA.user will contain just 1 user - that one which you originally loaded.
This behaviour is usually fine, though in some cases it might cause troubles - most often this happens when you want to serialize your EF object and step into circular references because of that.
There is no way to disable this behavior that I'm aware of.

Mapping One-to-Zero-or-One with EF7

I am currently in the process of cleaning up a fairly large database. Part of the database has a relationship which is a one-to-zero-or-one mapping. Specifically:
User -> UserSettings
Not all users will have user settings, but a user setting cannot exist without the user. Unfortunately, the tables already exist. User has an PK ID. UserSettings has a PK ID and a column, User_Id_Fk which, at this point in time, is not a true FK (there is no relationship defined).
I'm in the process of fixing that and have done so from the DB perspective through SQL and have confirmed with tests. (Added the FK constraint. Added a unique constraint on User_Id_Fk.) This was all done on the UserSettings table. (Note: I am not using EF Migrations here. I have to manually write the SQL at this point in time.)
However, I now need to wire up an existing application to properly handle this new mapping. The application is using ASP.NET Core 1.0 and EF7. Here are (shortened) versions of the existing data models.
public class User
{
public int Id { get; set; }
public virtual UserSettings UserSettings { get; set; }
}
public class UserSettings
{
public int Id { get; set; }
[Column("User_Id_Fk")]
public int UserId { get; set; }
[ForeignKey("UserId")]
public virtual User User { get; set; }
}
I have this Fluent Mapping as well:
builder.Entity<UserSettings>()
.HasOne(us => us.User)
.WithOne(u => u.User)
.IsRequired(false);
When I go to run the application and access these items in the database, I get this error followed with a cryptic set of messages that has no information relating directly back to my application.:
ArgumentNullException: Value cannot be null.
Parameter name: navigation
Microsoft.Data.Entity.Utilities.Check.NotNull[T] (Microsoft.Data.Entity.Utilities.T value, System.String parameterName) <0x10d28a650 + 0x00081> in <filename unknown>, line 0
After doing research, someone had mentioned that the ID of the UserSettings class must be the same as the foreign key, like so:
public class UserSettings
{
[Key, ForeignKey("User")]
public int Id { get; set; }
public virtual User User { get; set; }
}
I don't really have this as an option as the DB is being used for other applications I have no control over at this point. So, am I stuck here? Will I just have to maintain a 1:many mapping (which could happen now, though it hasn't) and not have proper constraints for a 1:0..1 mapping?
Update
Looking at octavioccl's answer below, I tried it out without any success. However, I then removed User from the mapping in UserSettings (but I left UserId). Everything appeared to work as far as I can tell. I'm really confused what is going on here, however, and if this is even the right answer, or if I'm just getting lucky.
Remove the data annotations and try with these configurations:
builder.Entity<UserSettings>()
.Property(b => b.UserId)
.HasColumnName("User_Id_Fk");
builder.Entity<User>()
.HasOne(us => us.UserSettings)
.WithOne(u => u.User)
.HasForeignKey<UserSettings>(b => b.UserId);
From EF Core documentation:
When configuring the foreign key you need to specify the dependent
entity type - notice the generic parameter provided to HasForeignKey
in the listing above. In a one-to-many relationship it is clear that
the entity with the reference navigation is the dependent and the one
with the collection is the principal. But this is not so in a
one-to-one relationship - hence the need to explicitly define it.
The example that is presented in the quoted link (Blog-BlogImage) is pretty much the same of what are you trying to achieve.
If the solution that I show above doesn't work, then you should check if User_Id_Fk column allows null. If that is the case, change the FK property type to int?:
public class UserSettings
{
public int Id { get; set; }
public int? UserId { get; set; }
public virtual User User { get; set; }
}

In EF6 Code First can I change a navigation property value, then break the relationship, and have both changes saved in one transaction?

I'm using Entity Framework 6 Code First to model a user/session relationship:
A user can have many sessions
Each session belongs to a single user
A user may also have a current session
My entities look like this (I've removed everything but those properties relating to my problem):
class User
{
public int Id { get; set; }
[ForeignKey("CurrentSession")]
public int? CurrentSessionId { get; set; }
public virtual Session CurrentSession { get; set; }
[InverseProperty("User")]
public virtual ICollection<Session> Sessions { get; set; }
}
class Session
{
public int Id { get; set; }
public int UserId { get; set; }
public virtual User User { get; set; }
public string X { get; set; }
}
This creates the appropriate foreign key relationships in the database (optional FK from User to Session on CurrentSessionId column; required FK from Session to User on UserId column) so I'm happy with that.
What I'm trying to do is load a user and its current session, change a property value on the CurrentSession (X in this example), remove the relationship between the user and the session (think: "user's session is no longer current"), then save both changes as a single transaction (ie. a single call to SaveChanges).
Unfortunately, whichever way I try to modify the entity properties it always results in the User entity change being saved (SQL UPDATE on User table) but not the Session entity change. I could really do with this pair of updates being atomic. Is there any way of doing that or must I be forced to perform two separate SaveChanges calls?
Thus far I've tried this:
user.CurrentSession.X = ...;
user.CurrentSession = null;
dbContext.SaveChanges();
and this:
user.Sessions.Add(user.CurrentSession);
user.CurrentSession.X = ...;
user.CurrentSession = null;
dbContext.SaveChanges();
and this:
Session currentSession = user.CurrentSession;
user.CurrentSession = null;
currentSession.X = ...;
dbContext.Sessions.Attach(currentSession);
dbContext.Entry(currentSession).State = EntityState.Modified;
dbContext.SaveChanges();
But as soon as I set CurrentSession to null, EF effectively 'loses' the changes I make to the session entity.
It's okay, it does seem to work after all, even the simplest first code example.
The thing that threw me was that I have a basic DbCommandInterceptor class running that is just echoing the SQL statements to the debug console, and this only shows a single SQL UPDATE command despite two different tables being updated. I'm not sure why that's happening. Here's the code for the interceptor:
class DummyInterceptor : DbCommandInterceptor
{
public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
base.ReaderExecuting(command, interceptionContext);
Debug.WriteLine(new string('=', 70));
Debug.WriteLine(command.CommandText);
}
}
Thinking about it now, maybe it's because I'm only overriding the ReaderExecuting method? Need to check that out later.
UPDATE
Yes, that was what it was. I overrided (overrode?) the ScalarExecuting and NonQueryExecuting methods and then I saw the other SQL UPDATE query. The reason the two were in different 'groups' was because the User table has a rowversion column on it, and EF was getting the new row version after performing the FK update.

How to only get one level deep with EntityFramework 5 on navigation properties?

Right now I have proxy creation disabled:
context.Configuration.ProxyCreationEnabled = false;
I have a data model like so (removed non-relevant fields):
public partial class Video
{
public int VideoID { get; set; }
public string Title { get; set; }
public int UserID { get; set; }
public virtual User User { get; set; }
}
public partial class User
{
public User()
{
this.Videos = new HashSet<Video>();
}
public int UserID { get; set; }
public string Username { get; set; }
public virtual ICollection<Video> Videos { get; set; }
}
I am using Unit of Work and Repository patterns to load my data like so,
Get all video's, including the user object:
var videos = videoService
.Include(v => v.User)
.Get()
I am using automapper to map from data model to domain model (hence the UI namespace in the screenshot below). When I inspect the video enumeration I get back, and look at the first item in the enumeration, I go to check the user object:
What I expect here is the VideoModel to be filled with data(ok), with only it's single UserModel entity to be filled with data(ok), and all collections in the UserModel to be empty(this is broke). As you can see in the second red box above, the Videos collection is populated with 6 videos. And on those video's, the user's are filled in. So this basically creates a very large object graph.
1) Can I make it so when using an include that it ONLY goes 1 level deep (IE doesn't fill in Video.User.Videos)?
2) Why doesn't ProxyCreationEnabled = false take care of this? Am I expecting too much?
p.s. I want to avoid creating a customer mapper for this with automapper.
p.p.s. I am doing db first, not model first
By default, EntityFramework uses lazy loading for virtual properties (such as User and Videos in your example). If you want these properties to be filled prior to them actually being accessed, you can use Include() or, to go another level deep, an Include() with a nested Select().
This default behavior, however, relies on the creation of a proxy class, which you have apparently turned off.
Not knowing all the things you're trying to do, this may not work, but it seems like you would get the behavior you wanted by simply removing ProxyCreationEnabled = false and using Include() as you have.
Also, viewing properties in the debugger may be misleading because you are in fact accessing the property when you try to view it in the debugger (which could cause the lazy loaded entity or collection to be filled right then, making you think it had been eagerly loaded).

Categories

Resources