Entity Framework Loading Related Entities - c#

I'm using code-first Entity Framework with a basic context that just consists of standard IDbSet collections where T is just a POCO class. On my context, I've disabled lazy loading. While there are "navigation properties" in my model classes, I've removed the virtual keyword from them.
The "Get All" methods in the repository do some custom filtering to ensure that the current user only sees the data that they own unless they are an administrator. I'm having a particular problem where I'm logged in as an administrator that also is associated with some records. Since the entity that I'm logged in as is loaded in the context, even though I have lazy loading disabled, virtual removed and am not using Include or Load, the objects in the results that have an association to my profile have the navigation property set automatically.
This is not code from my project, just an example to show the idea of what I'm doing. It probably has typos and syntax errors.
public class Record
{
public Guid Id { get; set; }
public string Name { get; set; }
public Owner Owner { get; set; } //No virtual keyword
public Guid OwnerId { get; set; }
}
public class Owner
{
public Guid Id { get; set; }
public string Name { get; set; }
public Collection<Record> Records { get; set; } //No virtual keyword
}
public class Context : DbContext
{
IDbSet<Owner> Owners { get; set; }
IDbSet<Record> Records { get; set; }
public static Context Create()
{
Context context = new Context();
context.Configuration.LazyLoadingEnabled = false; //Lazy loading disabled
return context;
}
}
public class Repository
{
private Context Context { get; set; }
public Owner CurrentOwner { get; private set; }
public Repository()
{
Context = Context.Create();
//Code here to get the application user and look up an associated "owner"
//entity if the user is an "owner" (they could just be an administrator)
//but the GetCurrentOwnerOrNull uses the Context to find the user
CurrentOwner = GetCurrentOwnerOrNull();
}
public IQueryable<Record> GetRecords(bool asAdmin)
{
IQueryable<Record> records = Context.Records; //Not including or loading Owner
if (asAdmin)
{
//Verify that the application user is an admin and throw exception otherwise
}
else
{
if (CurrentOwner == null)
{
//Throw a security exception
}
records = records.Where(r => r.OwnerId == CurrentOwner.Id);
}
return records;
}
}
So again, the problem with the above is that if I was to run that code as an Owner, whether administrator or not, then those Records that I Own will have the Owner property set instead of null. I want the entity framework to get out of my business and not automatically set this. It's causing problems downstream, especially when running the code as an administrator and an owner, so you get some records back with Owner = null and some with Owner set. It's annoying. Make it stop for me please.

This bug is actually a feature. Entity Framework will automagically wire-up associations within the same context even if the entities are loaded independent of each other.
Lets assume the following:
public class Person
{
public Person()
{
this.Pets = new List<Pet>();
}
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Pet> Pets { get; set; }
}
public class Pet
{
public int Id { get; set; }
public string Name { get; set; }
public int PersonId { get; set; }
public virtual Person Owner { get; set; }
}
Lets also assume that these are properly associated DB-First or with Attributes/Fluent API code-first.
Database:
Persons
Id Name
1 Erik Philips
Pets
Id Name PersonId
1 Joe 1
Here is what will happen:
var pet = DbContext.Pets.FirstOrDefault(id => id == 1);
var person = DbContext.Persons.FirstOrDefault(id => id == 1);
Assert.AreEqual(person.Pets.Count(), 1);
Assert.IsNotNull(pet.Person);
Assert.AreEqual(pet.Person, person);
The reason this can occurs is because the context will hold onto the objects in it's cache. If you don't care about the context holding onto these objects, you will need to use AsNoTracking().

Related

Entity Framework 6 - Multiple lookup inserts vs An object with the same key already exists in the ObjectStateManager

