2
votes

I am working on an MVC 4 (C#) project in Visual Studio 2012. I am using the Entity Framework which utilizes an *.edmx file to model an existing database. My goal is to implement database auditing to track any changes being made to the database. Classes are generated automatically with the fields from the database tables - pretty standard stuff, I believe.

I am running into a problem when attempting to retrieve the primary key of these generated classes. The Entity Framework does not assign the [Key] attribute to the class properties. I attempted to solve this by extending the partial classes that were created and assigning this attribute myself. Adding it directly to the generated class works fine. If I try the "buddy class" method, I get the following error later on in my code execution: "Entity {Class Name} has no [Key] attribute."

Auto-Generated class:

public partial class List
{
   public List()
   {
       this.Tasks = new HashSet<Task>();
   }

   public int Id { get; set; }
   public string ListName { get; set; }
}

Extension (buddy) class:

[MetadataType(typeof(ListMetaData))]
public partial class List
{

    private class ListMetaData 
    {
        [Key]
        public int Id { get; set; }

    }

}

Code snippet:

var property = dbEntry.Entity.GetType().GetProperties().FirstOrDefault(
   p => p.GetCustomAttributes(typeof(KeyAttribute), true).Any());

if (property == null)
    throw new InvalidOperationException(string.Format(
    "Entity {0} has no [Key] attribute.", dbEntry.Entity.GetType().Name));

string keyName = property.Name; 

I am overriding the SaveChanges() method in DbContext to track the changes being made. The snippet above shows a portion of code from a function that examines a DbEntityEntry object (dbEntry) to get the primary key.

5

5 Answers

1
votes

Is the List class the Entity Framework class or the ListMetaData class? If it's the List class, the Id property in that class does not have any attributes at all. The Id property of the nested ListMetaData class has the attribute, which is a totally different property.

If you intend the ListMetaData class to have the attribute, I'd add a table for that class to the model (if there isn't one already), un-nest the class and make it public.

1
votes

Thanks for the suggestions everyone. What I ended up doing is modifying the *.tt template to automatically add the [Key] Attribute and the System.ComponentModel.DataAnnotations Directive to dynamically generated class files.

0
votes

There's a few things you could check.

  1. Is your partial class in the same namespace as your entity class?

  2. Is your partial class in the same project?

  3. Try changing the access level on your ListMetaData class to public or internal and maybe un-nest it?

0
votes

I worked out something like this, hope it helps and is what you are after:

public string[] GetKeyNames<TEntity>() where TEntity : class
{
    var set = ((IObjectContextAdapter)this).ObjectContext.CreateObjectSet<TEntity>();
    var entitySet = set.EntitySet;
    return entitySet.ElementType.KeyMembers.Select(k => k.Name).ToArray();
}

I've created it as a public method on the DBContext itself. You'd then call it like this:

var keys = context.GetKeyNames<List>();

Used this as inspiration.

You shouldn't need the metadata class for identifying the key - that information will be stored in the edmx file. You usually use the metadata for adding validation such as [Required] etc.

0
votes

With EF 6.1, you can also create an extension methods to get the primary key. Following is the example and works perfectly fine.

PS: I am not 100% sure, if that would work with composite and compound keys tho.

using System;
using System.Data.Entity;
using System.Data.Entity.Core.Metadata.Edm;
using System.Data.Entity.Infrastructure;
using System.Linq;

namespace System.Data.Entity
{
    public static class DbContextExtensions
    {
        public static string[] GetKeyNames<TEntity>(this DbContext context)
            where TEntity : class
        {
            return context.GetKeyNames(typeof(TEntity));
        }

        public static string[] GetKeyNames(this DbContext context, Type entityType)
        {
            var metadata = ((IObjectContextAdapter)context).ObjectContext.MetadataWorkspace;

            // Get the mapping between CLR types and metadata OSpace
            var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace));

            // Get metadata for given CLR type
            var entityMetadata = metadata
                    .GetItems<EntityType>(DataSpace.OSpace)
                    .Single(e => objectItemCollection.GetClrType(e) == entityType);

            return entityMetadata.KeyProperties.Select(p => p.Name).ToArray();
        }
    }
}

Original Source