Entity Framework Setting Navigation Property by Id Issue - c#

I have two classes: Owner and Dog. In the Dog class there is a Navigation Property defined like this:
public class Dog {
//Other properties
public int OwnerId { get; set; }
[ForeignKey("OwnerId")]
public virtual Owner Owner{ get; set; }
}
I also have the following (example) method
public void SetOwnerOfFirstDog(int ownerId)
{
//var owner = context.Owners.First(e => e.Id == ownerId);
var dog = context.Dogs.First();
dog.OwnerId = ownerId;
context.SaveChanges();
}
This all works but when I later use the same db context and look up the same dog entity it's Owner property is null. (When I use another db context it is not null.)
However, if I uncomment the first line in the SetOwnerOfFirstDog method the Owner property will be correctly set. But of course this is an extra query to the database and I'd like to avoid it.
So my question:
How can I make sure that the Owner property is correctly filled in when I look it up after I have set the Dog's Owner by Id.
I am using lazy loading.

You can use Include to load related entities
public void SetOwnerOfFirstDog(int ownerId)
{
var dog = context.Dogs.Include(x => x.Owner).First();
dog.OwnerId = ownerId;
context.SaveChanges();
}

Related

Is there a way to fill navigation properties without saving an entity to the database?

Let's say I have an entity PersonEntity with a property EyeColorId and navigation property of type EyeColorEntity. Is there a way to prefetch the EyeColorEntity without saving the entity and without just querying for the EyeColor.
Ex.
public class PersonEntity
{
public int Id { get; set; }
public int EyeColorId { get; set; }
public EyeColorEntity EyeColor { get; set; }
}
public void FillFromDbExample(DbContext context)
{
var personEntity = new PersonEntity()
{
EyeColorId = 5
};
context.SetNavigationProperties(personEntity);
}
Theoretically this would fill the navigation property on the entity. The result would look something like this.
personEntity =
{
EyeColorId = 5,
EyeColor =
{
Id = 5,
Color = "Blue"
}
}
The trick here is that I don't want to have to individually query for each property and I don't want to have to save the entity to the database to pull these properties back. Does something like the described functionality exist in EnityFramework 6.2.0.
You can pre-load all properties in the context to make relationship fixup work. For example:
context.EyeColors.Load();
var personEntity = new PersonEntity()
{
EyeColorId = 5
};
context.Persons.Attach(personEntity);
In the last statement EF automatically populated personEntity.EyeColor.
Alternatively, you can rely on lazy loading by initializing the entity as a lazy-loading proxy. The property must be virtual to enable proxy creation:
public virtual EyeColorEntity EyeColor { get; set; }
Then:
var personEntity = context.Persons.Create(); // Creates a proxy
personEntity.EyeColorId = 5;
context.Persons.Attach(personEntity);
Now EF will query the matching EyeColor from the database when personEntity.EyeColor is accessed (= lazy loading).
I don't want to have to individually query for each property
Lazy loading does query properties individually, but it's not you who has to do it.
Note that in both cases personEntity must be attached to the context.

Entity Framework always includes data that is in context even if I don't ask for it

