Code First adding to collections? How to use Code First with repositories?
Asked Answered
G

6

15

EDIT: This happen only on larger scale projects with repositories. Is there anybody using EF4 with CodeFirst approach and using repositories? Please advise me.

Hi. Im currently working with EF4 CodeFirst Classes. In my test project I got two classes, Author and Book (author got books). What I'm trying to do is that I have a AddBook in my Author class, but that wont seem to work like I can't Add it to the collection.. here are my classes and two different exceptions.

 public class Book
{
    public virtual int BookId { get; set; }
    public virtual string Title { get; set; }
    public virtual Author Author { get; set; }
}

public class Author
{
    public virtual int AuthorId { get; set; }
    public virtual string Name { get; set; }
    public virtual ICollection<Book> Books { get; set; }

    public Author()
    {
        Books = new Collection<Book>();
    }

    public void AddBook(Book book)
    {
        book.Author = this;
        Books.Add(book);
    }
}

exception: The property 'Books' on type 'Author_4CF5D4EE954712D3502C5DCDDAA549C8E5BF02A0B2133E8826A1AC5A40A15D2A' cannot be set because the collection is already set to an EntityCollection.

I change the Author class to this

public class Author
{
    public virtual int AuthorId { get; set; }
    public virtual string Name { get; set; }
    public virtual ICollection<Book> Books { get; set; }

    public void AddBook(Book book)
    {
        book.Author = this;
        Books.Add(book);
    }
}

Exception: Object reference not set to an instance of an object.

cannot be set because the collection is already set to an EntityCollection.

And thats only natural that I get that exception because the Collection is not set to a new, but then I get that first exception. so how is this done with code first in EF?

maybe I should add that my It might collide with my DbSet?

public class EntityContext : DbContext, IUnitOfWork
{
    public DbSet<Author> Authors { get; set; }
    public DbSet<Book> Books { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.IncludeMetadataInDatabase = false;
    }

    public void Save()
    {
        SaveChanges();
    }
}
Gargan answered 4/10, 2010 at 20:37 Comment(0)
G
6

virtual on all the properties was the issue here for me.. Don't get why it have a impact like that when I did scaled it up to my project but that's how it was.. Hope this helps anybody that got the same issue..

Gargan answered 5/10, 2010 at 21:26 Comment(1)
I believe that if we make all the properties virtual we are allowing EF to generate Change Tracking Proxies.Kenosis
L
30

Removing the "virtual" keyword from the collection properties works around the problem, by preventing the Entity Framework from creating a change tracking proxy. However, this is not a solution for many people, because change tracking proxies can be really convenient and can help prevent issues when you forget to detect changes at the right places in your code.

A better approach would be to modify your POCO classes, so that they instantiate the collection properties in their get accessor, rather than in the constructor. Here's the original Author POCO class, modified to allow change tracking proxy creation:

public class Author
{
    public virtual int AuthorId { get; set; }
    public virtual string Name { get; set; }

    private ICollection<Book> _books;

    public virtual ICollection<Book> Books
    {
        get { return _books ?? (_books = new Collection<Book>()); }
        set { _books = value; }
    }

    public void AddBook(Book book)
    {
        book.Author = this;
        Books.Add(book);
    }
}

In the above code the collection property is no longer automatic, but rather has a backing field. It's better if you leave the setter protected, preventing any code (other than the proxy) from subsequently modifying these properties. You will notice that the constructor was no longer necessary and was removed.

Lilylilyan answered 28/3, 2012 at 17:37 Comment(1)
But why were the proxies causing this exception?Languor
G
7

The issue mentioned above by Dejan.S regarding the use of 'List' to initialize a collection still occurs in EF5 RTM and ASP.NET 4.5 RTM:

List init issue

BUT, if you remove the virtual keyword from the primary key (e.g. UserId in the above screencap), it works! So it seems all of your properties can be virtual, EXCEPT the primary key.

Geer answered 26/8, 2012 at 22:40 Comment(0)
G
6

virtual on all the properties was the issue here for me.. Don't get why it have a impact like that when I did scaled it up to my project but that's how it was.. Hope this helps anybody that got the same issue..

Gargan answered 5/10, 2010 at 21:26 Comment(1)
I believe that if we make all the properties virtual we are allowing EF to generate Change Tracking Proxies.Kenosis
P
4

I've got code first working, but the only (major) difference with my code is I initialize it as a list rather than a collection... so my code reads something like:

public class Author
{
    public virtual int AuthorId { get; set; }
    public virtual string Name { get; set; }
    public virtual ICollection<Book> Books { get; set; }

    public Author()
    {
        Books = new List<Book>();
    }
}

Also, I just add directly to the Books collection - there is no need to add the book to the collection AND add the author to the book because the entity framework should take care of that.

If this doesn't work for you. Let me know.

HTHs,
Charles

Phenazine answered 4/10, 2010 at 22:1 Comment(3)
This works fine when I try on a small test application but when I implement this type on my larger scale project with repositories and services the problem still remains..Gargan
I'm sorry, what is it that works fine on a small test application? My project that I'm working on is a large(ish) application with repositories and services - I don't see how this should make a difference though... it should scale without any issues.Phenazine
Me neither but it does or did.. I removed the Virtual on my properties and kept it on the ICollection and it works on my full scale project like it suppose to now, kinda like a DOH moment but I wont say to much yet, so far so good need to test this more later today.. thanks for your reply CharlinoGargan
N
2

Try use this signature. I hope this worked.

public class Author
{
    public virtual int AuthorId { get; set; }
    public virtual string Name { get; set; }

    private ICollection<Book> _books;

    public virtual ICollection<Book> Books
    {
        get { return _books ?? (_books = new HashSet<Book>()); } // Try HashSet<N>
        set { _books = value; }
    }

    public void AddBook(Book book)
    {
        book.Author = this;
        Books.Add(book);
    }
}
Neysa answered 20/1, 2013 at 17:38 Comment(0)
S
0

The problem in this instance is that change tracking proxies are being generated because all the properties on your class are marked as virtual. By convention this triggers Change Tracking Proxy generation.

So you can either remove virtual keyword from one or more properties or simply tell the context not to generate change tracking proxies as per Working with POCO Entities.

var ctx = new MyDbContext();
ctx.Configuration.ProxyCreationEnabled = false;
Sherl answered 12/8, 2014 at 22:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.