0

How can I create+persist+have-a-proxy-to a new instance of a code-first poco using only navigation property collections? In the bellow code, I show how you might want to do this, if using member functions to create POCOs that then create POCOs. You don't have a DbContext, but if you create an object and persist it using DbSet.Add, the returned object isn't a proxy, so you can't in turn use its DbSet.Add to add a different sub-object.

In this code, if you call MailingList.AddIncomingMessage("my message"), you get an exception at the "OOPS" comment, because the created msg isn't a proxy and thus its Message.doodads property is null.

class Doodad {
  public int ID { get; set; }
  public string doodad { get; set; };
}

class Message {
  public int ID { get; set; }
  public virtual MailingList mailingList { get; set; } 
  public virtual ICollection<Doodad> doodads { get; set; }
  public string text { get; set; }

  public void GetDoodadCreateIfNeeded(string doodad) {
    try {
      // won't be found since we just created this Message
      return this.doodads.First(d => d.doodad == doodad);
    } catch (Exception e) {
      Doodad newDoodad = new Doodad() { doodad=doodad };

      // OOPS! this.doodads == null, because its not a proxy object
      this.doodads.Add(newDoodad);
      return newDoodad;
    }
  }
}

class MailingList {
  public int ID { get; set; }
  public virtual ICollection<Message> messages { get; set; }

  public void AddIncomingMessage(string message) {
    var msg = new Message() { text=message };

    // we have no Context, because we're in a POCO's member function
    this.messages.Add(msg);

    var doodad = msg.GetDoodadCreateIfNeeded("bongo drums");
  }
}

EDIT: sorry guys, I forgot to put the property accessors and ID in for this simplified case, but I am using them in the actual code.

Seth
  • 2,712
  • 3
  • 25
  • 41
  • to anyone else running into this, check out Alex's comment on http://stackoverflow.com/questions/5974448/entityframework-4-1-dbcontext-changetracking-kills-performance – Seth Sep 06 '11 at 05:52

2 Answers2

2

It has nothing to do with proxies. It is the same as any other code - if you want to use object / collection you must first initialize it! Your fist command:

return this.doodads.First(d => d.doodad == doodad);

doesn't throw exception because it didn't find doodad but because the doodads is null.

What do you need to do? You need to initialize collections before you first use them. You can do it:

  • Directly in their definition
  • In entity's constructor
  • In property getter (lazy initialization) once they are first needed - that would require to change your fields to properties which is btw. correct way to write classes in .NET
  • In your custom methods you can check if they are null and initialize them
Ladislav Mrnka
  • 360,892
  • 59
  • 660
  • 670
  • I've been concerned about adding constructors, because I don't want to overwrite data from the DB... e.g. if I have: public Message() { this.doodads = new IList(); }, will that accidentally overwrite data loaded from the DB when there are already associations for doodads, or will it only be called before data is loaded (or only called when I explicitly do a new)? – Seth Aug 20 '11 at 13:16
  • When I explicitly initialize doodads with the above constructor, when I later try to context.SaveChanges() I get: InvalidOperationException "Collection was modified; enumeration operation may not execute.". I think maybe I do need a proxy? – Seth Aug 20 '11 at 13:58
  • Ladislav, could you give an example of initialing a collection? I'm currently creating a new List, and getting an error when I do so. Should I use a different type than List? – Seth Aug 23 '11 at 15:27
  • InvalidOperationException "Collection was modified; enumeration operation may not execute." – Seth Aug 24 '11 at 21:25
  • Put the relevant code into your question. Simple initialization should not produce this error. – Ladislav Mrnka Aug 24 '11 at 21:36
  • Constructors are not executed when objects are deserialized with DataContractSerializer. This *interesting* design choice makes the constructor a bad place to initialize the class if there is any chance the object might be serialized now or in the future. – Eric J. Apr 16 '12 at 03:20
1

Complementary to the navigation property, you need to have a property that is the Id of the foreign key.

So your MailingList will need to have this property:

  [Key] // this attribute is important
  public int Id { get; set; }

and you'll have to change the Message classe to have these properties:

public virtual int mailingListId { get; set; 
public virtual MailingList mailingList { get; set; }

The { get; set; } property is important, so that it is a property, not just a public attribute.

Doug
  • 6,322
  • 3
  • 29
  • 48
  • good catch Doug, I forgot to include these when I made the simplified example to try and more clearly explain my problem. – Seth Aug 20 '11 at 13:13