Many Relationship To Many Entity Framework 6

7

Good morning, I have the following classes:

CONSUL_CA_Aluno:

public class CONSUL_CA_Aluno
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public int Cpf { get; set; }
    public string Email { get; set; }
    public string Senha { get; set; }
    public int Ativo { get; set; }

    public virtual ICollection<CONSUL_CA_Curso> CONSUL_CA_Cursos { get; set; }
}

CONSUL_CA_Course:

public class CONSUL_CA_Curso
{
    public int Id { get; set; }
    public string Nome { get; set; }
    public int Ativo { get; set; }
    public string Ministrante { get; set; }
    public string Duracao { get; set; }
    public int CargaHoraria { get; set; }
    public string LocalCurso { get; set; }

    public ICollection<CONSUL_CA_Aluno> CONSUL_CA_Alunos { get; set; }
}

In the database I have the table CONSUL_CA_CourseAluno where the class data will be stored.

When testo:

CONSUL_CA_Aluno aluno = new CONSUL_CA_Aluno();
aluno.Ativo = 1;
aluno.Cpf = 1321;
aluno.Email = "email";
aluno.Nome = "diididid";
aluno.Senha = "123";
aluno.CONSUL_CA_Cursos = contexto.Cursos.ToList();
aluno.CONSUL_CA_Cursos = aluno.CONSUL_CA_Cursos.Select(curso => contexto.Curso.FirstOrDefault(x => x.Id == curso.Id)).ToList();
contexto.Aluno.Add(aluno);
contexto.SaveChanges();

Displays the following error:

  An unhandled exception of type 'System.Data.Entity.Infrastructure.DbUpdateException' occurred in EntityFramework.dll
  Additional information: An error occurred while saving entities that do not expose foreign key properties for their relationships. The EntityEntries property will return null because a single entity can not be identified as the source of the exception. Handling of exceptions while saving can be made easier by exposing foreign key properties in your entity types. See the InnerException for details.

    
asked by anonymous 05.05.2014 / 21:01

2 answers

8

The Entity Framework gets lost when it is time to define the association N for N, even if in theory the statement of its Model is correct.

There are two ways to resolve:

1. Using Fluent Configuration in Model Builder

modelBuilder.Entity<Aluno>()
        .HasMany(a => a.Cursos)
        .WithMany()
        .Map(x =>
        {
            x.MapLeftKey("AlunoId");
            x.MapRightKey("CursoId");
            x.ToTable("AlunosCursos");
        });

The problem with this approach is that the associative table is minimal. You can not add extra fields relative to the association. The change is minor, but will work for the code posted in the question.

2. Defining an associative entity

[Table("CONSUL_CA_Aluno")]
public class Aluno
{
    [Key]
    public int AlunoId { get; set; }
    [Required]
    public string Nome { get; set; }
    [Required]
    public int Cpf { get; set; }
    [Required]
    [EMailAddress]
    public string Email { get; set; }
    public string Senha { get; set; }
    [DefaultValue(true)]
    public Boolean Ativo { get; set; } /* troquei este para Boolean */

    public virtual ICollection<AlunoCurso> AlunosCursos { get; set; }
}

[Table("CONSUL_CA_Cursos")]
public class Curso 
{
    [Key]
    public int CursoId { get; set; }
    [Required]
    public string Nome { get; set; }
    [DefaultValue(true)]
    public Boolean Ativo { get; set; } /* troquei este para Boolean */
    public string Ministrante { get; set; }
    public string Duracao { get; set; }
    public int CargaHoraria { get; set; }
    public string LocalCurso { get; set; }

    public ICollection<AlunoCurso> AlunosCursos { get; set; }
}

public class AlunosCursos
{
    [Key]
    public int AlunoCursoId { get; set; }
    public int AlunoId { get; set; }
    public int CursoId { get; set; }

    public virtual Aluno Aluno { get; set; }
    public virtual Curso Curso { get; set; }
}

No Controller

Aluno aluno = new Aluno();
// A linha de baixo não precisa colocar porque eu já defini o default no Model
// aluno.Ativo = true;
aluno.Cpf = 1321;
aluno.Email = "email";
aluno.Nome = "diididid";
aluno.Senha = "123";

contexto.Aluno.Add(aluno);
contexto.SaveChanges();

foreach (var curso in contexto.Cursos.ToList()) {
    var alunoCurso = new AlunoCurso();
    alunoCurso.Aluno = aluno;
    alunoCurso.Curso = curso;
    contexto.AlunosCursos.Add(alunoCurso);
    contexto.SaveChanges();
}

contexto.Entry(aluno).State = EntityState.Modified;
contexto.SaveChanges();

In this case, your code should be changed to deal with this association. It's a bit more bureaucratic, but it's a more complete approach.

    
05.05.2014 / 21:12
3

You're wrong here:

aluno.CONSUL_CA_Cursos = aluno.CONSUL_CA_Cursos.Select(curso => contexto.Curso.FirstOrDefault(x => x.Id == curso.Id)).ToList();

You are using aluno which is a new object to fetch information, would not it be the context ?, ie the class that inherited from DbContext ?

Usage example

[Table("Livro")]
public class Livro
{
    public Livro()
    {
         this.Autores = new HashSet<Autor>();  
    }

    [Key()]
    [DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public String Titulo { get; set; }

    public virtual ICollection<Autor> Autores { get; set;}
}

[Table("Autor")]
public class Autor
{
    public Autor()
    {
        this.Livros = new HashSet<Livro>();
    }

    [Key()]
    [DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }
    public String Nome { get; set; }

    public virtual ICollection<Livro> Livros {get;set;}
}

These entities have N - M relationship ie many to many.

public class Context: DbContext
{
            public Context()
                : base("conexao") { }

            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                modelBuilder.Entity<Livro>()
                        .HasMany(l => l.Autores)
                        .WithMany(a => a.Livros)
                        .Map(x =>
                        {
                            x.MapLeftKey("LivroId");
                            x.MapRightKey("AutorId");
                            x.ToTable("LivroAutor");
                        });
                base.OnModelCreating(modelBuilder);
            }
            public DbSet<Livro> Livro { get; set; }
            public DbSet<Autor> Autor { get; set; }
}
//classe que herda de DbContext // EF6
Context db = new Context();

//Livro (Novo);
Livro livro = new Livro();
livro.Titulo = "Livro 1";


//Autor (Novo);
Autor autor = new Autor();
autor.Nome = "Autor 1";
// adicionando esse livro nesse autor muitos para muitos aqui ...
autor.Livros.Add(livro); 

//adicionando livro
db.Livro.Add(livro);

//adicionando autor
db.Autor.Add(autor);

//salvando todas alterações
db.SaveChanges();

db.Dispose();

This is a many-to-many layout, now if the middle table is different it has to be exposed in the Context ( DbSet<> ) for passing the additional values.

    
05.05.2014 / 21:24