EF6: Code First Complex Type

Joe picture Joe · Feb 23, 2014 · Viewed 13.6k times · Source

I'm having trouble getting entity framework to flatten my domain entity classes with Value Objects (complex type) fields to one table.

Everything works if I tell my model builder to ignore my value objects/complex type, but that results in all the attributes of the value object being missed in my tables. As soon as I remove the ignore statement i get "A value shared across entities is created in more than one location". If I look in the resulting CE SQL file I see an additional table named after my Domain class appended with a 1 and containing only the Value Object parameters.

Some Code:

My domain Classes:

public User {

    private User(){}
    public long Id {get; private set;} // dont ask, inherited legacy database
    public string UserId { get; private set; }
    public string Domain { get; private set; }
    public AuditIformation AuditDetails {get ; private set;}

    //..domain logic etc
}

public AuditInformation : IValueObject {
    public long CreatedByUserId { get; private set; }
    public DateTime CreatedDate { get; private set; }
} 

My repository project (going code first) has got this:

public partial class myContext : DbContext {

    protected override void OnModelCreating(DbModelBuilder mb) {

        mb.Conventions.Remove<PluralizingTableNameConvention>(); 

        mb.ComplexType<Domain.Model.AuditInformation>();
        mb.ComplexType<Domain.Model.AuditInformation>().Property(a => a.CreatedDate).HasColumnName("Created_On");
        mb.ComplexType<Domain.Model.AuditInformation>().Property(a => a.CreatedByUserId).HasColumnName("Created_By");

        //This line lets everything work but doesn't include my 
        //AuditInformation attributes in my User Table.
        mb.Ignore<Domain.Model.AuditInformation>(); // <== I think I need to remove this

        //..

        mb.Entity<User>().Map(a => {
            a.Property(x => x.Id).HasColumnName("Id");
            a.Property(x => x.UserId).HasColumnName("User_Id");
            a.Property(x => x.Domain).HasColumnName("User_Dmain");
            })
        .HasKey(x => x.Id)
        .ToTable("Tbl_User");   //<==Again, dont ask

  }
}

What I want to get is a table looking like:

[TBL_USER] 
ID AS BIGINT,
USER_ID as VARCHAR(MAX),
USER_DMAIN AS VARCHAR(MAX),
CREATED_ON as DATE,
CREATED_BY as BIGINT

But what im getting is only:

[TBL_USER] 
ID AS BIGINT,
USER_ID as VARCHAR(MAX),
USER_DMAIN AS VARCHAR(MAX),

and if I remove the ignore line i get this bonus freak table

[USER1]  <<==Note, named after the domain class, not the destination table.. 
ID AS BIGINT,
CREATED_ON as DATE,
CREATED_BY as BIGINT

and a whole bunch of error when I try to use my repository:

----> System.Data.Entity.Infrastructure.DbUpdateException : A value shared across entities or associations is generated in more than one location. Check that mapping does not split an EntityKey to multiple store-generated columns.
----> System.Data.Entity.Core.UpdateException : A value shared across entities or associations is generated in more than one location. Check that mapping does not split an EntityKey to multiple store-generated columns.
----> System.ArgumentException : An item with the same key has already been added.
TearDown : System.NullReferenceException : Object reference not set to an instance of an object.

Ive done a lot of searching but I just cant find any concrete examples of persisting my value object attributes into the tables created for my domain objects. Can someone show me where I'm going wrong?

Answer

Darius Iko picture Darius Iko · Feb 23, 2014

Try this:

public class AuditInformation
{
    public long CreatedByUserId { get; set; }
    public DateTime CreatedDate { get; set; }
}

public abstract class AuditInfo
{
    public AuditInformation AuditDetails { get; set; }

    public AuditInfo()
    {
        this.AuditDetails = new AuditInformation();
        this.AuditDetails.CreatedByUserId = 0;
        this.AuditDetails.CreatedDate = DateTime.Now;
    }
}

public User : AuditInfo
{
    private User(){}
    public long Id {get; private set;} // dont ask, inherited legacy database
    public string UserId { get; private set; }
    public string Domain { get; private set; }

    //..domain logic etc
}

public partial class myContext : DbContext 
{
    public DbSet<User> Users { get; set; }

    protected override void OnModelCreating(DbModelBuilder mb)
    {
        mb.Conventions.Remove<PluralizingTableNameConvention>();

        mb.ComplexType<Domain.Model.AuditInformation>();
        mb.ComplexType<Domain.Model.AuditInformation>().Property(a => a.CreatedDate).HasColumnName("Created_On");
        mb.ComplexType<Domain.Model.AuditInformation>().Property(a => a.CreatedByUserId).HasColumnName("Created_By");


        mb.Entity<Cricketer>().Map(a =>
        {
            a.Property(x => x.Id).HasColumnName("Id");
            a.Property(x => x.UserId).HasColumnName("User_Id");
            a.Property(x => x.Domain).HasColumnName("User_Dmain");
            a.Property(x => x.AuditDetails.CreatedByUserId).HasColumnName("CreatedByUserId");
            a.Property(x => x.AuditDetails.CreatedDate).HasColumnName("CreatedDate");
        })
        .HasKey(x => x.ID)
        .ToTable("Tbl_User");   //<==Again, dont ask
    }
}