I'm new to entity frame work code first.
I have simple class called Cat and a list of cats, when i'm doing the following :
mAllAnimals.Add(new Cat() { Father = null , Name = "Father Kitten", NickName = "Shmil" });
mAllAnimals.Add(new Cat() { Father = mAllAnimals.First(a => a.Name == "Father Kitten") , Name = "Son Kitten" , NickName = "son" });
i get an exception,because he couldn't find "Father kitten", but when i put between the to statements "SaveChanges()" it works perfectly.
This is very strange for me, do i actually need to save every step of the way ? can't he search on the local copy and on the db, i thought that part of the fun in entity framework is that i can work "normally" with my class and doesn't have to save my changes every step of the way.
Can i make him "auto save" every step i do, so i won't have to write all the time "SaveChanges"
One more question, i worked previously with NHibrnate and all the mapping where made using simple XML files. i don't see any files here, were is the mapping ? can i change it ?
Thanks in advance
On the lack of an .edmx, see:
http://blogs.msdn.com/b/adonet/archive/2011/03/07/when-is-code-first-not-code-first.aspx
Code First does not use an .edmx file, and it is also called Code Only for that reason. You do the mapping using attributes or fluent API. See the first two posts in the 12-part series on the Entity Framework blog:
http://blogs.msdn.com/b/adonet/archive/2011/01/27/using-dbcontext-in-ef-feature-ctp5-part-1-introduction-and-model.aspx
This should work:
Cat father = new Cat() { Father = null , Name = "Father Kitten", NickName = "Shmil" };
Cat son = new Cat() { Father = father , Name = "Son Kitten" , NickName = "son" };
mAllAnimals.Add(father);
mAllAnimals.Add(son);
context.SaveChanges();
The reason your version doesn't work is that you are looking for the father entity in the database - but you will only add it to the DB when you call SaveChanges()
As for the mapping - your project should have an .edmx file - double clicking that will bring up a designer which also allows you to modify the mapping.
Related
I have a simple Entity data model (using VS2010) that I reverse engineered from a simple SQL Server database that contains three unrelated tables.
I try to save data to the database using the following code:
var dbOptions = new ARC_WHENTECHModel.TEMP_LANDED_WHENTECH_OPTION_POSITION();
//first map fields required for standard di processing
dbOptions.OPTION_POSITION_SOURCE_ID = webSvcOption.TDR + "_" + webSvcOption.CNTR + "_" + webSvcOption.CRV;
dbOptions.INSERTED_DT = DateTime.Now;
dbOptions.CURRENT_IND = "Y";
//now map the data we've pulled from the web service call
dbOptions.CA = webSvcOption.CA;
dbOptions.CDTP = webSvcOption.CDTP;
dbOptions.CMD = webSvcOption.CMD;
dbOptions.CNTR = webSvcOption.CNTR;
dbOptions.CPP = webSvcOption.CPP;
dbOptions.PDTP = webSvcOption.PDTP;
dbOptions.SPR = webSvcOption.SPR;
dbOptions.TDR = webSvcOption.TDR;
context.AddToTEMP_LANDED_WHENTECH_OPTION_POSITION(dbOptions);
context.SaveChanges();
but I get the following exception:
An EdmType cannot be mapped to CLR classes multiple times. The EdmType 'RDLMServiceTier.TEMP_LANDED_WHENTECH_FUTURES_POSITION' is mapped more than once.
at the following line (base.AddObject) within the designer.cs code:
[global::System.CodeDom.Compiler.GeneratedCode("System.Data.Entity.Design.EntityClassGenerator", "4.0.0.0")]
public void AddToTEMP_LANDED_WHENTECH_OPTION_POSITION(TEMP_LANDED_WHENTECH_OPTION_POSITION tEMP_LANDED_WHENTECH_OPTION_POSITION)
{
base.AddObject("TEMP_LANDED_WHENTECH_OPTION_POSITION", tEMP_LANDED_WHENTECH_OPTION_POSITION);
}
Could anyone please advise what I may be doing wrong? I don't have much experience with Entity Framework (it's always "just worked" in the past). I guess the next step may be to reflect the EF code but that's going to be quite tricky because I'm debugging on a remote machine that I don't have great scope for installing extra software.
Thanks
Rob.
Gert answered this correctly - there was another copy of the model file present.
I just had a similar problem, and the specific line that caused it was:
[EdmComplexTypeAttribute(NamespaceName = "MyModel", Name = "MyDuplicatedClassName")]
Once the duplicated name was fixed everything worked fine.
My question can better be explained via example code. I am using POCO with change tracking proxies that is generated by the C# POCO generator by default. Please see below.
Assume you have Movie, MusicDirector and Director in the database and the relationship between them is a Director & MusicDirector can direct multiple movies and a movie can have only one Director and MusicDirector. Since I am a new user and cannot post images, here is my db structure.
Movie table has MovieId, Name,MusicDirectorId,DirectorId
Director table has DirectorId, Name
MusicDirector table has MusicDirectorId, Name
Here is the link to the diagram. http://i.stack.imgur.com/ce49r.png.
I am trying to insert a new movie and the director and musicdirector "already exists" in the db. Below is my code.
Movie movie = new Movie();
movie.Name = "Movie1";
movie.Director = new Director() { Name = "DirectorA" };
movie.MusicDirector = new MusicDirector() { Name = "MusicDirectorA" };
using (TestEFEntities ctx = new TestEFEntities())
{
movie.Director = ctx.Directors.Where(x => x.Name == movie.Director.Name).FirstOrDefault();
movie.MusicDirector = ctx.MusicDirectors.Where(x => x.Name == movie.MusicDirector.Name).FirstOrDefault();
ctx.Movies.AddObject(movie);
ctx.SaveChanges();
}
Now when I do this, MusicDirector record is added again even though it is overwritten by the record from the db. You may think why I am keeping this line movie.Director = new Director() { Name = "DirectorA" }; initially, it is an Asp.net MVC app where Movie object is bound with the director and musicdirector names the user adds. So, the first 4 lines are done implicitly by MVC and think all the other lines are in the service layer. Am I missing something as this is a very basic scenario and the framework should handle it? Of course, one solution to correct this problem is to create a new Movie object and assign the records from db which I don't want to do as I have to copy all the properties from the movie object sent by controller. How can I solve this problem?
Also this works correctly in Self-Tracking Entities. Is this some kind of a limitation to POCO and it would be great if someone can explain the behavior?
If you are using POCOs, then your code should work as the way you expected. Alternatively, you can try to populated the FKs instead of assigning the whole object like this:
using (TestEFEntities ctx = new TestEFEntities())
{
movie.DirectorID = ctx.Directors
.Where(x => x.Name == movie.Director.Name).First().DirectorID;
movie.MusicDirectorID = ctx.MusicDirectors
.Where(x => x.Name == movie.MusicDirector.Name).First().MusicDirectorID;
ctx.Movies.AddObject(movie);
ctx.SaveChanges();
}
I have an Entity Framework v1 project. I have two entities (Roles and Permissions), which have a many-to-many relationship with each other. I pass in a object to be saved (through a WCF call, I do not create it from a context myself), which has new entries in the many-to-many relationship.
I use "context.ApplyPropertyChanges" to update the record with the new properties. I know that this does not update relationships though. I attempt to either do a ChildCollection.Add(relatedObject); or ChildCollection.Attach(relatedObject).
When I use the "Add" method, I get the error that: The object cannot be added to the ObjectStateManager because it already has an EntityKey. Use ObjectContext.Attach to attach an object that has an existing key.
When I use the "Attach" method, I get the error that: The object cannot be added to the ObjectStateManager because it already has an EntityKey. Use ObjectContext.Attach to attach an object that has an existing key.
I am getting quite frustrated, and I think I can hear the Entity Framework laughing at me.
Does anyone know how I can resolve this?
MyRole x = context.Roles.FirstOrDefault(a => a.RoleId == this.RoleId);
context.ApplyPropertyChanges("Roles", this);
foreach (MyPermission p in this.Permissions)
{
x.Permissions.Add(p);
// ^ or v
x.Permissions.Attach(p);
}
context.SaveChanges();
Thanks.
Wow. After 20 or so straight hours on this problem, I'm starting to hate the Entity Framework. Here is the code that appears to be working currently. I would appreciate any advice on how to make this more streamlined.
I did rework the WCF service so that there is only the one data context. Thanks Craig.
Then I had to change the code to the following:
MyRole x = context.Roles.FirstOrDefault(a => a.RoleId == this.RoleId);
if (x == null) // inserting
{
MyApplication t = this.Application;
this.Application = null;
context.Attach(t);
this.Application = t;
}
else // updating
{
context.ApplyPropertyChanges("Roles", this);
x.Permissions.Load();
IEnumerable<Guid> oldPerms = x.Permissions.Select(y => y.PermissionId);
List<MyPermission> newPerms = this.Permissions.Where(y => !oldPerms.Contains(y.PermissionId)).ToList();
IEnumerable<Guid> curPerms = this.Permissions.Select(y => y.PermissionId);
List<MyPermission> deletedPerms = x.Permissions.Where(y => !curPerms.Contains(y.PermissionId)).ToList();
// new
foreach (MyPermission p in newPerms)
{
x.Permissions.Add(context.Permissions.First(z => z.PermissionId == p.PermissionId));
}
// deleted
foreach (MyPermission p in deletedPerms)
{
x.Permissions.Remove(context.Permissions.First(z => z.PermissionId == p.PermissionId));
}
}
You are using multiple ObjectContexts concurrently (the variable context and whereever this came from). Don't do that. It will only make things very difficult for you. Use one ObjectContext at a time.
I can give more specific advice if you show more code.
I suspect you are getting the errors because the ObjectContext thinks you are trying to add a new entity but finds it already has a EntityKey. I use the AttachTo method of the ObjectContext to attach my already existing entities to their EntitySet. I have had results generating my entities from stubs or hitting the database. This way when you add the entity to the navigation property on your entity, the ObjectContext finds the entity in it's EntitySet and knows it is an existing entity and not a new one. I don't know if this is clear. I could post some code if it would help. As Mr Stuntz said in his answer, posting more of your code would help.
I have the following code snippet.
// Here I get an entity out of an EntityCollection<TEntity> i.e ContactSet. Entity is obtained and not null.
ProjectContact obj = ((Project)projectDataGrid.SelectedItem).ContactSet
.Where(projectContact => projectContact.ProjectId == item.ProjectId &&
projectContact.ContactId == item.ContactId).First();
// And the next line I just check whether ContactSet contains the queried entity that i.e. obj.
bool found = ((Project)projectDataGrid.SelectedItem).ContactSet.Contains(obj);
but found is always false. How can that be?
edit: Matt thank you for your guidance but let me make it a bit more clear since I haven't given out the full source code.
I have three tables in my database:
Project, Contact and ProjectContact and there's a many-to-many relationship between Project and Contact table through the ProjectContact table, although ProjectContact table has some extra columns other than Project and Contact table keys, and that's why I get an extra entity called ProjectContact if I use ADO.NET entity framework's entity designer generated code.
Now at some point I get a Project instance within my code by using a linq to entities query i.e:
var item = (from project in myObjectContext.Project.Include("ContactSet.Contact")
orderby project.Name select project).FirstOrDefault();
Note that ContactSet is the navigational property of Project to ProjectContact table and Contact is the navigational property of ProjectContact to Contact table.
Moreover the queried Project in question i.e. "item" has already some ProjectContacts in its item.ContactSet entity collection, and ContactSet is a standard EntityCollection implementation generated by entity designer.
On the other hand, ProjectContact overrides Equals() and GetHashCode() etc. but if I use that overriden implementation within an EqualityComparer then Project.ContactSet.Contains returns true so I'm guessing there's no problem with that but now the tricky part comes along. Assume that I have the following snippet:
using(SomeObjectContext myObjectContext = new SomeObjectContext())
{
var projectQueryable = from project in myObjectContext.Project.Include("ContactSet.Contact") orderby project.Name select project;
ObservableCollection<Project> projects = new ObservableCollection<Project>(projectQueryable.ToList());
var contactQueryable = from contact in myObjectContext.Contact select contact;
ObservableCollection<Contact> contacts = new ObservableCollection<Contact>(contactQueryable.ToList());
Project p = projects[0];
Contact c = contacts[0];
//Now if I execute the code below it fails.
ProjectContact projectContact = new ProjectContact();
projectContact.Contact = c;
projectContact.Project = p;
projectContact.ContactId = c.Id;
projectContact.ProjectId = p.Id;
projectContact.Role = ContactRole.Administrator; // This corresponds to the column in ProjectContact table and I do manual conversion within the partial class since EF doesn't support enums yet.
p.ContactSet.Add(projectContact); // This line might be unnecessary but just to be on the safe side.
// So now p.ContactSet does indeed contain the projectContact and projectContact's EntityState is Added as expected. But when I execute the line below without saving changes it fails.
bool result = p.ContactSet.Remove(projectContact); // result == false and projectContact is still in the p.ContactSet EntityCollection.
//Now if I execute the line below
myObjectContext.Delete(projectContact);
//Now projectContact's EntityState becomes Detached but it's still in p.ContactSet.
// Also note that if I test
bool exists = p.ContactSet.Contains(projectContact);
// It also returns false even if I query the item with p.ProjectContact.Where(...) it returns false.
}
Since everything occurs within the same ObjectContext I think I am missing something about EntityCollection.Remove(). But it seems still odd that ContactSet.Contains() returns false for an item obtained via direct Where query over ContactSet. In the end the question becomes:
How do you really Remove an item from an EntityCollection without persisting to the database first. Since a Remove() call after Add() apparently fails.
This looks like it should work, Some ideas:
Does ProjectContact override Object.Equals()? Or perhaps the ContactSet implements ICollection, and there may be a bug in the implementation of ICollection.Contains()?
I must admin this is kind of funny even though I think I understand why :) I created a UnitTest to add a blog entry to try Alex suggestion about my inheritance problems. Now I come across another one.
[TestMethod]
public void UserCanAddBlogEntry()
{
var context = new EntityContext(Options.LazyLoading);
var user = (from u in context.Users
.Include("Blog.BlogEntries")
where u.Id == 1
select u).FirstOrDefault();
BlogEntry entry = new BlogEntry();
entry.Header = "Test Entry";
entry.Text = "Test Text blah blah blah";
entry.CreatedAt = DateTime.Now;
entry.Blog = user.Blog;
user.Blog.BlogEntries.Add(entry);
context.SaveChanges();
Assert.IsTrue(user.Blog.BlogEntries.Count > 0);
}
Causes the exception:
Failed UserCanAddBlogEntry Zirzle.UnitTests
Test method UserCanAddBlogEntry threw
exception:
System.InvalidOperationException:
Invalid relationship fixup detected in
the navigation property 'User' of the
entity of the type 'Profile'.
Not sure what is wrong with this picture. If I add .Include("Profile") in the get query then save changes doesnt complain any more. I tried adding a 0.1 relation end for profile but that didn't work out either. Any suggestions? I suppose stack overflows personal EF expert might have an explanation :)
Well this one is interesting.
I'm assuming that Blog.BlogEntries is the inverse of BlogEntry.Blog
I'm also assuming that all the properties on all classes are virtual, so the EF can proxy the classes.
Given these assumptions some objects will be proxied (user and user.Blog) because the ObjectContext constructed them and some won't be proxied (entry) because you created them yourself.
Proxied classes automatically do fix-up, i.e. keep both ends of a relationship in sync
So doing this on a proxied user.Blog:
user.Blog.BlogEntries.Add(entry)
will automatically set entry.Blog to user.Blog at the same time to keep both relationships in sync for you.
On the other hand because entry isn't proxied this:
entry.Blog = user.Blog
won't do fixup.
So in this code you are essentially doing one half of the fix-up twice. We should probably gracefully handle this situation, but obviously we aren't, I will talk this through with the team.
Having said all that I suggest you do something a little simplier:
// notice there is no need to get the User just the users blog
var blog = (from u in context.Users
where u.Id == 1
select u.Blog).Single();
and just do this:
BlogEntry entry = new BlogEntry();
entry.Header = "Test Entry";
entry.Text = "Test Text blah blah blah";
entry.CreatedAt = DateTime.Now;
entry.Blog = blog;
//because BlogEntry inherits from Post if I remember your model
context.Posts.Add(entry);
context.SaveChanges();
This should work.
Alex