Pregunta

Estoy usando Entity Framework 4.3.1 Code-First y necesito dividir una entidad entre dos tablas.Las tablas tienen una clave principal compartida y es 1 a 1, pero las columnas no tienen el mismo nombre en cada tabla.

No controlo el diseño de los datos ni puedo solicitar ningún cambio.

Así, por ejemplo, las tablas SQL podrían ser

SQL data tables

Y esta sería mi entidad...

public class MyEntity
{
    public int Id {get; set;}
    public string Name {get;set}
    public string FromAnotherTable {get;set;}
}

Y aquí está el mapeo que tengo.

public class MyEntityMapping : EntityTypeConfiguration<MyEntity>
{
    public MyEntityMapping()
    {
        this.Property(e => e.Id).HasColumnName("ThePrimaryKeyId");
        this.Property(e => e.Name).HasColumnName("MyDatabaseName");
        this.Property(e => e.FromAnothertable).HasColumnName("AnotherTableColumn");
        this.Map(m =>
            {
                m.Properties(e =>
                     {
                         e.Id,
                         e.Name
                     });
                m.ToTable("MainTable");
            });
        this.Map(m =>
            {
                m.Properties(e =>
                     {
                         e.Id,
                         e.FromAnotherTable
                     });
                m.ToTable("ExtendedTable");
            });
}

Dado que la clave compartida entre ellos tiene un nombre de columna diferente, no estoy seguro de cómo asignarla.Esta asignación se compilará, pero falla en tiempo de ejecución porque EF emite SQL buscando la columna "ThePrimaryKeyId" en la tabla "ExtendedTable", que no existe.

EDITARPara aclarar, lo que he definido anteriormente puede (y funciona) funcionar si la PK en "ExtendedTable" siguió las convenciones de nomenclatura.Pero no es así y no puedo cambiar el esquema.

Básicamente, lo que necesito que EF emita es una declaración SQL como

SELECT
    [e1].*,   /*yes, wildcards are bad. doing it here for brevity*/
    [e2].*
FROM [MainTable] AS [e1]
INNER JOIN [ExtendedTable] AS [e2]  /*Could be left join, don't care. */
    ON  [e1].[ThePrimaryKeyId] = [e2].[NotTheSameName]

Pero lo único que parece querer emitir es

 SELECT
        [e1].*,
        [e2].*
    FROM [MainTable] AS [e1]
    INNER JOIN [ExtendedTable] AS [e2]
        ON  [e1].[ThePrimaryKeyId] = [e2].[ThePrimaryKeyId] /* this column doesn't exist */

EditarProbé nuevamente el enfoque 1 a 1 por sugerencia de NSGaga.No funcionó, pero aquí están los resultados.Entidades

public class MyEntity
{
    public int Id { get; set; }
    public int Name { get; set; }
    public virtual ExtEntity ExtendedProperties { get; set; }
}
public class ExtEntity
{
    public int Id { get; set; }
    public string AnotherTableColumn { get; set; }
    public virtual MyEntity MainEntry { get; set; }
}

Aquí están las clases de mapeo.

public class MyEntityMapping : EntityTypeConfiguration<MyEntity>
{
    public MyEntityMapping()
    {
        this.Property(e => e.Id).HasColumnName("ThePrimaryKeyId");
        this.Property(e => e.Name).HasColumnName("MyDatabaseName");
        this.ToTable("MainTable");
        this.HasKey(e => e.Id);
        this.HasRequired(e => e.ExtendedProperties).WithRequiredPrincipal(f => f.MainEntry);
    }
}

public class ExtEntityMapping : EntityTypeConfiguration<ExtEntity>
{
    public ExtEntityMapping()
    {
        this.Property(e => e.Id).HasColumnName("NotTheSameName");
        this.Property(e => e.AnotherTableColumn).HasColumnName("AnotherTableColumn");
        this.ToTable("ExtendedTable");
        this.HasKey(e => e.Id);
        this.HasRequired(e => e.MainEntry).WithRequiredDependent(f => f.ExtendedProperties);
    }
}

Esta configuración recibe el mensaje

"Column or attribute 'MyEntity_ThePrimaryKeyId' is not defined in 'ExtendedTable'"

Cambiando la línea final del mapa a

this.HasRequired(e => e.MainEntry).WithRequiredDependent(f => f.ExtendedProperties).Map(m => M.MapKey("NotTheSameName"));

Devuelve este mensaje

"Each property name in a type must be unique. property name 'NotTheSameName' was already defined."

Cambiar la clave asignada para usar la columna de la tabla principal, MapKey("ThePrimaryKeyId").devuelve este mensaje

"Column or attribute 'ThePrimaryKeyId' is not defined in 'ExtendedTable'"

La eliminación de la Id propiedad de la ExtEntity la clase arroja un error porque entonces la entidad no tiene una clave definida.

¿Fue útil?

Solución

No puedo encontrar nada que indique específicamente que el nombre de la columna debe ser el mismo en ambas tablas;pero tampoco puedo encontrar nada que diga que no, o que explique cómo mapearía ese escenario.Cada ejemplo que puedo encontrar tiene la clave con el mismo nombre en ambas tablas.Me parece que esto es un agujero en el diseño de DbContext.

Otros consejos

He estado trabajando en este mismo problema durante unos días, lo que finalmente hice fue establecer el nombre de la columna del campo Id dentro del contexto del fragmento de mapeo.De esta manera, puede darle al Id (o la clave externa que depende del Id) un nombre diferente del Id de la tabla principal.

this.Map(m =>
    {
        m.Property(p => p.Id).HasColumnName("NotTheSameName");
        m.Properties(e =>
             {
                 e.Id,
                 e.FromAnotherTable
             });
        m.ToTable("ExtendedTable");
    });

Si ejecuta y depura esto, encontrará que le dará algo como lo que desea:

[e1].[ThePrimaryKeyId] = [e2].[NotTheSameName]

Mueva HasColumnName dentro de la asignación:

this.Property(e => e.FromAnothertable).HasColumnName("AnotherTableColumn");
this.Map(m =>
    {
        m.Properties(e => new
             {
                 e.Id,
                 e.Name
             });
             m.Property(e => e.Id).HasColumnName("ThePrimaryKeyId");
             m.Property(e => e.Name).HasColumnName("MyDatabaseName");

           m.Property(e => e.Id).HasColumnName("ThePrimaryKeyId");
        m.ToTable("MainTable");
    });
this.Map(m =>
    {
        m.Properties(e => new
             {
                 e.Id,
                 e.FromAnotherTable
             });
        m.ToTable("ExtendedTable");
    });
}

