4

I'm building a forum project for ASP.NET MVC 3, running on .NET 4 with the latest version of Entity Framework.

I have the usual forum design, with a Board with Categories, and Categories with Forums, Forums with Topics and Topics with Posts etc.

Simplified:

public class Category {
    [Required]
    public virtual Board Board { get; set; }
}
public class Forum {
    [Required]
    public virtual Category Category { get; set; }
}
public class Topic {
    [Required]
    public virtual Forum Forum { get; set; }
}
public class Post {
    [Required]
    public virtual Topic Topic { get; set; }
}

When a new post is created the topic is informed, and when a topic is changed, the forum is informed.

So, again simplified:

public class Forum {
    public void TopicChanged(Topic topic) {
        // Do stuff
    }
}
public class Topic {
    public void PostAdded(Post post) {
        // Do stuff
        this.Forum.TopicChanged(this);
    }
}

So after creating a new Post (and committing it to the database), I call PostAdded on the parent topic. Now this is where it get odd!

When I commit the changes to my repositories, I get a validation error, Category on Forum is required.

If I look in the database, the Forum has a reference to the parent Category. If I stop the code and looks at the Forum object, it has a Category instance. So this looks like an issue with lazy loading, especially because if I add this line:

var cat = this.Category

At the bottom of the TopicChanged method in the Forum class, there's no longer any errors.

Am I doing something totally wrong here, have I run into a borderline issue, or what is going on? I figured EF would see that the reference is a lazy loaded reference, and if it hasn't been changed, there's no reason why this should fail on save???

Thanks, Steen

Steen Tøttrup
  • 3,755
  • 2
  • 22
  • 36
  • EF disables lazy loading when it validates. I remember [Ladislav](http://stackoverflow.com/users/413501/ladislav-mrnka) has given a detailed answer but I couldn't find the post. – Eranga Sep 21 '11 at 08:00
  • The answer is here: http://stackoverflow.com/questions/7305144/lazy-deferred-loading-of-links-not-on-time – Ladislav Mrnka Sep 21 '11 at 09:47
  • Thank you. I can understand why, but that makes it really hard to do what I want to! Bugger! – Steen Tøttrup Sep 21 '11 at 10:56
  • No it only shows how validation on data layer is stupid because it validates entities which are not modified / persisted as well. – Ladislav Mrnka Sep 21 '11 at 18:21
  • Well, ideally EF would know that it should either load the lazy loadable references and validate them, or ignore them if they haven't changed. EF knows which objects have changed, so it should also know which properties have changed. Annoying! – Steen Tøttrup Sep 22 '11 at 11:59
  • Take a look at this solution: http://stackoverflow.com/a/18528465/846767 Hope it helps, – Gorka Lerchundi Osa Aug 30 '13 at 09:33

1 Answers1

8

I've fixed my problem. It might not be a really nice and clean solution, but at least I can handle this situation now, without having to change a lot of my code (which needs to run with nHibernate also). So no dirty solution.

Just to give you an idea on how it's solved, I'll try and explain it here.

On the Commit() method on my repository, I start off calling GetValidationErrors() on the DbContext instance. This returns the error I encountered above along with the entity where the error occurs. On the base type of this entity (the entity is a proxy generated by EF) I run through all properties and when I encounter a property with the Required attribute, I call GetValue on the identical property on the proxy object.

Enough talk, more code:

var errors = this.context.GetValidationErrors();
foreach (DbEntityValidationResult result in errors) {
    Type baseType = result.Entry.Entity.GetType().BaseType;
    foreach (PropertyInfo property in result.Entry.Entity.GetType().GetProperties()) {
        if (baseType.GetProperty(property.Name).GetCustomAttributes(typeof(RequiredAttribute), true).Any()) {
            property.GetValue(result.Entry.Entity, null);
        }
    }
}
friism
  • 19,068
  • 5
  • 80
  • 116
Steen Tøttrup
  • 3,755
  • 2
  • 22
  • 36