views:

208

answers:

1

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 :)

+1  A: 

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

Alex James
All your assumptions were right. I do however still get problems with the fixup using your suggestion:Test method UserCanAddBlogEntry threw exception: System.InvalidOperationException: Invalid relationship fixup detected in the navigation property 'BlogEntries' of the entity of the type 'Blog'.
mhenrixon
Hmm... I'm a little baffled right now.Instead of entry.Blog = blog let me know what happens if you try blog.BlogEntries.Add(entry) instead.
Alex James
I did as you suggested except for one thing. I created a a method AddEntry(Entry entry) that just add an entry to the IList<BlogEntry> as recommended in the poco posts ado.net blog has. Should be in the User class in future. There are two issues, doing what you suggested is giving me fixup problem. Doing what you suggested in the comment gives me a nullreference exception. I think it's the proxy that creates some sort of issue but seriously I don't even know if it's a bug. I mean I said I can take care of myself it just tells me to. I am not going to expose the context this way anyways.
mhenrixon