I am using MVC.NET web api, EF with DB first, and I have lazy loading turned off on my context. EF is returning way too much data, even with LazyLoading turned off.
For example, I have Users with one Role. When I query for Users and Include Role, the Role.Users property is automatically filled with data since Users have been loaded into the context.
Why can't I get EF to give me JUST what I request? Or am I missing something big here?
public partial class User
{
public int UserID { get; set; }
public string Title { get; set; }
public string Email { get; set; }
public int RoleID { get; set; }
....
public virtual Role Role { get; set; }
}
public partial class Role
{
public int RoleID { get; set; }
public string RoleName { get; set; }
....
public virtual ICollection<User> Users { get; set; }
}
return db.Users.Include(u => u.Role);
// ^^ user.Role.Users is filled with 1000s of users
TL;DR - I want EF to never load data into navigation properties/collections unless I .Include() it directly. When serializing to JSON I want just what I ask for explicitly. It seems that even with lazy loading off, navigation properties that are already in the context (ie usually "circular references") will be loaded and returned.
The behaviour your are seeing is called Relationship Fixup and you cannot disable it.
If you are loading users with roles to serialize them and sent them to somewhere I guess that you don't want to track changes of entities in the context they have been loaded in. So, there is no need to attach them to the context and you can use:
return db.Users.Include(u => u.Role).AsNoTracking();
Or use a projection into an object specialized for serialization, as suggested by #STLRick.
You can select only what you need by using Select().
var users = _db.Users.Select(x => new
{
UserID = x.UserID,
Title = x.Title,
Email = x.Email,
RoleID = x.RoleID
}).AsEnumerable();
You are right that with lazy loading on, you will get back navigation properties because they are "touched" by the serializer which causes them to be loaded. Lazy loading should be off if you want the properties to come back as null. That said, it "seems" that once entities are loaded into the context (through other queries, for example), they will be processed by the serializer. So the answer is to tell the serializer not to return the navigation properties. The best way I've been able to find to do this is to use DTOs (Data Transfer Objects). This allows you to return exactly the data you want rather than your actual entities.
Your DTO might look something like this:
public partial class UserDto
{
public UserDto(user User)
{
UserID = user.UserID;
Title = user.Title;
//... and so on
}
public int UserID { get; set; }
public string Title { get; set; }
public string Email { get; set; }
public int RoleID { get; set; }
//exclude the Role navigation property from your DTO
}
...and then you could do something like this:
return db.Users.Include(u => u.Role).Select(user => new UserDto(user));
I don't want it to load anything besides what I tell it to include.
It looks like you need to use Explicit Loading. Basically, you could load the specific entities like this:
context.Include("Roles")
To my best knowledge that should not include related entities. Lazy loading should indeed be disabled and you could load navigational properties explicitly with Load.
First: Turn Lazy Loading on.
Second: If you want to filter down what you retrieve and return, then do a custom return object or something.
from u in db.Users
join r in db.Roles
on u.RoleID equals r.RoleID
select new { u.UserID, u.Title, u.Email, r.RoleName }
Or something like that. You will have a minimal return object and your object graph will be tiny.

Custom key generation in RavenDB

I have sets of entities all of them are derived from abstract class
public abstract class NamedEntity : INamedEntity
{
#region Public Properties
public string Description { get; set; }
public string Id { get; set; }
public string Name { get; set; }
#endregion
}
When I persist all entities I want to use Name field as a key, so I override DocumentKeyGenerator and provide such implementation:
store.Conventions.DocumentKeyGenerator = entity =>
{
var namedEntity = entity as NamedEntity;
if (namedEntity != null)
{
return string.Format("{0}/{1}", store.Conventions.GetTypeTagName(entity.GetType()), namedEntity.Name);
}
return string.Format("{0}/", store.Conventions.GetTypeTagName(entity.GetType()));
};
It works fine when I persist the list of entities for the first time, but if I want to persist them again I get an exception
PUT attempted on document 'xxxxx' using a non current etag
I just started using RavenDB, so I cannot understand what I am doing wrong?
Just a guess, but it's probably not with your key generation, but how you are storing them.
On first usage you probably have something like:
var myEntity = new MyEntity(...);
session.Store(myEntity);
...
session.SaveChanges();
That part is fine, but on subsequent usage, you should not be doing the same thing. Instead, it should be more like this:
var myEntity = session.Load<MyEntity>("myentities/foobar");
myEntity.Something = 123;
...
session.SaveChanges();
Note there is no call to .Store() when making changes. This is because the entity is "tracked" by the session, and all changes to it are automatically persisted when you call .SaveChanges()

Entity Framework - Determining if a Relationship Exists without a navigation property on one end

