Code First Data Annotations on non-public properties

The Entity Framework supports mapping to public, protected, internal, and private properties. However, there are a few restrictions when mapping to non-public properties. and there are also some things you need to know when attempting to configure such mappings using Code First. In particular, mapping to non-public properties cannot be done using data annotations alone.


Mapping to non-public members using Code First

We’ll get to data annotations in a minute, but first let’s look at how to actually get the non-public members mapped. Consider an entity class like this:

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }

    [MaxLength(8)]
    private string Alias { get; set; }
}

Code First only maps public properties by default. This means that it will map Id and Name, but it will not map Alias. In other words, the value for Alias will not be written to or read from the database.

To map the Alias property you need to use the Code First fluent API. What you want to do is do something like this in the OnModelCreating method of your context:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder
        .Entity()
        .Property(p => p.Alias);
}

The problem is that this won’t compile because the Alias property is not visible from outside the Person class. There are several ways to overcome this problem, probably the easiest of which is to embed an EntityTypeConfiguration for the Person entity inside the Person class itself. For example:

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }

    [MaxLength(8)]
    private string Alias { get; set; }

    public class PersonConfiguration : EntityTypeConfiguration<Person>
    {
        public PersonConfiguration()
        {
            Property(p => p.Alias);
        }
    }
}

Then in OnModelCreating you pass this EntityTypeConfiguration to Code First:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder
        .Configurations.Add(new Person.PersonConfiguration());
}

The less intrusive way to do it

Embedding an EntityTypeConfiguration in your entity class isn’t very clean and also means that your entity is no longer POCO, both conceptually and practically since it requires a reference to EntityFramework.dll to compile.

Jiri Cincura blogged a very nice solution to the problem that retains most of the POCO-ness of your entity classes. It is based on understanding that the Property fluent method expects to be passed an expression tree that describes the property. So we just need to find a way to create that expression tree. We can do that with a simple static Expression property embedded inside the entity class:

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }

    [MaxLength(8)]
    private string Alias { get; set; }

    public static readonly Expression<Func<Person, string>> AliasExpression = p => p.Alias;
}

The value of this property can now be used in OnModelCreating:

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder
        .Entity()
        .Property(Person.AliasExpression);
}

Is this really POCO?

The entity shown above is not persistence ignorant in the strictest sense since it contains code that is used to expose information about itself for use in configuring persistence. You could eliminate this code entirely if you’re willing to use Reflection and expression builders  (or equivalent utility libraries) to create the needed expression manually.

To me, the added complexity of doing this is not worth it over adding a static read-only property to the entity that does not reference any persistence types and does not change the behavior or interface of the entity in any way. Also, once you use Reflection it means using strings and the loss of Intelisense and refactoring support that this entails.

So what about data annotations

Before EF 4.3 any data annotation on a non-public property would be ignored by Code First. This was fixed in 4.3 so that data annotations are respected on any mapped property. The key phrase here is “any mapped property”. In other words, the property must still be mapped for the data annotation to be used. And putting a data annotation on a property does not cause it to be mapped.

So in the example above just putting MaxLength on the property did not cause Code First to map it. However, once the property was mapped using the fluent API then Code First read the data annotation and set the maximum length for the property to 8.

I understand that this is kind of confusing, especially when you read that Code First now supports data annotations on non-public properties without any clear explanation of what that means. We’re not sure yet how to make this less confusing, but we’re thinking about it.

Restrictions on mapping to non-public members

I said at the top of the post that there were a few restrictions on mapping to non-public properties. Those restrictions fall into three categories:

  • EF currently blocks the use of non-public properties when your app is running under partial trust (without ReflectionPermission) such as in a shared hosting environment. This is something we are hoping to address in a future release.
  • A private or internal property cannot be overridden by a type in another assembly, which means that the services of proxies such as lazy-loading and change-tracking will not work with private or internal properties.
  • There are some bugs around mapping private properties that are defined in unmapped base classes of the entity.

Hopefully this post clears up some of the confusion around data annotations on non-public properties.

About Arthur Vickers

Developer on the Entity Framework team at Microsoft.
This entry was posted in Code First, Data Annotations, Entity Framework and tagged , , . Bookmark the permalink.