I am apparently having a real devil of a time understanding Entity Framework 6 which I am using with ASP.NET MVC 5.
The core of the matter is that I have a really quite simple data model that is typical of any real world situation where I have various business objects that have other business objects as properties (and of course they child objects may in turn have other child business objects) and also various types of lookup/type data (Country, State/Province, LanguageType, StatusType etc.) and I cannot figure out how to save/update it properly.
I keep going back and forth between two error states:
1) I either run into the situation where saving a parent business object results in unwanted duplicate values being inserted into my lookup/type tables (for example saving a business object that has been assigned an existing LanguageType of 'English' will result in another LanguageType for 'English' being inserted into the LanguageType table), or
2) I use some of the suggestions I've seen here and elsewhere on the net (e.g. Saving Entity causes duplicate insert into lookup data, Prevent Entity Framework to Insert Values for Navigational Properties ) to solve issue 1 and then find myself fighting against this same issue: An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key .
I will now provide a few code snippets to help build the picture of what I am trying to do and what I am using to do it. First, an example of the entities involved:
public class Customer : BaseEntity
{
public string Name { get; set; }
[LocalizedDisplayName("Contacts")]
public virtual List Contacts { get; set; }
}
public class Contact : BaseEntity
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
public int? LanguageTypeID { get; set; }
[Required]
[ForeignKey("LanguageTypeID")]
public virtual LanguageType Language { get; set; }
}
public class LanguageType : Lookup
{
[LocalizedDisplayName("CultureName")]
public string CultureName { get; set; }
}
public class Lookup : BaseEntity
{
public string DisplayName { get; set; }
public int DisplayOrder { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class BaseEntity
{
public int ID { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public DateTime? DeletedOn { get; set; }
public bool Deleted { get; set; }
public bool Active { get; set; }
public ApplicationUser CreatedByUser { get; set; }
public ApplicationUser UpdatedByUser { get; set; }
}
In my controller, I have some code like the following:
foreach(Contact contact in lstContacts)
{
customer.Contacts.Add(contact);
}
if (ModelState.IsValid)
{
repository.Add(customer);
}
Let us suppose that each of the contacts has the same LanguageType of 'English' assigned (and in this example it is the fact that I am trying to save multiple contacts that have the same LanguageType that triggers the ObjectStateManager error). Initially, the repository.Add() code just did a context.SaveChanges() which did not work as expected, so now it looks something like this (Entity variable is a Customer):
try
{
if(Entity.Contacts != null)
{
foreach(Contact contact in Entity.Contacts)
{
var entry = this.context.Entry(contact.Language);
var key = contact.Language.ID;
if (entry.State == EntityState.Detached)
{
var currentEntry = this.context.LanguageTypes.Local.SingleOrDefault(l => l.ID == key);
if (currentEntry != null)
{
var attachedEntry = this.context.Entry(currentEntry);
//attachedEntry.CurrentValues.SetValues(entityToUpdate);
attachedEntry.State = EntityState.Unchanged;
}
else
{
this.context.LanguageTypes.Attach(contact.Language);
entry.State = EntityState.Unchanged;
}
}
}
}
context.Customers.Add(Entity);
context.SaveChanges();
}
catch(Exception ex)
{
throw;
}
Is it fundamentally wrong to expect this to have worked? How am I supposed to save and example like this? I have similar problems saving similar object graphs. When I look at tutorials and examples for EF, they are all simple and they all just call SaveChanges() after doing something very similar to what I am doing here.
I've just recently been using the ORM capabilities of ColdFusion (which is hibernate under the covers) and there are would simply load the LanguageType entity, assign it to the Contact entity, save the Contact entity, assign it to the Customer and then save the Customer.
In my mind, this is the most basic of situations and I cannot believe that it has caused me so much pain - I hate to say it, but using plain old ADO.NET (or heaven forbid, ColdFusion which I really don't enjoy) would have been MUCH simpler. So I am missing SOMETHING. I apparently have a key flaw in my understanding/approach to EF and If somebody could help me to make this work as expected and help me to figure out just where my misunderstanding lies, I would greatly appreciate it. I have spend too many hours and hours on this and it is a waste of time - I have/will have countless examples just like this one in the code I am building so I need to adjust my thinking with respect to EF right now so I can be productive and do approach things in the expected way.
Your help will mean so much and I thank you for it!
Let's consider the following object graph in which a teacher instance is the root object,
Teacher --[has many]--> courses
Teacher --[Has One]--> Department
In entity framework's DbContext, each instance of an object has a State indicating whether the object is Added, Modified, Removed or Unchanged. What happens apparently is the following :
Creating the root object for the first time
In this case, in addition to the newly created root object Teacher, ALL the child objects in the graph will have the State Added as well even if they're already created. The solution for this problem is to include the foreign key property for each child element and use it instead, i.e. Teacher.DepartmentId = 3 for example.
Updating the root object and one of its child elements' properties
Suppose you fetch a teacher object from the db, and you change the Teacher.Name property as well as the Teacher.Department.Name property; in this case, only the teacher root object will have the State marked as Modified, the department's State on the other hand remains Unchanged and the modification won't be persisted into DB; Silently without any warning.
EDIT 1
I used your classes as follows and I don't have a problem with persisting the objects :
public class Customer : BaseEntity
{
public string Name { get; set; }
public virtual List<Contact> Contacts { get; set; }
}
public class Contact : BaseEntity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int? LanguageTypeID { get; set; }
public Customer Customer { get; set; }
[ForeignKey("LanguageTypeID")]
public LanguageType Language { get; set; }
}
public class LanguageType : Lookup
{
public string CultureName { get; set; }
}
public class Lookup : BaseEntity
{
public string DisplayName { get; set; }
public int DisplayOrder { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
public class BaseEntity
{
public int ID { get; set; }
public DateTime? CreatedOn { get; set; }
public DateTime? UpdatedOn { get; set; }
public DateTime? DeletedOn { get; set; }
public bool Deleted { get; set; }
public bool Active { get; set; }
public ApplicationUser CreatedByUser { get; set; }
public ApplicationUser UpdatedByUser { get; set; }
}
public class ApplicationUser
{
public int ID { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
}
And used the following Context :
public class Context : DbContext
{
public Context() : base("name=CS") { }
public DbSet<Customer> Customers { get; set; }
public DbSet<Contact> Contacts { get; set; }
public DbSet<LanguageType> LanguageTypes { get; set; }
public DbSet<ApplicationUser> ApplicationUsers { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
//I'm generating the database using those entities you defined;
//Here we're demanding not add 's' to the end of table names
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
Then I created a unit tests class with the following :
[TestMethod]
public void TestMethod1()
{
//our context
var ctx = new Infrastructure.EF.Context();
//our language types
var languageType1 = new LanguageType { ID = 1, Name = "French" };
var languageType2 = new LanguageType { ID = 2, Name = "English" };
ctx.LanguageTypes.AddRange(new LanguageType[] { languageType1, languageType2 });
//persist our language types into db before we continue.
ctx.SaveChanges();
//now we're about to start a new unit of work
var customer = new Customer
{
ID = 1,
Name = "C1",
Contacts = new List<Contact>() //To avoid null exception
};
//notice that we're assigning the id of the language type and not
//an object.
var Contacts = new List<Contact>(new Contact[] {
new Contact{ID=1, Customer = customer, LanguageTypeID=1},
new Contact{ID=2, Customer = customer, LanguageTypeID=2}
});
customer.Contacts.AddRange(Contacts);
//adding the customer here will mark the whole object graph as 'Added'
ctx.Customers.Add(customer);
//The customer & contacts are persisted, and in the DB, the language
//types are not redundant.
ctx.SaveChanges();
}
It all worked smoothly without any problems.
As far as i know there is no build in support for reattaching modified graphs (like the SaveOrUpdate method of nHibernate). Perhaps this or this can help you.

Identity Specification false Entity Code First

I have the fallowing 2 classes:
[Table("People")]
public class Person : IPerson
{
[Key]
[DatabaseGeneratedAttribute(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
public string FirstName { get; set; }
public Person()
{
Results = new Collection<Result>();
}
public string LastName { get; set; }
public string Name
{
get
{
return FirstName + " " + LastName;
}
set{}
}
public string Email { get; set; }
public DateTime? LastModified { get; set; }
public virtual ICollection<Result> Results { get; set; }
}
and
[Table("UserProfile")]
public class UserProfile : Person
{
public UserProfile()
{
Faculty = new Faculty();
Projects = new Collection<Project>();
}
public string UserName { get; set; }
public string CNP { get; set; }
public virtual Faculty Faculty { get; set; }
public virtual ICollection<Project> Projects { get; set; }
}
Every time i use EF CodeFirst to generate my DB, and try too run the seed method i get an error. An error occurred while updating the entries. See the inner exception for details. System.Data.SqlClient.SqlException: Cannot insert explicit value for identity column in table 'UserProfile' when IDENTITY_INSERT is set to OFF. You can find more about it here Seeding Membership with custom data Entity framework
I found out that to fix it after running update-database from the package manager console i have to open the table in server explorer and set Idendtity Specification to false for the Id in the UsersProfile table, my question is can i do something to my model so that i won't have to this every time i regenerate my Db.
I tried the answer from here Identity specification set to false but because of the inheritance (i think) i get
Conflicting configuration settings were specified for property 'Id' on type 'Prometheus.Models.Person':
DatabaseGeneratedOption = None conflicts with DatabaseGeneratedOption = Identity
Thank you.
I'm not totally sure exactly what is happening here but I do know the UserProfile table is often created with a call to WebSecurity.InitializeDatabaseConnection rather than the migrations code and that's going to put in an IDENTITY field. So there is a conflict between WebSecurity and Entity Framework here.
Then you are adding inheritance into the picture and because you have specified table names it is Table per Type - and Entity Framework wants that to use a shared primary key. So it probably does not want the UserProfile table to have an IDENTITY field.
I think you hit the nail on the head when you said
UserProfile inherits from People, that's my problem
I would change that relationship so that Person has a UserProfile instead. I think that models the real world more accurately and if you ever have any people who aren't users it will be easier to make the relationship optional. Like this:
[Table("People")]
public class Person : IPerson
{
//avoids virtual call in constructor
private ICollection<Result> _Results;
public Person()
{
_Results = new Collection<Result>();
}
//no annotations required.
//An int field named Id is a database generated key by convention
public int Id { get; set; }
//Person has a UserProfile (optional)
public int? UserProfileID { get; set; }
public UserProfile UserProfile { get; set; }
//etc
public virtual ICollection<Result> Results
{
get { return _Results; }
set { _Results = value; }
}
}
public class UserProfile : ModelBase
{
//UserProfile is always related to a Person
public int PersonID { get; set; }
public UserProfile Person { get; set; }
//etc
}
You will also find loads of stuff about preferring composition over inheritance.
Otherwise you need to dig in to the migrations to get the tables created in the way that supports TPT - but you should also be aware that switching identity on/off in migrations does not happen automatically.Perhaps you could create the UserProfile table in a migration instead of with WebSecurity.InitializeDatabaseConnection

When to initialize collection properties in Entities?

I am confused about initializing navigation properties which are of a collection type. In examples I see on web, the properties are always explicitly initialized in class constructor, however in my project I have two sets of such properties and one work without such initialization, one not:
public class User
{
public int UserId { get; set; }
[System.ComponentModel.DataAnnotations.Schema.InverseProperty("Requested")]
public virtual System.Collections.Generic.ICollection<Friendship> RequestedFriendships { get; set; }
[System.ComponentModel.DataAnnotations.Schema.InverseProperty("Received")]
public virtual System.Collections.Generic.ICollection<Message> ReceivedMessages { get; set; }
}
public class Message
{
public int MessageId { get; set; }
string Message { get; set; }
public virtual System.Collections.Generic.ICollection<User> Received { get; set; }
}
public class Friendship
{
public int FriendshipId { get; set; }
public int RequestedUserId { get; set; }
public virtual User Requested { get; set; }
}
The following works:
db.Users.Single(u => u.UserId == userId).RequestedFriendships.Add(new B.Models.Friendship
{
RequestedUserId = userId,
});
Also this:
B.Models.Message message = db.Messages.Add(new B.Models.Message
{
Message = text
});
db.SaveChanges();
However following the above the below fails:
message.Received.Add(db.Users.Single(u => u.UserId == userId));
Since message.Received is null above. What is the difference here from the first successful one?
The difference i see here is that your User instance is loaded from the database, which means that EF sets your properties, including navigation collections (with empty collections if no relationship is found for this User). However, your Message instance is created in your code (new B.Models.Message { Message = text }), your navigation collection is not initialized so you get an exception when you try to add an item in it.
I suggest you always initialize such navigation collections in your constructors when dealing with entities.

The number of elements in ICollection is zero in many-to-many relationship

I have a database made with Entity Framework. I have two tables Users and Advertisments and the relationship between them is Many-to-Many. Everything is working fine except when I want to return the number of ICollection in class Users.
[Table("Advertisments")]
public class Advertisment
{
public Advertisment()
{
Users = new HashSet<User>();
}
[Key]
public int AdvertismentID { get; set; }
public string Address { get; set; }
public string City { get; set; }
public double Rating { get; set; }
public int NumberOfRates { get; set; }
public string Title { get; set; }
public string PhoneNumber { get; set; }
public string Email { get; set; }
public double Latitude { get; set; }
public double Longitude { get; set; }
public ICollection<User> Users { get; set; }
}
[Table("Users")]
public class User
{
public User()
{
FavouriteAdvertisments = new HashSet<Advertisment>();
}
[Key]
public int UserID { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Name { get; set; }
public string Surname { get; set; }
public ICollection<Advertisment> FavouriteAdvertisments { get; set; }
}
public class GetHiredDBContext : DbContext
{
public GetHiredDBContext()
: base("GetHiredDBContext")
{ }
public DbSet<User> Users { get; set; }
public DbSet<Advertisment> Advertisments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<User>().HasMany(a => a.FavouriteAdvertisments).WithMany(u => u.Users).Map(m =>
{
m.MapLeftKey("UserID");
m.MapRightKey("AdvertismentID");
m.ToTable("UserAdvertisment");
});
}
}
And this is what I want to do:
public ICollection<Advertisment> favouriteAdvertismentsByUser(int UserID)
{
GetHiredDBContext db = new GetHiredDBContext();
foreach (User user in db.Users)
{
if (user.UserID == UserID)
{
return user.FavouriteAdvertisments;
}
}
return null;
}
Everytime I call this method, the number of elements in ICollection for every user is 0!
public ICollection<Advertisment> favouriteAdvertismentsByUser(int UserID)
{
GetHiredDBContext db = new GetHiredDBContext();
// First of all, you probably forgot to "include" FavouriteAdvertisments
var users = db.Users.Include(u => u.FavouriteAdvertisments);
// Second of all, use linq!
return users.SingleOrDefault(u => u.UserID == UserID).FavouriteAdvertisments;
}
If your using Entity Framework you need to write your queries in Linq so the query provider can translate that into a SQL statement. As you have it now it is doing a table scan. Instead try this:
public ICollection<Advertisment> favouriteAdvertismentsByUser(int UserID)
{
return new GetHiredDbContext()
.Users
.Single(u => u.UserID = UserID)
.FavouriteAdvertisements;
}
One thing to note, this method now expects there to be exactly 1 record in your table with that UserID. It will throw an exception if it does not exist. Personally I prefer this because if I'm calling a method I expect it to work, and exception would mean I coded something wrong allowing me to find bugs earlier. You also do not have to check if your collection is null before getting the count.
The way your entites are currently set up, you will have to either use Eager, or Explicit loading, your related entites will not be loaded automatically.
To Explicitly load, I believe you can use your original query (provided you're passing an entity that can be found in the DBSet, and make an explicit call to load the related information (using Load):
E.g. (provided your entity can be found).
public ICollection<Advertisment> favouriteAdvertismentsByUser(User userEntity)
{
// Load the blog related to a given post
GetHiredDBContext db = new GetHiredDBContext();
db.Entry(userEntity).Reference(p => p.FavouriteAdvertisments).Load();
return user.FavouriteAdvertisments;
}
Although it's probably cleaner to obtain your entity from your context, and call load on that, rather than interating through your entire set.
To Eagerly load, you make your load request at the time of query, using Include:
public ICollection<Advertisment> favouriteAdvertismentsByUser(int userID)
{
GetHiredDBContext db = new GetHiredDBContext();
User myUser = db.Users
.Where(x => x.UserID = userID)
.Include(x => x.FavouriteAdvertisments)
.FirstOrDefault();
return myUser.FavouriteAdvertisments;
}
To obtain the data the third way, using Lazy-Loading, you would have to make some alterations to your classes, namely marking your ICollection navigation properties as virtual, so entity framework is able to create valid proxy types for your classes. Your data would be available as required, and loaded on demand.
Hopefully I haven't got the problem completely wrong, just about to shut down/sleep.
Good luck.
More info: http://msdn.microsoft.com/en-us/data/jj574232.aspx

EF and Generic Repository, how to have it not add a new entity when updating

Having an issue with trying to add a new entity that has a graph of objects/entities that are not new.
Here's my Domain types with Code First approach:
public class Person : IEntity
{
public int Id { get; set; }
public string Name { get; set; }
public List<PersonSession> Sessions { get; set; }
}
public class PersonSession : IEntity
{
public int Id { get; set; }
public Person Person { get; set; }
public Session Session { get; set; }
}
public class Session : IEntity
{
public int Id { get; set; }
public string Title { get; set; }
}
So I have a person, they can have many sessions, and there will be a table of their sessions. The Session table though will not have any reference back to persons, that's what the PersonSessions table/relationship is for.
EF 5 does create these 3 tables using one DbSet.
But I am trying to make a simple Repository pattern with Generics and I have a Save method.
Only issue is it's adding a new session to the Sessions table even when I use an existing session given to me by the Repository.GetAll();
public int Save(T t)
{
if (t.Id == 0)
_context.Entry(t).State = EntityState.Added;
else
_context.Entry(t).State = EntityState.Modified;
_context.SaveChanges();
return t.Id;
}
So when I create 2 new sessions, then add them through the Generic Repo instance for Sessions, they get added correctly,
when I try and create a new person and give him those existing sessions like so:
private static void AddPerson()
{
var newPerson = new Person
{
JobTitle = EJobTitle.SoftwareDeveloper,
Name = "Mark W",
};
var sessionsForPerson = new List<PersonSession>();
var session1 = _sessionRepo.Retrieve(1);
var session2 = _sessionRepo.Retrieve(2);
sessionsForPerson.Add(new PersonSession
{
Session = session1,
Person = newPerson
});
sessionsForPerson.Add(new PersonSession
{
Session = session2,
Person = newPerson,
});
newPerson.Sessions = sessionsForPerson;
_personRepo.Save(newPerson);
}
It's adding the sessions a second time.
Is there a data attribute I can put over the PersonSession.Session property to tell it this will always be an existing session, or do I need to use something in the FluentApi to instruct EF?
thanks.
In your Save method you set the state of the new Person to EntityState.Added. When you do that, EF sets the state of each object in the object graph to Added. It does not matter that the Sessions already have an Id > 0. This is unless the objects in the graph were previously retrieved by the same context. In that case they are and remain unchanged (and only the association will be saved).
So that's the problem. Your repositories each have a context of their own. It's a common problem with generic repositories. One possible remedy is to create a context outside the repositories and inject it into the repositories that are involved in one business transaction. Another one is to make sure that existing objects enter a context in an unmodified state by traversing the object graph after adding a root entity.
Try specifying which properties are your Entity Keys:
public class Person : IEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public List<PersonSession> Sessions { get; set; }
}
public class PersonSession : IEntity
{
[Key]
public int Id { get; set; }
public Person Person { get; set; }
public Session Session { get; set; }
}
public class Session : IEntity
{
[Key]
public int Id { get; set; }
public string Title { get; set; }
}

Categories