Reputation: 7004
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):
However, when I then close everything and get these from the database:
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.
Upvotes: 1
Views: 3657
Reputation: 7004
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.
Upvotes: 3