1

I am using EF5 and I have some entities that I wrote myself and also wrote a function that adds all the mappings to the modelbuilder configurations.

Running a simple test query, I can query items from a table successfully but when I try to add a new item and save, I get an exception that the primary key of my entity is null, even though I gave it a value.

It's quite possible that I messed up the mappings, but I don't know why it would work for a query and not a save.

public class User : IMappedEntity 
{
    [Key]
    [Column("USER_ID")]
    public int UserID { get; set; }

    [Column("FIRST_NAME")]
    public String FirstName { get; set; }

    [Column("LAST_NAME")]
    public String LastName { get; set; }

}

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
        modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.PluralizingEntitySetNameConvention>();
        modelBuilder.Conventions.Remove<System.Data.Entity.ModelConfiguration.Conventions.PluralizingTableNameConvention>();

    var addMethod = (from m in (modelBuilder.Configurations).GetType().GetMethods()
                     where m.Name == "Add" 
                        && m.GetParameters().Count() == 1
                        && m.GetParameters()[0].ParameterType.Name == typeof(EntityTypeConfiguration<>).Name
                     select m).First();

    if(mappings != null)
    {
        foreach(var map in mappings)
        {
            if(map != null && !mappedTypes.Contains(map.GetType()))
            {
                var thisType = map.GetType();

                if (thisType.IsGenericType)
                {
                    thisType = map.GetType().GenericTypeArguments[0];
                }

                var thisAddMethod = addMethod.MakeGenericMethod(new[] {thisType});
                thisAddMethod.Invoke(modelBuilder.Configurations, new[] { map });
                mappedTypes.Add(map.GetType());
            }
        }
    }
}

private List<Object> BuildMappings(IEnumerable<Type> types)
{
    List<Object> results = new List<Object>();

    var pkType = typeof(KeyAttribute);
    var dbGenType = typeof(DatabaseGeneratedAttribute);

    foreach (Type t in types)
    {
        String tableName = GetTableName(t);
        String schemaName = GetSchema(t);
        var mappingType = typeof(EntityTypeConfiguration<>).MakeGenericType(t);
        dynamic mapping = Activator.CreateInstance(mappingType);

        if (!String.IsNullOrWhiteSpace(schemaName))
           mapping.ToTable(tableName, SchemaName.ToUpper());
        else
           mapping.ToTable(tableName);

        var keys = new List<PropertyInfo>();

        foreach (PropertyInfo prop in t.GetProperties())
        {
            String columnName = prop.Name;

            if(Attribute.IsDefined(prop, typeof(ColumnAttribute)))
            {
                columnName  = (prop.GetCustomAttribute(typeof(ColumnAttribute)) as ColumnAttribute).Name;
            }

            if(Attribute.IsDefined(prop, pkType))
               keys.Add(prop);

            var genFunc = (typeof(Func<,>)).MakeGenericType(t, prop.PropertyType);
            var param = Expression.Parameter(t, "t");
            var body = Expression.PropertyOrField(param, prop.Name);
            dynamic lambda = Expression.Lambda(genFunc, body, new ParameterExpression[] { param });

            //if (prop.PropertyType == typeof(Guid) || prop.PropertyType == typeof(Nullable<Guid>))
            //{
            //    mapping.Property(lambda).HasColumnType("Guid");
            //}
            //else
            mapping.Property(lambda).HasColumnName(columnName);

            if (Attribute.IsDefined(prop, dbGenType))
               mapping.Property(lambda).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Computed);
        }

        if (keys.Count == 0)
           throw new InvalidOperationException("Entity must have a primary key");
        dynamic entKey = null;

        if(keys.Count == 1)
        {
            var genFunc = (typeof(Func<,>)).MakeGenericType(t, keys[0].PropertyType);
            var param = Expression.Parameter(t, "t");
            var body = Expression.PropertyOrField(param, keys[0].Name);
            entKey = Expression.Lambda(genFunc, body, new ParameterExpression[] { param });
        }
        else
        {
            //if entity uses a compound key, it must have a function named "GetPrimaryKey()" which returns Expression<Func<EntityType,Object>>
            //this is because I can't create an expression tree that creates an anonymous type
            entKey = t.GetMethod("GetPrimaryKey");
        }

        mapping.HasKey(entKey);

        results.Add(mapping);
    }

    return results;
}

static void Main(string[] args)
{
    using (var ctx = new DQSA.Data.DBContext("DQSATEST"))
    {
        var xxx = (from u in ctx.Query<DQSA.Data.Entities.User>()
                   select u).ToList(); //this works, I can see my user

        ctx.Set<DQSA.Data.Entities.User>().Add(new DQSA.Data.Entities.User()
            { UserID = 0,
              FirstName="Sam",
              LastName="Sam"
            });

        ctx.SaveChanges(); //get an exception here

        xxx = (from u in ctx.Query<DQSA.Data.Entities.User>()
               select u).ToList();
    }
}
12
  • Can you try a simplified test method? Just create a new User and try to add it, then save the context? Commented Aug 13, 2015 at 17:54
  • 1
    Is your User_ID an identity column? Commented Aug 13, 2015 at 18:10
  • 1
    I would debug that whole dynamic section. See where the data is final before saving, and check User_ID. Walk backwards from there. Also, try to set User_ID to 1, instead of 0. Commented Aug 13, 2015 at 18:21
  • 1
    My theory is that your UserID property is being mapped as an Identity column (even though that's not what you want) so the EF query provider thinks it doesn't need to insert that value and the database complains because the field is not nullable. Out of curiousity, why do all that dynamic mapping registration? Commented Aug 13, 2015 at 18:33
  • 1
    Peter, you were right. I removed the Identify mapping convention on the dbmodelbuilder and it worked correctly. Thanks! Commented Aug 13, 2015 at 18:41

3 Answers 3

2

It looks like your UserID property is being mapped as an Identity column by convention so the EF query provider thinks it doesn't need to insert that value and the database complains because the field is not nullable.

You can override the convention in your model by using the DatabaseGeneratedAttribute ...

public class User : IMappedEntity 
{
    [Key]
    [Column("USER_ID")]
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int UserID { get; set; }

    ...
}

or by removing the convention globally (in your DbContext's OnModelCreating() method) ...

modelBuilder.Conventions.Remove<StoreGeneratedIdentityKeyConvention>();
Sign up to request clarification or add additional context in comments.

Comments

1

I think you need to try to seed with one or few records then context.SaveChanges()

1 Comment

That's the problem. I have a single record in the database and I can read it just fine. I try to add a new User and it throws an exception when I save changes. It says the User_ID cannot be null.
0

by default, entity framework should mark the primary key column of a new table created with Code First as an identity column. Did the database exist prior to your code, or did you use code first to create it?

Can you verify in Management Studio that the column has identity turned on for that field?

1 Comment

I created the table with a statement. And I did verify that it is not an identity column.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.