Aquí no hay Visual Studio, pero intente esto con el enfoque 1 a 1:

this.HasRequired (e=> e.ExtendedProperties) .HasConstraint ((e, m)=> e.Id== m.Id);

Actualización:
Aquí hay algunos enlaces que pueden ayudar (no se pudo encontrar un enlace de referencia real)

Cómopara declarar una relación uno a uno utilizando Entity Framework 4 Code First (POCO)
Entity Framework 4 CTP4 Code First: cómo trabajar con nombres de claves externas y primarias no convencionales

Y solo para proporcionar (como prometí) un mapeo 1 a 1 (dos entidades, dos tablas), por si sirve de algo.
Esto es lo que funciona para mí y debería hacerlo en su caso...

public class MainTable
{
    public int ThePrimaryKeyId { get; set; }
    public string Name { get; set; }
}
public class ExtendedTable
{
    public int NotTheSameNameID { get; set; }
    public string AnotherTableColumn { get; set; }
    public MainTable MainEntry { get; set; }
}
public class MainDbContext : DbContext
{
    public DbSet<MainTable> MainEntries { get; set; }
    public DbSet<ExtendedTable> ExtendedEntries { get; set; }
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<MainTable>()
            .HasKey(x => new { x.ThePrimaryKeyId });

        modelBuilder.Entity<ExtendedTable>()
            .HasKey(x => new { x.NotTheSameNameID });

        // Extended To Main 1 on 1
        modelBuilder.Entity<ExtendedTable>()
            .HasRequired(i => i.MainEntry)
            .WithRequiredDependent();
    }
}

...y un código de prueba algo así como...

using (var db = new UserDbContext())
{
    foreach (var userid in Enumerable.Range(1, 100))
    {
        var main = new MainTable { Name = "Main" + userid };
        db.MainEntries.Add(main);

        var extended = new ExtendedTable { AnotherTableColumn = "Extended" + userid, MainEntry = main };
        db.ExtendedEntries.Add(extended);
    }
    int recordsAffected = db.SaveChanges();
    foreach (var main in db.MainEntries)
        Console.WriteLine("{0}, {1}", main.Name, main.ThePrimaryKeyId);
    foreach (var extended in db.ExtendedEntries)
        Console.WriteLine("{0}, {1}, {2}, {3}", extended.AnotherTableColumn, extended.NotTheSameNameID, extended.MainEntry.Name, extended.MainEntry.ThePrimaryKeyId);
}

