1

I'm using Entity Framework with Code first. My Relationship properties keep breaking.

I have the object Element:

public class Element : IElement
{
    // ... some event handlers (removed)

    [Key]
    public Guid ID { get; set; } = Guid.NewGuid();

    public string Name { get; set; }

    // navigation properties
    public virtual ElementType ElementType { get; private set; }

    public virtual NotifiableCollection<Property> Properties { get; private set; } = new NotifiableCollection<Property>();

    // Parameterless constructor for serialization
    private Element() { }

    public Element(ElementType elementType) : base()
    {
        // loop through and create Properties for each Property Type
        ElementType = elementType;
        if (ElementType?.PropertyTypes != null)
        {
            ElementType.PropertyTypes.ToList().ForEach((property) =>
            {
                Properties.Add(new Property(property));
            });
        }
    }
}

And ElementType:

public class ElementType : IElementType
{
    // ... some event handlers (removed)

    [Key]
    public Guid ID { get; set; } = Guid.NewGuid();

    public string Name { get; set; }

    // navigation properties
    public virtual NotifiableCollection<PropertyType> PropertyTypes { get; set; } = new NotifiableCollection<PropertyType>();

    public virtual NotifiableCollection<Element> Elements { get; set; } = new NotifiableCollection<Element>();

    public ElementType()
    {
        // ensure our Element's get updates
        PropertyTypes.CollectionChanged += (e, a) =>
        {
            //update the database to send out renewal to interested entities
            if (a.ChangeType == ChangeType.Added)
            {
                foreach (Element element in Elements)
                {
                    element.Properties.Add(new Property(a.Item));
                }
            }
        };
    }
}

It works fine when I create these objects the first time (as I've explicitly set the navigation properties then saved):

enter image description here

However, when I then close everything and get these from the database:

enter image description here

The navigation properties are not resolved. The table definitions set up the foregn key relationship fine:

CREATE TABLE [dbo].[Elements] (
    [ID]             UNIQUEIDENTIFIER NOT NULL,
    [Name]           NVARCHAR (MAX)   NULL,
    [ElementType_ID] UNIQUEIDENTIFIER NULL,
    CONSTRAINT [PK_dbo.Elements] PRIMARY KEY CLUSTERED ([ID] ASC),
    CONSTRAINT [FK_dbo.Elements_dbo.ElementTypes_ElementType_ID] FOREIGN KEY ([ElementType_ID]) REFERENCES [dbo].[ElementTypes] ([ID])
);


GO
CREATE NONCLUSTERED INDEX [IX_ElementType_ID]
    ON [dbo].[Elements]([ElementType_ID] ASC);

and I can see the data is all correct:

ID                                    Name                             ElementType_ID
ff186746-62cb-4246-9c64-f2d007b23ac0  Aircon Test 27/03/2017 12:54:03  57d93ac1-ad3b-4718-a593-80639cc24907

which matches an ID in ElementType table.

I have this set in my repository:

context.Configuration.ProxyCreationEnabled = true;
context.Configuration.LazyLoadingEnabled = true;

And the context is still active at the time where I'm trying to resolve this property.

Everything was working, but I've had this problem multiple times with EF, where my navigation properties just break randomly. I don't remember touching any of the code associated with this element, just ran it and now it doesn't work. Can anyone help?

Edit: This is the repository code:

public sealed class Repository : IRepository
{
    public event ObjectMaterializedEventHandler ObjectMaterialized;

    public Repository() {
        (context as IObjectContextAdapter).ObjectContext.ObjectMaterialized += ObjectContext_ObjectMaterialized; ;
        context.Configuration.ProxyCreationEnabled = true;
        context.Configuration.LazyLoadingEnabled = true;
    }

    // I do this to wire in some events later
    private void ObjectContext_ObjectMaterialized(object sender, ObjectMaterializedEventArgs e)
    {
        ObjectMaterialized?.Invoke(this, e);
    }

    private DataContext context = new DataContext(false);

    public IEnumerable<T> GetAll<T>() where T : class
    {
        return context.Set<T>().ToList() as IEnumerable<T>;
    }

    public T GetItem<T>(Guid id) where T : class
    {
        return context.Set<T>().Find(id) as T;
    }
    ...
}

the context stores them like this:

public class DataContext : DbContext
{
    ...
    public DbSet<Element> Elements { get; set; }
    public DbSet<ElementType> ElementTypes { get; set; }
}

I think it IS something to do with accessing. I'm accessing the Element with context.Set().Find(id) as T, and it fails. However, if I navagate through the ElementTypes, find it's list of Entities, then it works fine.

10
  • Do you reuse your EF context for multiple operations (that is: do you save it in some static\instance variable)? Commented Mar 27, 2017 at 12:06
  • Can you show how you get the entities from the database too? Are you wrapping the context/repository in a using block perhaps? Commented Mar 27, 2017 at 12:07
  • Yes, the context is retained in the repository. I'll add some code from this to show retrieval. Commented Mar 27, 2017 at 12:10
  • Is the repository destroyed before you try to enumerate the navigation properties? Commented Mar 27, 2017 at 12:16
  • 1
    ` as T` cannot change the actual type. Looks like Find is not returning proxy. So either (A) for some reason the Element is not eligible for lazy loading to EF, or (B) you have added the Element instance manually. If you put a breakpoint and try context.Set<Element>().Create();, does it return proxy (to eliminate the case (B))? Commented Mar 27, 2017 at 13:21

1 Answer 1

3

Found the answer with the help of Ivan in the comments.

The issue is with having a private constructor:

// Parameterless constructor for serialization
private Element() { }

One of the requirement for proxies is a public or protected constructor:

For either of these proxies to be created: A custom data class must be declared with public access.

  • A custom data class must not be sealed (NotInheritable in Visual Basic)

  • A custom data class must not be abstract (MustInherit in Visual Basic).

  • A custom data class must have a public or protected constructor that does not have parameters. Use a protected constructor without parameters if you want the CreateObject method to be used to create a proxy for the POCO entity. Calling the CreateObject method does not guarantee the creation of the proxy: the POCO class must follow the other requirements that are described in this topic.

  • The class cannot implement the IEntityWithChangeTracker or IEntityWithRelationships interfaces because the proxy classes implement these interfaces.

  • The ProxyCreationEnabled option must be set to true.

For lazy loading proxies: Each navigation property must be declared as public, virtual (Overridable in Visual Basic), and not sealed (NotOverridable in Visual Basic) get accessor.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.