17 Responses to Code First Data Annotations on non-public properties

  1. githunman says:

    Add ‘plumbing code’ a-la Jiri Cincura is a nice solution?? I don’t agree with you sincerely!. I seek a solution more like this http://geeks.ms/blogs/unai/archive/2011/03/30/ef-4-1-code-first-191-map-private-members.aspx, ie, more like Reveal in NHibernate..

    githunman

    • That link certainly shows the cleanest way to do it in terms of keeping all “plumbing” code out of the enity. I much prefer Jiri’s solution because it is so much simpler and also doesn’t use string parameters and thereby supports intellisence, refactoring, etc. I’m a big proponant of POCO and persistence ignorance but having a static readonly field in your entity that only uses standard types (i.e. no EF types) and doesn’t effect the behavior or interface of your class in any way is not a high price to pay.

  2. I fall at the first problem about SecurityException when writing my FujiyBlog. I thought EF 4.3 didn´t support Medium Trust, but I realized that was fault of my internal property.

    • EF 4.3/5.0 supports medium trust to the same degree as previous versions of EF–i.e. with the limitation on mapping to non-public members. However this is something we think can be fixed. In a couple of months I may be able to tell you whether or not we were right tinking that!

  3. Tina says:

    This works great if the non-public field is a string. Does this work if it is an ICollection? I tried the following:
    public class Person
    {
    :
    protected virtual ICollection privateCollection { get; set; }
    public static readonly Expression<Func<Person, ICollection>> privateCollectionExpression = p => p.privateCollection;
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
    modelBuilder.Entity().Property(Person.privateCollectionExpression);
    :
    }
    Results in the following error:
    Error 1 The type ‘System.Collections.Generic.ICollection’ must be a non-nullable value type in order to use it as parameter ‘T’ in the generic type or method ‘System.Data.Entity.ModelConfiguration.Configuration.StructuralTypeConfiguration.Property(System.Linq.Expressions.Expression<System.Func>)’ D:\samples\InternetSpecial_Nonpublic\DataAccess\DataAccess.cs 27 12 DataAccess

    Thanks in advance.

    • Yes, you can use it for collection navigation properties. However you should use HasMany and its related methods to let Code First know about navigation properties in relationships. The Property method is only used for scalar properties. Also, EF only supports generic ICollection for navigation properties–you can’t use the non-generic ICollection interface.

      • Tina says:

        Thanks so much! That worked (also had to change ICollection which brings me to the next (unrelated) question. Does EF code first handle collection of primitives (i.e. string[] or ICollection? I’ve read about various workarounds for this problem. None of which are very elegant. Thanks again.

      • As of EF5 EF doesn’t support collections of primitive types.

      • alexsandro_xpt says:

        I can’t do work it with ICollection of Entity( generic )
        @Tina, do you get?

  4. alexsandro_xpt says:

    And about EF5? doesn’t suport yet?

  5. Andy Wyatt says:

    Just spent the last few days learning code first EF and it’s thinks like not being able to easily change access modifiers on properties that make you go hummm… Letting the whole world change collections with out consideration of business concerns is painful. Hate the way that the Add method comes up on intellisence on all mapped collections. Sure wish we could make them private and expose public filtered get properties based on these collections.

  6. @Andy Wyatt You can make properties private, although as this post covers, you do have to do some additional work to map them once they are private. We also have planned enhancements to make the object mapping more flexible.

  7. Petr says:

    The expression trick if used with ICollection is incompatible with lazy loading. Collection elements are not loaded because an incorrect SQL query is generated..

    private ICollection ClientsData { get; set; }

    public ICollection Clients
    {
    get
    {
    return new ReadOnlyCollection(ClientsData.ToList()); // .NET 4.0 ReadOnlyCollection tragedy
    }
    }

    public static readonly Expression<Func<Server, ICollection>> ClientsDataExpression = p => p.ClientsData;

    System.Data.EntityCommandExecutionException: An error occurred while executing the command definition. See the inner exception for details. —> System.Data.SqlClient.SqlException: Invalid column name ‘Server_HiId’. Invalid column name ‘Server_LoId’
    Result StackTrace:
    at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
    at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
    at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
    at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
    at System.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
    at System.Data.SqlClient.SqlDataReader.get_MetaData()
    at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
    at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite)
    at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean asyncWrite)
    at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
    at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
    at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
    at System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior)
    at System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior)
    — End of inner exception stack trace —
    at System.Data.EntityClient.EntityCommandDefinition.ExecuteStoreCommands(EntityCommand entityCommand, CommandBehavior behavior)
    at System.Data.Objects.Internal.ObjectQueryExecutionPlan.Execute[TResultType](ObjectContext context, ObjectParameterCollection parameterValues)
    at System.Data.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
    at System.Data.Objects.ObjectQuery`1.Execute(MergeOption mergeOption)
    at System.Data.Objects.DataClasses.EntityReference`1.Load(MergeOption mergeOption)
    at System.Data.Objects.DataClasses.RelatedEnd.Load()
    at System.Data.Objects.DataClasses.RelatedEnd.DeferredLoad()
    at System.Data.Objects.Internal.LazyLoadBehavior.LoadProperty[TItem](TItem propertyValue, String relationshipName, String targetRoleName, Boolean mustBeNull, Object wrapperObject)
    at System.Data.Objects.Internal.LazyLoadBehavior.c__DisplayClass7`2.b__2(TProxy proxy, TItem item)
    at System.Data.Entity.DynamicProxies.Account_ABFA0D194F42E4F2F36B04E4851E96A798374A3F4EB5B62507EA92766372CBBB.get_Client()

    • Lazy loading is implemented by creating dynamic proxy classes at runtime that override navigation properties to inject the loading behavior. If your navigation property getter is private (or internal or not virtual) then there is no way it can be overridden in the dynamic assembly and hence lazy loading is not supported. This is true regardless of how the model is configured in the fluent API or elsewhere.

      Thanks,
      Arthur

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s