Pergunta

Estou usando o Entity Framework 4.3.1 Code-First e preciso dividir uma entidade entre duas tabelas. As tabelas têm uma chave primária compartilhada e é 1 para 1, mas as colunas não são nomeadas da mesma forma em cada tabela.

Não controlo o layout dos dados, nem posso solicitar alterações.

Então, por exemplo, as tabelas SQL podem ser

tabelas de dados SQL

E esta seria minha entidade ...

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

E aqui está o mapeamento que tenho.

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");
            });
}

Como a chave compartilhada entre eles tem um nome de coluna diferente, não tenho certeza de como mapeá-la. Este mapeamento irá compilar, mas falhará em tempo de execução porque EF emite SQL procurando pela coluna "ThePrimaryKeyId" na tabela "ExtendedTable", que não existe.

EDITAR Para esclarecer, o que defini acima pode (e funciona) se o PK na "ExtendedTable" seguir as convenções de nomenclatura. Mas isso não acontece e eu não posso mudar o esquema.

Basicamente, o que eu preciso que o EF emita é uma instrução 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]

Mas a única coisa que parece querer emitir é

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

Editar Tentei a abordagem 1 para 1 novamente por sugestão da NSGaga. Não funcionou, mas aqui estão os 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; }
}

Aqui estão as classes de mapeamento

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 configuração capta a mensagem

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

Alterando a linha final do mapa para

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

Retorna esta mensagem

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

Alterar a chave mapeada para usar a coluna da tabela pai, MapKey("ThePrimaryKeyId"). retorna esta mensagem

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

Remover a propriedade Id da classe ExtEntity gera um erro porque a entidade não tem uma chave definida.

Foi útil?

Solução

Não consigo encontrar nada que afirme especificamente que o nome da coluna deve ser o mesmo nas duas tabelas;mas também não consigo encontrar nada que diga que não, ou explique como você mapearia esse cenário.Todos os exemplos que encontro têm a chave com o mesmo nome nas duas tabelas.Parece-me que isso é uma lacuna no design do DbContext.

Outras dicas

Estou trabalhando nesse mesmo problema há alguns dias, o que finalmente fiz foi definir o nome da coluna do campo Id dentro do contexto do fragmento de mapeamento.Desta forma, você pode dar ao Id (ou à chave estrangeira dependente do Id) um nome diferente do Id da tabela principal.

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

Se você executasse e depurasse isso, descobriria que teria algo parecido com o que você deseja:

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

Mova HasColumnName para dentro do mapeamento:

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");
    });
}

Nenhum Visual Studio aqui, mas tente isso com a abordagem 1 para 1:

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

Atualização:
Aqui estão alguns links que podem ajudar (não foi possível encontrar um link de referência real)

Comopara declarar um para um relacionamento usando Entity Framework 4 Code First (POCO)
Entity Framework 4 CTP4 Codifique primeiro: como trabalhar com nomes de chave primária e estrangeira não convencionais

E apenas para fornecer (como prometi) um mapeamento 1 para 1 (duas entidades, duas tabelas), pelo que vale a pena.
Aqui está o que funciona para mim e, no seu caso, deve ...

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();
    }
}

... e um código de teste algo 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);
}

Isso cria o seguinte script SQL, tabelas ...

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])

E uma observação, conforme nossa discussão acima ...
Esta não é a 'divisão' - mas
(a) O código primeiro IMO não permite nada parecido (eu tentei isso primeiro e também modifiquei as migrações manualmente, mas é 'internamente' tudo baseado nos nomes de coluna esperados sendo os mesmos e parece não haver maneira de contornar isso, pois pelo menos esta versão do EF.
(b) estrutura da tabela - as tabelas podem ser feitas para parecer exatamente o que você precisa (como eu disse antes, usei para relacionar as tabelas de associação aspnet existentes (que não pude alterar) em minha tabela de usuário que tem um próprio usuário -id apontando para a tabela externa / aspnet e id.
É verdade, você não pode fazer isso usando uma classe de modelo C # - mas o lado C # é muito mais flexível e se você pode controlar o C # isso deve dar o mesmo efeito, pelo menos na minha opinião (como no teste, você pode acessá-lo sempre por meio da entidade estendida, tanto as colunas estendidas quanto as principais e elas são sempre correspondidas 1 a 1 e permanecem 'em sincronia'.
Espero que isso ajude algum
NOTA: você não precisa se preocupar com o fk id etc. - apenas sempre acesse e adicione a entrada Principal via MainEntry e id-s ficará bem.

EDITAR:
Você também pode fazer o seguinte, para ter a aparência de ter que lidar com apenas uma classe (ou seja, uma espécie de divisão)

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();
    }
}

... e use-o assim ...

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

... também você pode reverter a direção do fk executando o WithRequiredPrincipal em vez de dependente.
(também todas as referências devem ser sem 'virtuais' se você tiver requerido um-para-um)
(e MainTable pode ser feito 'interno' como está aqui, então não é visível de fora - não pode ser aninhado porque o EF não permite - é tratado como NotMapped)
... bem, isso é o melhor que pude fazer :)

Parece que foi corrigido no Entity Framework 6. Veja este problema http://entityframework.codeplex.com/ workitem / 388

Eu gostaria de sugerir o uso de algumas anotações de dados 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; }
}

Enfrentei esse problema e resolvi adicionando atributo de coluna para corresponder aos nomes de coluna. [Key] [Column("Id")] public int GroupId { get; set; }

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top