Eso crea el siguiente script SQL, tablas...

CREATE TABLE [MainTables] (
    [ThePrimaryKeyId] [int] NOT NULL IDENTITY,
    [Name] [nvarchar](4000),
    CONSTRAINT [PK_MainTables] PRIMARY KEY ([ThePrimaryKeyId])
)
CREATE TABLE [ExtendedTables] (
    [NotTheSameNameID] [int] NOT NULL,
    [AnotherTableColumn] [nvarchar](4000),
    CONSTRAINT [PK_ExtendedTables] PRIMARY KEY ([NotTheSameNameID])
)
CREATE INDEX [IX_NotTheSameNameID] ON [ExtendedTables]([NotTheSameNameID])
ALTER TABLE [ExtendedTables] ADD CONSTRAINT [FK_ExtendedTables_MainTables_NotTheSameNameID] FOREIGN KEY ([NotTheSameNameID]) REFERENCES [MainTables] ([ThePrimaryKeyId])

Y una nota, según nuestra discusión anterior...
Esta no es la "división", pero
(a) el código primero, en mi opinión, no permite nada de eso (lo intenté primero y también modifiqué las migraciones manualmente, pero todo se basa "internamente" en que los nombres de las columnas esperados son los mismos y parece que no hay forma de evitarlo, por Esta versión de EF al menos.
(b) en cuanto a la estructura de la tabla: las tablas se podrían hacer para que se vean exactamente como usted necesita (como dije antes, lo usé para relacionar las tablas de membresía de aspnet existentes (que no pude cambiar) con mi tabla de usuarios, que tiene un usuario propio. -id apuntando a la tabla exterior/aspnet e id.
Es cierto que no puedes hacerlo usando una clase de modelo de C#, pero el lado de C# es mucho más flexible y si puedes controlar C#, eso debería dar el mismo efecto, al menos en mi opinión (como en la prueba, puedes acceder a él siempre). a través de la entidad extendida, tanto las columnas extendidas como las principales, y siempre coinciden 1 a 1 y permanecen "sincronizadas".
Espero que esto ayude a algunos.
NOTA:no tienes que preocuparte por la identificación fk, etc.- Siempre acceda y agregue la entrada principal a través de MainEntry, y los ID estarán bien.

EDITAR:
También podrías hacer lo siguiente para dar la apariencia de tener que tratar con una sola clase (es decir,una especie de división)

public class ExtendedTable
{
    public int NotTheSameNameID { get; set; }
    public string AnotherTableColumn { get; set; }

    public string Name { get { return MainEntry.Name; } set { MainEntry.Name = value; } }
    // public int MainID { get { return MainEntry.ThePrimaryKeyId; } set { MainEntry.ThePrimaryKeyId = value; } }
    internal MainTable MainEntry { get; set; }

    public ExtendedTable()
    {
        this.MainEntry = new MainTable();
    }
}

...y usarlo así...

var extended = new ExtendedTable { AnotherTableColumn = "Extended" + userid, Name = "Main" + userid };  

...también puedes revertir la dirección del fk haciendo lo siguiente WithRequiredPrincipal en lugar de dependiente.
(Además, todas las referencias deben ser sin 'virtuales' si las ha requerido individualmente)
(y MainTable se puede hacer 'interno' como está aquí, por lo que no es visible desde afuera; no se puede anidar porque EF no lo permite; se trata como NotMapped)
...bueno, eso es lo mejor que pude hacer :)

Parece que se ha solucionado en Entity Framework 6. Consulte este problema http://entityframework.codeplex.com/ workitem / 388

Me gustaría sugerir el uso de algunas anotaciones de datos como esta:

MainTable
---------
MainTableId
DatabaseName

ExtendedTable
----------
NotTheSameName
AnotherColumn

public class MainTable
{
 [Key]
 public int MainTableId { get; set; }
 public string DatabaseName { get; set; }

 [InverseProperty("MainTable")]
 public virtual ExtendedTable ExtendedTable { get; set; }
}

public class ExtendedTable
{
 [Key]
 public int NotTheSameName { get; set; }

 public string AnotherColumn { get; set; }

 [ForeignKey("NotTheSameName")]
 public virtual MainTable MainTable { get; set; }
}

Me enfrenté a este problema y lo resolví agregando el atributo Column para que coincida con los nombres de ambas columnas.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top