3

Closed

Cannot use a property with a private setter that is declared in a non-mapped base types as key for an entity set

description

"The following scenario in which the entity set is of the base type works as expected:
using System.Data.Entity;

namespace ConsoleApplication13
{
class Program
{
    static void Main(string[] args)
    {
        using (var context = new MyContext())
        {
            context.Customers.Add(new Customer() { Name = ""a"" });
            context.SaveChanges();
        }
    }
}

public class BaseEntity
{
    public int Id { get; private set; }
}

public class Customer : BaseEntity
{
    public string Name { get; set; }
}

public class MyContext : DbContext
{
    //public DbSet<Customer> Customers { get; set; }
    public DbSet<BaseEntity> Customers { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        //modelBuilder.Entity<Customer>().HasKey(e => e.Id);
    }
}
}

Changing the code to make the entity set of the derived type as follows:
using System.Data.Entity;

namespace ConsoleApplication13
{
class Program
{
    static void Main(string[] args)
    {
        using (var context = new MyContext())
        {
            context.Customers.Add(new Customer() { Name = ""a"" });
            context.SaveChanges();
        }
    }
}

public class BaseEntity
{
    public int Id { get; private set; }
}

public class Customer : BaseEntity
{
    public string Name { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<Customer> Customers { get; set; }
    //public DbSet<BaseEntity> Customers { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        //modelBuilder.Entity<Customer>().HasKey(e => e.Id);
    }
}
}

Causes the following exception to be thrown in the line containg the call to the Add method (notice the non-text characters used in the exception message):

System.Data.Entity.ModelConfiguration.ModelValidationException was unhandled
Message=One or more validation errors were detected during model generation:
System.Data.Edm.EdmEntityType: : EntityType 'Customer' has no key defined. Define the key for this EntityType.
System.Data.Edm.EdmEntitySet: EntityType: EntitySet �Customers� is based on type �Customer� that has no keys defined.
Source=EntityFramework
StackTrace:
   at System.Data.Entity.ModelConfiguration.Edm.EdmModelExtensions.ValidateAndSerializeCsdl(EdmModel model, XmlWriter writer)
   at System.Data.Entity.ModelConfiguration.Edm.EdmModelExtensions.ValidateCsdl(EdmModel model)
   at System.Data.Entity.DbModelBuilder.Build(DbProviderManifest providerManifest, DbProviderInfo providerInfo)
   at System.Data.Entity.DbModelBuilder.Build(DbConnection providerConnection)
   at System.Data.Entity.Internal.LazyInternalContext.CreateModel(LazyInternalContext internalContext)
   at System.Data.Entity.Internal.RetryLazy`2.GetValue(TInput input)
   at System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
   at System.Data.Entity.Internal.InternalContext.Initialize()
   at System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType)
   at System.Data.Entity.Internal.Linq.InternalSet`1.Initialize()
   at System.Data.Entity.Internal.Linq.InternalSet`1.get_InternalContext()
   at System.Data.Entity.Internal.Linq.InternalSet`1.ActOnSet(Action action, EntityState newState, Object entity, String methodName)
   at System.Data.Entity.Internal.Linq.InternalSet`1.Add(Object entity)
   at System.Data.Entity.DbSet`1.Add(TEntity entity)
   at ConsoleApplication13.Program.Main(String[] args) in C:\Users\divega\Documents\Visual Studio 2010\Projects\ConsoleApplication13\ConsoleApplication13\Program.cs:line 11
   at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()
InnerException:

Finally, uncommenting the line containing the call to HasKey in the OnModelCreating method as follows:
using System.Data.Entity;

namespace ConsoleApplication13
{
class Program
{
    static void Main(string[] args)
    {
        using (var context = new MyContext())
        {
            context.Customers.Add(new Customer() { Name = ""a"" });
            context.SaveChanges();
        }
    }
}

public class BaseEntity
{
    public int Id { get; private set; }
}

public class Customer : BaseEntity
{
    public string Name { get; set; }
}

public class MyContext : DbContext
{
    public DbSet<Customer> Customers { get; set; }
    //public DbSet<BaseEntity> Customers { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
        modelBuilder.Entity<Customer>().HasKey(e => e.Id);
    }
}
}

