Many-To-Many Update EntityFramework does not work

0

I have a N: N relationship between Activity and Project, where an activity has many projects:

public class Atividade : ObjetoPersistente
{

    public Atividade()
    {
        StatusAtividade = EStatusAtividade.NaoIniciado;
        TipoAtividade = ETipoAtividade.NovaImplementacao;
        Usuario = new Usuario();
        Projetos = new List<Projeto>();
    }

    [JsonConverter(typeof(FormatoDataHoraMinutoNullableConverter))]
    public DateTime? DataHoraFim { get; set; }

    [JsonConverter(typeof(FormatoDataHoraMinutoNullableConverter))]
    public DateTime? DataHoraInicio { get; set; }

    public string DescricaoAtividade { get; set; }
    public string EstimativaInicialAtividade { get; set; }      

    public Usuario Usuario { get; set; }

    public string LoginUsuario
    {
        get { return Usuario.Login; }
    }      

    public EStatusAtividade StatusAtividade { get; set; }

    public string DescricaoStatusAtividade
    {
        get { return StatusAtividade.Descricao; }
    }           

    public string DescricaoTipoAtividade
    {
        get { return TipoAtividade.Descricao; }
    }

    public ETipoAtividade TipoAtividade { get; set; }
    public string TituloAtividade { get; set; }

    public long CodigoUsuario
    {
        get { return Usuario.Codigo; }
        set { Usuario.Codigo = value; }
    }

    public List<Projeto> Projetos { get; set; }


    public List<long> CodigoProjetos
    {
        get { return ObtenhaListaDeCodigosPorListaDeObjetos(Projetos); }

        set { Projetos = ObtenhaListaDeObjetoPorListaDeCodigos<Projeto>(value); }
    }

    public override bool Equals(object obj)
    {
        return (obj is Atividade) && (obj as Atividade).Codigo.Equals(Codigo);
    }

    public override int GetHashCode()
    {
        return Codigo.GetHashCode();
    }
}

Project:

public class Projeto : ObjetoPersistente, IObjetoElementoOption
{       
    public string Nome { get; set; }


    public override bool Equals(object obj)
    {
        return (obj is Projeto) && (obj as Projeto).Codigo.Equals(Codigo);
    }

    public override int GetHashCode()
    {
        return Codigo.GetHashCode();
    }

    public string Valor
    {
        get { return Codigo.ToString(); }
    }

    public string Descricao {
        get { return Nome; }
    }
}

The mappings look like this:

    public AtividadeMap()
    {
        HasKey(a => a.Codigo);

        ToTable("tb_atividade");

        Property(a => a.TituloAtividade).HasColumnName("titulo_atividade");
        Property(a => a.DescricaoAtividade).HasColumnName("descricao_atividade");
        Property(a => a.DataHoraInicio).HasColumnName("data_hora_inicio");
        Property(a => a.DataHoraFim).HasColumnName("data_hora_fim");
        Property(a => a.Codigo).HasColumnName("pk_atividade");
        Property(a => a.StatusAtividade.Identificador).HasColumnName("status_atividade");
        Property(a => a.EstimativaInicialAtividade).HasColumnName("estimativa_inicial_atividade");
        Property(a => a.TipoAtividade.Identificador).HasColumnName("tipo_atividade");
        Property(a => a.CodigoUsuario).HasColumnName("fk_usuario");

        HasRequired(a => a.Usuario)
            .WithMany()
            .HasForeignKey(a => a.CodigoUsuario);

        HasMany(a => a.Projetos)
            .WithMany()
            .Map(pa =>
            {
                pa.MapLeftKey("fk_atividade");
                pa.MapRightKey("fk_projeto");
                pa.ToTable("tb_atividade_projeto");
            });
    }


public class ProjetoMap : EntityTypeConfiguration<Projeto>
{
    public ProjetoMap()
    {            
        HasKey(a => a.Codigo);

        ToTable("tb_projeto");

        Property(p => p.Codigo).HasColumnName("pk_projeto");
        Property(a => a.Nome).HasColumnName("nome");
    }

}

The query process is OK, however, inclusions and changes do not work.

What's missing?

After searching on the subject, I found several answers like this, which in summary, in my situation, I would have to load each Project from the list of the Activity object to perform a Attach in the context class:

a>

Is this the only way to perform an update in the situation of many-to-many relationships? Could you make the persistence of these relationships generic?

    
asked by anonymous 20.07.2015 / 21:01

2 answers

3

I think I understand what you're trying to do. Your relationship is wrong. If a Projeto has N Atividade s, and a Atividade belongs to N Projeto s, you could never use something like this:

public class Atividade : ObjetoPersistente
{

    public Atividade()
    {
        ...
        Projetos = new List<Projeto>();
    }

    ...
    public List<Projeto> Projetos { get; set; }    
    ...
}

So you're saying that Atividade has N Projeto s, but that a Projeto belongs to only Atividade .

The correct one would be:

[Table("tb_atividade")]
public class Atividade : ObjetoPersistente
{

    public Atividade()
    {
        ...
        // Retire isso
        // Projetos = new List<Projeto>();
    }

    ...
    // Não use Projetos diretamente. Use uma tabela associativa.
    public virtual ICollection<ProjetoAtividade> ProjetoAtividades { get; set; }    
    ...
}

Projeto also receives ProjetoAtividades , as it is an association:

[Table("tb_projeto")]
public class Projeto : ObjetoPersistente, IObjetoElementoOption
{       
    ...
    public virtual ICollection<ProjetoAtividade> ProjetoAtividades { get; set; }  
}

Now you also need to map ProjetoAtividade :

[Table("tb_atividade_projeto")]
public class ProjetoAtividade
{
    [Key]
    [Column("fk_atividade")]
    public int AtividadeId { get; set; }

    [Key]
    [Column("fk_projeto")]
    public int ProjetoId { get; set; }

    // Estas são propriedades de navegação.
    // O Entity Framework carrega essas classes automaticamente.
    public virtual Projeto Projeto { get; set; }
    public virtual Atividade Atividade { get; set; }
}

Notice that I do not need to use Fluent API here. I can just decorate the properties with Attributes and the Entity Framework does everything else alone.

Inclusions and Modifications

As the Models mapping is wrong, you will need to map Codigo to the classes to work:

[Table("tb_atividade")]
public class Atividade : ObjetoPersistente
{
    [Key]
    // Não sei se a coluna no banco chama "codigo", mas suponho que sim.
    [Column("codigo")]
    public int Codigo { get; set; }

    ...
}

[Table("tb_projeto")]
public class Projeto : ObjetoPersistente, IObjetoElementoOption
{       
    ...
    [Key]
    // Não sei se a coluna no banco chama "codigo", mas suponho que sim.
    [Column("codigo")]
    public int Codigo { get; set; }
}

About inclusions, to insert a new association, do the following:

// Carregue o projeto
var projeto = contexto.Projetos.FirstOrDefault(p => p.Codigo == /* Coloque o valor da chave aqui */);
// Carregue a atividade
var atividade = contexto.Atividades.FirstOrDefault(a => a.Codigo == /* Coloque o valor da chave aqui */);
if (projeto != null && atividade != null)
{
    var projetoAtividade = new ProjetoAtividade 
    {
        // É assim mesmo que usa. __NUNCA__ defina o Id diretamente.
        Atividade = atividade,
        Projeto = projeto
    }

    contexto.ProjetoAtividades.Add(projetoAtividade);
    contexto.SaveChanges();
}

As the change to the associative entity does not make sense, I'll teach you how to do an exclusion:

var projetoAtividade = contexto.ProjetoAtividades.FirstOrDefault(/* Coloque aqui a condição para selecionar a associação, aqui usando os Ids */);
if (projetoAtividade != null)
{
    contexto.ProjetoAtividades.Remove(projetoAtividade);
    contexto.SaveChanges();
}
    
27.07.2015 / 18:35
3

Well, a priori you do not have to load related entities at all times. Especially when making changes.

Now, whenever you use a reference to an entity that exists in the database, you need to give attach or retrieve the entities. Do not always search the database, but need to give attach in dbcontext .

Interestingly, I have a question exactly like this in the OS, which did not get an answer (and I had to go back and respond): Question SO .

At first it seems strange: having to recover entities that are already there. But it works that way. Let's say you already have the projects, say returned from the user interface in a web application. Let's also say that you trust that these objects are okay. You could create a new activity and insert these projects, right? No. If you do this, DBContext will actually create new projects, because the ones you are using are not attached .

An alternative way to do this is to give attach and change the entity state to unchanged . So DBContext does not change these projects, but does not change them either.

But deep down, the practice of getting them back into the bank is correct. I do not imagine a scenario where you could have dettached objects that are safe, even if they were previously recovered (possibly GOOD before) could have been removed from the DB.

So, yes , this logic with attach is the only way to update entities with N: N or 1: N relationships.

You can encapsulate this in your repositories, but not generic . It is precisely the fact that each entity has special relationships that makes these logics necessary.

    
21.07.2015 / 13:00