3

I am in the process of creating a domain model and would like to have a "BaseEntity" class with an "Id" property (and some other audit tracking stuff). The Id property is the primary key and each Entity in my Domain Model will inherit from the BaseEntity class. Pretty straightforward stuff.....

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

    public DateTime LastUpdate { get; set; }
    public string LastUpdateBy { get; set; }
}
public class Location : BaseEntity
{
    [Required]
    public string Name { get; set; }

    public string Description { get; set; }
}

Using the example above, I would like to map the "Id" field to a "LocationId" column. I understand that I can use the modelBuilder to do this for each entity explicitly by doing something like this:

modelBuilder.Entity<Location>().Property(s => s.Id).HasColumnName("LocationId");

But I would like to do this for every Entity in my domain model and it would be ugly.

I tried the following bit of reflection but did not have any luck. For whatever reason, the compiler "cannot resolve symbol type":

foreach (var type in GetTypesInNamespace(Assembly.Load("Domain.Model"),"Domain.Model"))
{
    modelBuilder.Entity<type>().Property(x=>x.Id).....
}

Is there a way to define a convention to override the default PrimaryKey convention to map my "Id" property to a "ClassNameId" property in the database? I am using Entity Framework 6.

gunr2171
  • 16,104
  • 25
  • 61
  • 88
lafferrs
  • 33
  • 1
  • 5
  • Is there a reason you need to use EF6 (which is still in Alpha) rather than EF5? – gunr2171 May 07 '13 at 16:45
  • No particular reason, no. This is just a new project and I am experimenting with EF6. Can you do what I am trying to do in EF5? – lafferrs May 07 '13 at 16:56
  • I would suggest using EF5 because it's stable. However, you should read [this page](http://entityframework.codeplex.com/wikipage?title=specs) to see if what you are trying to do is only accomplished by EF6. – gunr2171 May 07 '13 at 17:18
  • there's http://msdn.microsoft.com/en-us/library/system.data.linq.mapping.columnattribute.aspx but I don't know if it will get inherited ... – qujck May 07 '13 at 17:26
  • 2
    sounds like a job for [Custom Code First Conventions!](http://msdn.microsoft.com/en-us/data/jj819164.aspx) – Aron May 07 '13 at 17:47
  • @Aron - I tried doing this using the following lightweight convention: `modelBuilder.Properties().Where(p => p.Name.Equals("Id")) .Configure(p => p.HasColumnName(p.ClrPropertyInfo.DeclaringType.ToString() + "Id")); ` but that just lead to a column named after my base entity type. I also tried to use a configuration convention but couldn't figure it out. – lafferrs May 08 '13 at 13:16
  • Oh dear. I am very sorry if Custom Code First Conventions isn't working out for you. As EF6 (and hence CCFC is still alpha) I suggest you give the EF boys and girls a shout about it. – Aron May 08 '13 at 14:30

5 Answers5

2

You should take a look at Custom Code First Conventions. You need EF6 for it to work, but it looks like you're already using it.
Just to give you an overview, take a look at the following convention I've used to convert PascalCase names to underscore names. It includes a convention for id properties... It also includes an optional table name prefix.

public class UnderscoreNamingConvention : IConfigurationConvention<PropertyInfo, PrimitivePropertyConfiguration>,
                                          IConfigurationConvention<Type, ModelConfiguration>
{
    public UnderscoreNamingConvention()
    {
        IdFieldName = "Id";
    }

    public string TableNamePrefix { get; set; }

    public string IdFieldName { get; set; }

    public void Apply(PropertyInfo propertyInfo, Func<PrimitivePropertyConfiguration> configuration)
    {
        var columnName = propertyInfo.Name;

        if (propertyInfo.Name == IdFieldName)
            columnName = propertyInfo.ReflectedType.Name + IdFieldName;

        configuration().ColumnName = ToUnderscore(columnName);
    }

    public void Apply(Type type, Func<ModelConfiguration> configuration)
    {
        var entityTypeConfiguration = configuration().Entity(type);
        if (entityTypeConfiguration.IsTableNameConfigured) return;

        var tableName = ToUnderscore(type.Name);

        if (!string.IsNullOrEmpty(TableNamePrefix))
        {
            tableName = string.Format("{0}_{1}", TableNamePrefix, tableName);
        }

        entityTypeConfiguration.ToTable(tableName);
    }

    public static string ToUnderscore(string value)
    {
        return Regex.Replace(value, "(\\B[A-Z])", "_$1").ToLowerInvariant();
    }
}

You use it like this

modelBuilder.Conventions.Add(new UnderscoreNamingConvention { TableNamePrefix = "app" });

EDIT: In your case, the Apply method should be something like this:

public void Apply(PropertyInfo propertyInfo, Func<PrimitivePropertyConfiguration> configuration)
{
    if (propertyInfo.Name == "Id")
    {
        configuration().ColumnName = propertyInfo.ReflectedType.Name + "Id";
    }
}
khellang
  • 17,550
  • 6
  • 64
  • 84
1

Try this out in your DbContext class;

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Properties<int>()
      .Where(p => p.Name.Equals("Id"))
      .Configure(c => c.HasColumnName(c.ClrPropertyInfo.ReflectedType.Name + "Id"));
}