Causes the following exception in the line containing the call to the Add method:

System.InvalidOperationException was unhandled
Message=The key component 'Id' is not a declared property on type 'Customer'. Verify that it has not been explicitly excluded from the model and that it is a valid primitive property.
Source=EntityFramework
StackTrace:
   at System.Data.Entity.ModelConfiguration.Configuration.Types.EntityTypeConfiguration.ConfigureKey(EdmEntityType entityType)
   at System.Data.Entity.ModelConfiguration.Configuration.Types.EntityTypeConfiguration.Configure(EdmEntityType entityType, EdmModel model)
   at System.Data.Entity.ModelConfiguration.Configuration.ModelConfiguration.ConfigureEntities(EdmModel model)
   at System.Data.Entity.ModelConfiguration.Configuration.ModelConfiguration.Configure(EdmModel model)
   at System.Data.Entity.DbModelBuilder.Build(DbProviderManifest providerManifest, DbProviderInfo providerInfo)
   at System.Data.Entity.DbModelBuilder.Build(DbConnection providerConnection)
   at System.Data.Entity.Internal.LazyInternalContext.CreateModel(LazyInternalContext internalContext)
   at System.Data.Entity.Internal.RetryLazy`2.GetValue(TInput input)
   at System.Data.Entity.Internal.LazyInternalContext.InitializeContext()
   at System.Data.Entity.Internal.InternalContext.Initialize()
   at System.Data.Entity.Internal.InternalContext.GetEntitySetAndBaseTypeForType(Type entityType)
   at System.Data.Entity.Internal.Linq.InternalSet`1.Initialize()
   at System.Data.Entity.Internal.Linq.InternalSet`1.get_InternalContext()
   at System.Data.Entity.Internal.Linq.InternalSet`1.ActOnSet(Action action, EntityState newState, Object entity, String methodName)
   at System.Data.Entity.Internal.Linq.InternalSet`1.Add(Object entity)
   at System.Data.Entity.DbSet`1.Add(TEntity entity)
   at ConsoleApplication13.Program.Main(String[] args) in C:\Users\divega\Documents\Visual Studio 2010\Projects\ConsoleApplication13\ConsoleApplication13\Program.cs:line 11
   at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()
InnerException:


I have investigated the issue and found that this is because we require properties to have a setter (even private), but if the property is obtained from an inheriting type, not the declaring type, and there is only a private setter on the declaring type we fail to recognize that. This assumption is scattered in many places: code first, ObjectItemConventionAssemblyLoader, materialization (translator). While I have prototyped a fix that passes “dp run”, the change seems risky to me for RC. My main worry is that there may be many other places were this assumption is made that we may not be seeing/catching. In addition is would be a breaking change, as we would be mapping properties that we did not map/”see” before: i.e. properties with these characteristics.

(There is a prototype of this bug fix already. Details are included in the internal bug entry)
"

This item was migrated from the DevDiv work item tracking system [ID=323531].
Closed Apr 2, 2013 at 9:46 PM by lukew
Verified fixed

comments

ajcvickers wrote Apr 30, 2012 at 4:43 PM

ajcvickers wrote Mar 27, 2013 at 8:17 PM

Fixed in 20bb3dc12fbd

PrivacyPolicyUpdated (Enable use of unmapped properties with private setters on unmapped base classes)

The scenario here is an entity like so:

public class MyBase
{
public int Id { get; private set; }
}

public class MyEntity : MyBase
{
}

Code First would not allow this to be mapped and furthermore if it could be mapped then core EF would not be able to write to the property. The reason for this is that PropertyInfo.CanWrite returns false if the PropertyInfo is obtained from the derived class. In addition, methods that use the setter also don't work--for example SetValue, and GetSetMethod.

The fix is to look for the PropertyInfo for the same property on the property's DeclaringType amd see whether it is writable. If it is, then this is used throughout the stack for writing.

I attempted to functional test all the areas that I could think of where EF writes to properties and I think I have caught them all.