I have the following two entities (using Code First) in my application:
public class Note
{
public int NoteId { get; set; }
public string Text { get; set; }
}
public class Decision
{
// PK/FK
public int NoteId { get; set; }
// other fields ...
public virtual Note Note { get; set; }
}
I configured my relationship like this:
modelBuilder.Entity<Decision>().HasRequired(d => d.Note).WithOptional();
A Decision must have a note but a Note does not always have a decision. A 1:1 mapping with one side being optional.
I would like a property on my note that lets me know if there is a decision for it. Something like:
public bool HasDecision
{
get
{
// not sure what to do here
}
}
Is there a way to do this without having Decision be a lazy loaded property on Note?
You would need to do an explicite query. There is no such thing like "lazy loading proxies for scalar properties". Lazy loading is only supported for navigation properties. Your entity must have a reference to a context if you want to have HasDecision as a property on the entity. I would prefer to create a repository or service method like so:
public bool HasDecision(Note note)
{
return _context.Decisions.Any(d => d.NoteId == note.NoteId);
}

entity framework 4.1 code first and auto mapper issue

Consider this simple Model and ViewModel scenario:
public class SomeModel
{
public virtual Company company {get; set;}
public string name {get; set;}
public string address {get; set;}
//some other few tens of properties
}
public class SomeViewModel
{
public Company company {get; set;}
public string name {get; set;}
public string address {get; set;}
//some other few tens of properties
}
Problem that occurs is:
I have a edit page where company is not needed so I do not fetch it from database. Now when the form is submitted I do:
SomeModel destinationModel = someContext.SomeModel.Include("Company").Where( i => i.Id == id) // assume id is available from somewhere.
Then I do a
Company oldCompany = destinationModel.company; // save it before mapper assigns it null
Mapper.Map(sourceViewModel,destinationModel);
//After this piece of line my company in destinationModel will be null because sourceViewModel's company is null. Great!!
//so I assign old company to it
destinationModel.company = oldCompany;
context.Entry(destinationModel).State = EntityState.Modified;
context.SaveChanges();
And problem is even when I assign oldCompany to my company it is still null in database after savechanges.
Note:
If I change these lines:
destinationModel.company = oldCompany;
context.Entry(destinationModel).State = EntityState.Modified;
context.SaveChanges();
to these:
context.Entry(destinationModel).State = EntityState.Modified;
destinationModel.company = oldCompany;
context.Entry(destinationModel).State = EntityState.Modified;
context.SaveChanges();
Notice I change the state 2 times, it works fine. What can be the issue? Is this a ef 4.1 bug?
This is a sample console application to address the issue:
using System;
using System.Linq;
using System.Data.Entity;
using System.ComponentModel.DataAnnotations;
using AutoMapper;
namespace Slauma
{
public class SlaumaContext : DbContext
{
public DbSet<Company> Companies { get; set; }
public DbSet<MyModel> MyModels { get; set; }
public SlaumaContext()
{
this.Configuration.AutoDetectChangesEnabled = true;
this.Configuration.LazyLoadingEnabled = true;
}
}
public class MyModel
{
public int Id { get; set; }
public string Foo { get; set; }
[ForeignKey("CompanyId")]
public virtual Company Company { get; set; }
public int? CompanyId { get; set; }
}
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
}
public class MyViewModel
{
public string Foo { get; set; }
public Company Company { get; set; }
public int? CompanyId { get; set; }
}
class Program
{
static void Main(string[] args)
{
Database.SetInitializer<SlaumaContext>(new DropCreateDatabaseIfModelChanges<SlaumaContext>());
SlaumaContext slaumaContext = new SlaumaContext();
Company company = new Company { Name = "Microsoft" };
MyModel myModel = new MyModel { Company = company, Foo = "Foo"};
slaumaContext.Companies.Add(company);
slaumaContext.MyModels.Add(myModel);
slaumaContext.SaveChanges();
Mapper.CreateMap<MyModel, MyViewModel>();
Mapper.CreateMap<MyViewModel, MyModel>();
//fetch the company
MyModel dest = slaumaContext.MyModels.Include("Company").Where( c => c.Id == 1).First(); //hardcoded for demo
Company oldCompany = dest.Company;
//creating a viewmodel
MyViewModel source = new MyViewModel();
source.Company = null;
source.CompanyId = null;
source.Foo = "foo hoo";
Mapper.Map(source, dest); // company null in dest
//uncomment this line then only it will work else it won't is this bug?
//slaumaContext.Entry(dest).State = System.Data.EntityState.Modified;
dest.Company = oldCompany;
slaumaContext.Entry(dest).State = System.Data.EntityState.Modified;
slaumaContext.SaveChanges();
Console.ReadKey();
}
}
}
Automapper always updates every property from the source instance to the destination instance by default. So if you don't want your Company property overwritten then you have to explicitly configure this for your mapper:
Mapper.CreateMap<MyViewModel, MyModel>().ForMember(m => m.Company, c => c.UseDestinationValue());
So far nothing EF related. But if you are using this with EF you have to use your navigation property Company and the CompanyId consistently: you also need to use the destination value for CompanyId during mapping:
Mapper.CreateMap<MyViewModel, MyModel>().ForMember(m => m.CompanyId, c => c.UseDestinationValue());
EDIT: But the problem is not that your Company is null but after resetting it is still null in the DB. And this caused by the fact that if you are having an explicit Id property like 'CompanyId' you have to mantain it. So it is not enough to call destinationModel.company = oldCompany; you also need to call destinationModel.companyId = oldCompany.Id;
And because you retrieved your dest entity from the context it's already doing the change tracking for you therefore there is no need set EntityState.Modified.
EDIT: Your modified sample:
Mapper.CreateMap<MyModel, MyViewModel>();
Mapper.CreateMap<MyViewModel, MyModel>();
//fetch the company
MyModel dest = slaumaContext.MyModels.Include("Company").Where(c => c.Id == 18).First(); //hardcoded for demo
var oldCompany = dest.Company;
//creating a viewmodel
MyViewModel source = new MyViewModel();
source.Company = null;
source.CompanyId = null;
source.Foo = "fdsfdf";
Mapper.Map(source, dest); // company null in dest
dest.Company = oldCompany;
dest.CompanyId = oldCompany.Id;
slaumaContext.SaveChanges();
The second EDIT in #nemesv's answer or the finetuning of AutoMapper is the way to go in my opinion. You should accept his answer. I only add an explanation why your code doesn't work (but your code with setting the state twice does). First of all, the problem has nothing to do with AutoMapper, you will get the same behaviour when you set the properties manually.
The important thing to know is that setting the state ( Entry(dest).State = EntityState.Modified ) does not only set some internal flag in the context but the property setter for State calls actually some complex methods, especially it calls DbContext.ChangeTracker.DetectChanges() (if you don't disable AutoDetectChangesEnabled).
So, what happens in the first case:
// ...
Mapper.Map(source, dest);
dest.Company = oldCompany;
// at this point the state of dest EF knows about is still the state
// when you loaded the entity from the context because you are not working
// with change tracking proxies, so the values are at this point:
// dest.CompanyId = null <- this changed compared to original value
// dest.Company = company <- this did NOT change compared to original value
// The next line will call DetectChanges() internally: EF will compare the
// current property values of dest with the snapshot of the values it had
// when you loaded the entity
slaumaContext.Entry(dest).State = System.Data.EntityState.Modified;
// So what did EF detect:
// dest.Company didn't change, but dest.CompanyId did!
// So, it assumes that you have set the FK property to null and want
// to null out the relationship. As a consequence, EF also sets dest.Company
// to null at this point and later saves null to the DB
What happens in the second case:
// ...
Mapper.Map(source, dest);
// Again in the next line DetectChanges() is called, but now
// dest.Company is null. So EF will detect a change of the navigation property
// compared to the original state
slaumaContext.Entry(dest).State = System.Data.EntityState.Modified;
dest.Company = oldCompany;
// Now DetectChanges() will find that dest.Company has changed again
// compared to the last call of DetectChanges. As a consequence it will
// set dest.CompanyId to the correct value of dest.Company
slaumaContext.Entry(dest).State = System.Data.EntityState.Modified;
// dest.Company and dest.CompanyId will have the old values now
// and SaveChanges() doesn't null out the relationship
So, this is actually normal change tracking behaviour and not a bug in EF.
One thing I find disturbing is that you have a ViewModel which apparently has properties you are not using in the view. If your ViewModel wouldn't have Company and CompanyId all the trouble would disappear. (Or configure at least AutoMapper to not map these properties, as shown by #nemesv.)

Categories

Resources