Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unable to set field/property on entity type with Entity Framework 4.3.1

I have been using Entity Framework in a .NET 4.0 solution for a few weeks. It is EF 4.3.1. I have created the database schema first, and generated my Entity Objects using "EF4.x DbContext Generator" template.

I had three tables in the schema and it was all working fine with simple CRUD methods.

I have now added a fourth table, "Subjects", which has a foreign Key reference to an existing table "SourceUri", such that a SourceUri can have 0-many subjects, and a Subject has exactly one SourceUri.

I have updated my edmx model and it looks correct. However, no matter what I try, I cannot seem to do the following:

  • Add a new SourceUri record
  • Add one or more Subjects for the new SourceUri

This is the code I am currently trying. You can see I am saving the context regularly, but originally I was only saving the changes once at the end of the method.

    /// <summary>
    /// Adds a new Source URI to the system
    /// </summary>
    /// <param name="sourceUri">The source URI to add</param>
    /// <param name="subjectNames">List of subjects for this source URI, in order</param>
    /// <returns>The added source URI</returns>
    public SourceUri AddSourceUri(SourceUri sourceUri, IList<string> subjectNames)
    {
        try
        {
            _logger.Debug("Adding new source URI '{0}', with '{1}' subjects.", sourceUri.Uri, 
                subjectNames != null ? subjectNames.Count : 0);
            LogSourceUriDetails(sourceUri, "Adding");

            using (var dbContext = GetDbContext())
            {
                dbContext.SourceUris.Add(sourceUri);
                dbContext.SaveChanges(); // this save succeeds

                // add the subjects if there are any
                if (subjectNames != null)
                {
                    for (int i = 0; i < subjectNames.Count; i++)
                    {
                        Subject newSubject = new Subject()
                                                 {
                                                     DisplayOrder = i,
                                                     SourceUriId = sourceUri.SourceUriId,
                                                     SubjectText = subjectNames.ElementAt(i).Trim()
                                                 };
                        _logger.Debug("Adding new subject '{0}' to source URI '{1}'.", newSubject.SubjectText,
                                      sourceUri.Uri);
                        dbContext.Subjects.Add(newSubject); // this line fails
                        dbContext.SaveChanges();
                    }
                }

                _logger.Debug("Successfully added new source URI '{0}' with '{1}' subjects. Source URI ID is '{2}'.", 
                    sourceUri.Uri, subjectNames != null ? subjectNames.Count : 0, sourceUri.SourceUriId);
                return sourceUri;
            }
        }
        catch (Exception exception)
        {
            _logger.ErrorException(string.Format("An error occurred adding new source URI '{0}' with '{1}' subjects.",
                sourceUri.Uri, subjectNames != null ? subjectNames.Count : 0), exception);
            throw;
        }
    }

The code adds the new SourceUri and saves the changes. However, it does not manage to add the new Subject to the data context. It does not get as far as trying to save that change.

The exception is:

Unable to set field/property Subjects on entity type CommentService.DomainObjects.SourceUri. See InnerException for details.
System.Data.Objects.Internal.PocoPropertyAccessorStrategy.CollectionRemove(RelatedEnd relatedEnd, Object value)
System.Data.Objects.Internal.EntityWrapper`1.CollectionRemove(RelatedEnd relatedEnd, Object value)
System.Data.Objects.DataClasses.EntityCollection`1.RemoveFromObjectCache(IEntityWrapper wrappedEntity)
System.Data.Objects.DataClasses.RelatedEnd.Remove(IEntityWrapper wrappedEntity, Boolean doFixup, Boolean deleteEntity, Boolean deleteOwner, Boolean applyReferentialConstraints, Boolean preserveForeignKey)
System.Data.Objects.DataClasses.RelatedEnd.FixupOtherEndOfRelationshipForRemove(IEntityWrapper wrappedEntity, Boolean preserveForeignKey)
System.Data.Objects.DataClasses.RelatedEnd.Remove(IEntityWrapper wrappedEntity, Boolean doFixup, Boolean deleteEntity, Boolean deleteOwner, Boolean applyReferentialConstraints, Boolean preserveForeignKey)
System.Data.Objects.DataClasses.EntityReference`1.Exclude()
System.Data.Objects.DataClasses.RelationshipManager.RemoveRelatedEntitiesFromObjectStateManager(IEntityWrapper wrappedEntity)
System.Data.Objects.ObjectContext.AddObject(String entitySetName, Object entity)
System.Data.Entity.Internal.Linq.InternalSet`1.&lt;&gt;c__DisplayClass5.&lt;Add&gt;b__4()
System.Data.Entity.Internal.Linq.InternalSet`1.ActOnSet(Action action, EntityState newState, Object entity, String methodName)
System.Data.Entity.Internal.Linq.InternalSet`1.Add(Object entity)
System.Data.Entity.DbSet`1.Add(TEntity entity)
CommentService.Business.Managers.SourceUriManager.AddSourceUri(SourceUri sourceUri, IList`1 subjectNames) in C:\Projects\SVN Misc Projects\CommentService\trunk\CommentService.Business\Managers\SourceUriManager.cs:line 152
CommentService.Web.Comment.AddSourceUri(SourceUri sourceUri, IList`1 subjectNames) in C:\Projects\SVN Misc Projects\CommentService\trunk\CommentService.Web\Comment.svc.cs:line 173
SyncInvokeAddSourceUri(Object , Object[] , Object[] )
System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]&amp; outputs)
System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc&amp; rpc)
System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc&amp; rpc)
System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage41(MessageRpc&amp; rpc)
System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc&amp; rpc)
System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc&amp; rpc)
System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc&amp; rpc)
System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc&amp; rpc)
System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc&amp; rpc)
System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc&amp; rpc)
System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)

I have gone round and round on this, and have seen several slightly different exceptions. They all seem to point to the fact that the Subjects navigation property of the SourceUri object is in some way read-only or fixed length (array?).

The generated entity classes look as follows:

//------------------------------------------------------------------------------
// <auto-generated>
//    This code was generated from a template.
//
//    Manual changes to this file may cause unexpected behavior in your application.
//    Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace CommentService.DomainObjects
{
    using System;
    using System.Collections.Generic;

    public partial class SourceUri
    {
        public SourceUri()
        {
            this.Comments = new HashSet<Comment>();
            this.Subjects = new HashSet<Subject>();
        }

        public long SourceUriId { get; set; }
        public string Uri { get; set; }
        public string Description { get; set; }
        public System.DateTime DateCreated { get; set; }
        public string AdminUser { get; set; }

        public virtual ICollection<Comment> Comments { get; set; }
        public virtual ICollection<Subject> Subjects { get; set; }
    }
}


//------------------------------------------------------------------------------
// <auto-generated>
//    This code was generated from a template.
//
//    Manual changes to this file may cause unexpected behavior in your application.
//    Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace CommentService.DomainObjects
{
    using System;
    using System.Collections.Generic;

    public partial class Subject
    {
        public Subject()
        {
            this.Comments = new HashSet<Comment>();
        }

        public long SubjectId { get; set; }
        public long SourceUriId { get; set; }
        public string SubjectText { get; set; }
        public int DisplayOrder { get; set; }

        public virtual ICollection<Comment> Comments { get; set; }
        public virtual SourceUri SourceUri { get; set; }
    }
}

Why doesn't this work?

Quick list of things I've checked/tried:

  • The database schema looks correct - I can insert records as expected with SQL and primary keys, foreign keys and identities seem to be behaving correctly
  • The model seems to reflect the database schema correctly, including PK identities (EntityKey = true, StoreGeneratedPattern=Identity)
  • I have managed to get EF to persist data into my schema, but after the data is inserted, an exception is thrown stating that the context might be out of sync - again pertaining to not being able to update the Subjects navigation property of the SourceUri object
  • I have tried adding the Subjects to the dbContext.Subjects collection, and also to the SourceUri.Subjects collection. I have also tried setting the Subject's SourceUri property instead of the Subject's SourceUriId property.
like image 370
Paul Houghton Avatar asked Jul 16 '12 10:07

Paul Houghton


1 Answers

I have got to the bottom of this issue. The problem behaviour was caused by the SourceUri entity being passed into the method via a WCF web service. It meant that the ICollection properties of the object were being deserialized as an Array, which is of fixed-length, and cannot be added to.

I have resolved this issue by altering the template that generates my entities so that it produces classes where the collections are explicitly Lists, as follows:

//------------------------------------------------------------------------------
// <auto-generated>
//    This code was generated from a template.
//
//    Manual changes to this file may cause unexpected behavior in your application.
//    Manual changes to this file will be overwritten if the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------

namespace CommentService.DomainObjects
{
    using System;
    using System.Collections.Generic;
    using System.Linq; // added this

    public partial class SourceUri
    {
        public SourceUri()
        {
            this.Comments = new HashSet<Comment>().ToList(); // added this
            this.Subjects = new HashSet<Subject>().ToList(); // added this
        }

        public long SourceUriId { get; set; }
        public string Uri { get; set; }
        public string Description { get; set; }
        public System.DateTime DateCreated { get; set; }
        public string AdminUser { get; set; }

        public virtual List<Comment> Comments { get; set; } // altered these to List
        public virtual List<Subject> Subjects { get; set; } // altered these to List
    }
}
like image 111
Paul Houghton Avatar answered Nov 19 '22 13:11

Paul Houghton