int is the CLR Type of my Primary Key fields. I want to refer to all keys in code as Id but DBA's require keys to be Id with Table entity name prefix. Above gives me exactly what I want in my created database.

Entity Framework 6.x is required.

Monty0018
  • 193
  • 11
1

In Entity Framework 6 Code First:

modelBuilder.Entity<roles>().Property(b => b.id).HasColumnName("role_id");

and update-database...

Change in model

[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long id { get; set; }

to:

[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public long role_id { get; set; }

Then remove this:

//modelBuilder.Entity<roles>().Property(b => b.id).HasColumnName("role_id");
0

A start to the Dynamic approach if NOT using custom conventions

 modelBuilder.Entity<Location>().Property(s => s.Id).HasColumnName("LocationId");

You can do this using reflection on the context. Pseudo Code as explanation:

Reflect Context to get a list of POCO names
For each POCO in a dbcontext.
Map Property Id -> string PocoName+Id

Here are the extensions I use for this type of solution.

    // DBSet Types is the Generic Types POCO name  used for a DBSet
    public static List<string> GetModelTypes(this DbContext context) {
        var propList = context.GetType().GetProperties();
        return GetDbSetTypes(propList);
    }

    // DBSet Types POCO types as IEnumerable List
    public static IEnumerable<Type> GetDbSetPropertyList<T>() where T : DbContext {
        return typeof (T).GetProperties().Where(p => p.PropertyType.GetTypeInfo()
                                                      .Name.StartsWith("DbSet"))
                         .Select(propertyInfo => propertyInfo.PropertyType.GetGenericArguments()[0]).ToList();
    }


   private static List<string> GetDbSetTypes(IEnumerable<PropertyInfo> propList) {
        var modelTypeNames = propList.Where(p => p.PropertyType.GetTypeInfo().Name.StartsWith("DbSet"))
                                     .Select(p => p.PropertyType.GenericTypeArguments[0].Name)
                                     .ToList();
        return modelTypeNames;
    }

    private static List<string> GetDbSetNames(IEnumerable<PropertyInfo> propList) {
        var modelNames = propList.Where(p => p.PropertyType.GetTypeInfo().Name.StartsWith("DbSet"))
                                 .Select(p => p.Name)
                                 .ToList();

        return modelNames;
    }

However, you will still need to employee dynamic lambda to finish. Continue that topic here: Dynamic lambda example with EF scenario

EDIT: Add link to another question that address the common BAse Config class approach Abstract domain model base class when using EntityTypeConfiguration<T>

Community
  • 1
  • 1
phil soady
  • 11,043
  • 5
  • 50
  • 95
  • Hi, Thanks for your response. I did try something like you suggest, but the problem is that no matter how I generate the list of types, I cannot use them in the `modelBuilder.Entity().Property()` call. It seems that I cannot use the reflected type generically. I did look at [this stackoverflow article](http://stackoverflow.com/questions/955331/creating-a-generic-object-based-on-a-type-variable), but we cannot do that because we'd have to cast from object to the generic type in order to call the "Property()" method. – lafferrs May 08 '13 at 13:14
  • It is possible with dynamic lambda, the extensions are to help reflect on Context. – phil soady Jun 01 '13 at 12:08
0

Piggybacking on @Monty0018 's answer but this just need to be updated a little if, like me, you're using Entity Framework 7 and/or SQLite.

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
        try
        {
            _builder = modelBuilder;
            var typeName = typeof(T).Name;

            _builder
                .Entity(typeof(T))
                .Property<int>("Id")
                .ForSqliteHasColumnName(typeName + "Id");
        }

        catch (Exception e)
        {
            throw e;
        }
}
prestonsmith
  • 758
  • 1
  • 9
  • 29