Solucionado (ver solução)
Solucionado
(ver solução)
17
respostas

Como gravar uma foreignkey

Para exercitar, fiz o seguinte exemplo: - Eu tenho a seguinte situação: "Uma escola, possui uma lista de áreas." - Para ter acesso ao site da escola, tenho os meus usuários por escola. O modelo fica:

    public class Escola
    {

        public Escola ()
        {
            Usuarios = new List<Usuario>();
            Areas = new List<Area>();
        }
        public int EscolaId { get; set; }
        public string Nome { get; set; }
        public virtual ICollection <Usuario> Usuarios { get; set; }
        public virtual ICollection<Area> Areas { get; set; }
    }
    public class Area
    {
        public int AreaId { get; set; }
        public string Nome { get; set; }
        //public virtual ICollection<Curso> Cursos { get; set; }
        public int EscolaId { get; set; }
        public virtual Escola Escolas { get; set; }
    }
    public class Usuario
    {

        public int UsuarioId { get; set; }
        public string Nome { get; set; }
        [EmailAddress]
        public string Email { get; set; }
        public int? EscolaId { get; set; }
        public virtual Escola Escolas { get; set; }
    }

No cadastro: 1º passo cadastro usuário - ok. 2º passo cadastro escola - Não consigo gravar o EscolaId (foreignkey) na tabela usuário. Segue o código abaixo da controller.

[HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit([Bind(Include = "EscolaId,Nome")] Escola escola)
        {
            if (ModelState.IsValid)
            {
                db.Entry(escola).State = EntityState.Modified;
                if (escola.Usuarios.Count() == 0)
                {
                    var usuario = (Usuario)Session["usuarioLogado"];
                    if (usuario != null)
                    {
                        escola.Usuarios.Add(usuario);
                    }
                    Usuario usu = usuario;
                    usu.EscolaId =  escola.EscolaId; // erro aqui.
                    UsuarioDAO dao = new UsuarioDAO();
                    dao.Atualiza(usuario);
                }
                db.SaveChanges();

                return RedirectToAction("Index");
            }
            return View(escola);
        }

Dá erro.

17 respostas

Olá, Vanessa!

Nesta linha:

usu.EscolaId =  escola.EscolaId; // erro aqui.

Qual o valor de usu? Imagino que seja nulo. Qual o stacktrace da exceção? Obrigado!

Isso mesmo.

Qual a melhor pratica pra essa situação?

Bom, se o usuário é nulo, então ele não está logado:

var usuario = (Usuario)Session["usuarioLogado"];

O que fazer nesse caso? Acho que o correto e mais simples seria adicionar um atributo [Authorize], que, além de impedir usuários não-logados de acessarem a action, também redireciona o usuário para action de login:

[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public ActionResult Edit(

Estou tentando compilar para ver se resolveu. Porém, esbarro em outro erro. Como as escolas são por usuário, estou tentando só mostrar as escolas que o usuário logado tem acesso.

        public IList<Escola> ListaPorUsuario(int UsuarioId)
        {

            var query = from e in context.Escolas
                        join u in context.Usuarios
                            on e.EscolaId equals u.EscolaId
                        where u.UsuarioId == UsuarioId
                        select new
                        {
                            EscolaId = e.EscolaId,
                            Nome = e.Nome,
                            Usuarios = new  {
                                UsuarioId = u.UsuarioId,
                                Email = u.Email,
                                Nome = u.Nome
                            }

                        };
            //SELECT* FROM ESCOLA E
            //         INNER JOIN USUARIO U
            //         ON U.ESCOLAID = E.ESCOLAID

            IList<Escola> escola = query.ToList<Escola>(); // erro aqui! Nem compila.
            return (escola);
        }

O erro é aqui: IList escola = query.ToList();

Mensagem de erro: - Severity Code Description Project File Line Suppression State Error CS1929 'IQueryable< Usuarios>>' does not contain a definition for 'ToList' and the best extension method overload 'Enumerable.ToList(IEnumerable)' requires a receiver of type 'IEnumerable' EscolaWeb C:\Users\Vanessa\Documents\Vanessa Documentos_ProjetosMVC\EscolaWeb\EscolaWeb\EscolaWeb\EscolaWeb\DAO\EscolaDAO.cs 62 Active

O método ToList() faz parte do LINQ. Você está usando a diretiva para a biblioteca do LINQ?

using System.Linq

Aproveitando, no mesmo método... Eu leio a session (Session["usuarioLogado"]), beleza. Mas por exemplo, interrompi o funcionamento do sistema (VS 2015) para corrigir algo no fonte. Quando eu compilo novamente, o usuário já está logado! Mas não deveria, né? Se eu saí do sistema... ele deveria deslogar tb. Existe algum comando que eu possa dar no meu sistema para alinhar isso melhor?? Obrigada.

Eu olhei agora o meu fonte... o [Authorize] estava direto na classe. Por isso que não aparecia no método. Deve ter uma forma de melhorar a questão do login?

Nesse caso ele não deve ter parado o processo que mantém a sessão do usuário. Você está rodando no IIS express, correto? Faz um teste com o stop website do IIS express, e vê se o usuário continua logado (não deveria):

O login está feito assim: Só que não funciona direito. E a mensagem que coloquei, não está aparecendo na tela.

Essa mensagem: ViewBag.Mensagem = usuario.Nome + ". Seja bem vinda!"; Não aparece nunca na View.

  public ActionResult Autentica(String login, String senha)
        {
            UsuarioDAO dao = new UsuarioDAO();
            Usuario usuario =  dao.Busca(login, senha);

            if (WebSecurity.Login(login, senha))
            {
                ViewBag.Mensagem = usuario.Nome + ". Seja bem vinda!";
                Session["usuarioLogado"] = usuario;
                return RedirectToAction("Index","Home");
            }
            else
            {
                ViewBag.Mensagem = "Necessário realizar o login.";
                return View("Index", "Home");
            }
        }

        public ActionResult Logout()
        {
            Session.Clear();
            WebSecurity.Logout();
            return RedirectToAction("Index");
        }

View:


<form action="@Url.Action("Autentica")" method="post"  protection="All" timeout="90" slidingExpiration="true">
    @Html.ValidationMessage("login.Invalido")

    <label for="login">Login:</label>
    <input id="login" type="text" name="login" class="form-control" />


    <label for="senha">Senha:</label>
    <input id="senha" type="password" name="senha" class="form-control" />


    <input type="submit" value="Autenticar" />
</form>
solução!

Ah sim, não me atentei a isso. Se você usar o ASP.NET Identity, você pode pegar o usuário logado assim:

using Microsoft.AspNet.Identity;

...
User.Identity.GetUserId();

Mas note que isso depende do ASP.NET Identity. É recomendável que você siga exemplos de como estender a classe ApplicationUser do ASP.NET Identity, e só então colocar suas informações custom (como id de escola, por exemplo):

http://www.eduardopires.net.br/2014/03/asp-net-identity-customizando-cadastro-usuarios/

Ou seja, fazer um serviço de autenticação/autorização totalmente customizado, fora do ASP.NET Identity, dá muita dor de cabeça e não vale a pena. O negócio é focar nas funcionalidades realmente importantes da sua aplicação e deixar essa parte de infraestrutura na mão do ASP.NET Identity.

Saquei. Então essa parte do usuário eu vou pegar o exemplo de vcs do CaelumEstoque. Vou tentar novamente. Eu estou fazendo o curso do Eduardo Pires em paralelo tb. O módulo ASP.NET MVC 5. Preciso aprender a trabalhar neste formato. Travar em coisas básicas assim é muito frustrante. Mas vamos lá... essa criança vai ter que nascer. rsrs Valeu Marcelo. Vou tentar aqui e já retorno. Abs.

Aí sim! Boa sorte, Vanessa!

Marcelo, Refiz a modelagem de usuários, me guiando pelo exemplo do CaelumEstoque - essa parte ficou show de bola! A parte do cadastro de estoque - ficou bem mais simples e muito estável. Tanto o login quanto o cadastro de estoque, ficaram show de bola. Funcionando super bem. Agora vamos para a 3ª e última tela deste meu exemplo. A tela de Áreas, onde eu tenho um combobox. 1) já consigo gravar (inserir) na tabela, 2) excluir na tabela e consultar.

Meu problema é no EDITAR. Já estou identificando outras limitações tb, exempo: 1) no login, eu gostaria de exibir uma mensagem na tela, por uma viewbag por exemplo, mas não descobri como fazer. Não exibe a mensagem. 2) se eu excluir uma escola que tem uma área, deveria dar a mensagem em tela da validation annotation, mas tb ainda não acertei fazer.

MAS... vamos para o EDITAR para eu conseguir fechar essas tres telas. :D Seguem os meus códigos. O modelo ficou assim:

    public class Area
    {
        public int AreaId { get; set; }
        public string Nome { get; set; }
        public int EscolaId { get; set; }
        public virtual Escola Escolas { get; set; }
    }
    public class Escola
    {

        public Escola ()
        {
            Areas = new List<Area>();
        }
        public int EscolaId { get; set; }
        public string Nome { get; set; }
        public int UsuarioId { get; set; }
        public virtual  Usuario Usuarios { get; set; }
        public virtual ICollection<Area> Areas { get; set; }
    }
    public class Usuario
    {

        public Usuario()
        {
            Escolas = new List<Escola>();
        }
        public int UsuarioId { get; set; }
        public string Nome { get; set; }
        [EmailAddress]
        public string Email { get; set; }
        public string Senha { get; set; }
        public virtual ICollection<Escola> Escolas { get; set; }
    }

Meus "Map" de Area (Fluent Api)


        public AreaMap()
        {
            ToTable("Area");
            HasKey(x => x.AreaId);
            Property(x => x.AreaId)
                       .HasColumnName("AreaId")
                       .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);

            Property(x => x.Nome).IsRequired().HasMaxLength(50)
                       .HasColumnAnnotation("Index", new IndexAnnotation(new IndexAttribute { IsUnique = true }));

            //S3: A chave estrangeira para a tabela Area - EscolaId
            HasRequired(c => c.Escolas)
             .WithMany(p => p.Areas).HasForeignKey(p => p.EscolaId);
        }

Meu Controller de Area:


    [AutorizacaoFilter]
    public class AreaController : Controller
    {
        private AreaDAO dao;
        private EscolaDAO daoEscola;

        public AreaController()
        {
            this.dao = new AreaDAO();
            this.daoEscola = new EscolaDAO();
        }

        // GET: Area
        public ActionResult Index()
        {
            IList<Area> area = dao.Lista();
            return View(area);
        }

        // GET: Area/Details/5
        public ActionResult Details(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Area area = dao.BuscarPorId(id);

            ViewBag.Escolas = daoEscola.Lista();

            if (area == null)
            {
                return HttpNotFound();
            }
            return View(area);
        }

        // GET: Area/Create
        public ActionResult Create()
        {
            ViewBag.Escolas = daoEscola.Lista();

            return View();
        }

        // POST: Area/Create
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create(/*[Bind(Include = "AreaId,Nome")]*/ Area area)
        {
            if (ModelState.IsValid)
            {
                dao.Adiciona(area);

                return RedirectToAction("Index");
            }

            return View(area);
        }

        // GET: Area/Edit/5
        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }

            Area area = dao.BuscarPorId(id);

            ViewBag.Escolas = daoEscola.Lista();

            if (area == null)
            {
                return HttpNotFound();
            }
            return View(area);
        }

        // POST: Area/Edit/5
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit(/*[Bind(Include = "AreaId,Nome")]*/ Area area)
        {
            if (ModelState.IsValid)
            {
                dao.Atualiza(area);
                return RedirectToAction("Index");
            }
            return View(area);
        }

        // GET: Area/Delete/5
        public ActionResult Delete(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Area area = dao.BuscarPorId(id);
            if (area == null)
            {
                return HttpNotFound();
            }
            return View(area);
        }

        // POST: Area/Delete/5
        [HttpPost, ActionName("Delete")]
        [ValidateAntiForgeryToken]
        public ActionResult DeleteConfirmed(int id)
        {
            Area area = dao.BuscarPorId(id);
            dao.Remove(area);
            return RedirectToAction("Index");
        }

    }

View de Area:

@model EscolaWeb.Models.Area

@{
    ViewBag.Title = "Edit";
}

<h2>Edit</h2>

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>Area</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        @Html.HiddenFor(model => model.AreaId)

        <div class="form-group">
            @Html.LabelFor(model => model.Nome, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Nome, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.Nome, "", new { @class = "text-danger" })
            </div>
        </div>
        <div class="form-group">
            <label for="escola" class="control-label col-md-2">Escola:</label>
            <div class="col-md-8">
                <select id="escola" name="area.EscolaId" class="form-control">
                    @foreach (var escola in ViewBag.Escolas)
                    {
                        <option value="@escola.EscolaId" selected="@escola.EscolaId.Equals(Model.EscolaId)">
                            @escola.Nome
                        </option>
                }
                </select>
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Save" class="btn btn-default" />
            </div>
        </div>
    </div>
}

<div>
    @Html.ActionLink("Back to List", "Index")
</div>

Acho que faltou o principal que é o código DAO. Obrigada pela força de vcs do Alura.

insira seu código aquiusing System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;
using EscolaWeb.Models;

namespace EscolaWeb.DAO
{
    public class AreaDAO
    {
        private DBContexto context = new DBContexto();

        public AreaDAO(DBContexto context)
        {
            this.context = context;
        }

        public AreaDAO()
        {
        }

        public void Adiciona(Area Area)
        {
            context.Areas.Add(Area);
            context.SaveChanges();
        }
        public void Atualiza(Area area)
        {
            context.Areas.Attach(area);
            context.Entry(area).State = EntityState.Modified;
            context.SaveChanges();
        }

        public void Remove(Area area)
        {
            context.Areas.Remove(area);
            context.SaveChanges();

        }
        public virtual IList<Area> Lista()
        {
            return context.Areas.Include("Escolas").ToList();
        }

        public virtual Area BuscarPorId(int? id)
        {
            Area area = context.Areas.Find(id);
            Escola escola = context.Escolas.Find(area.EscolaId);
            area.Escolas = escola;
            return area;
        }

        public virtual IList<Area> ListarPorEscola(int EscolaId)
        {
            return (IList<Area>)context.Areas.ToList().Where(x => x.Escolas.EscolaId == EscolaId);
        }



    }
}

A mensagem do erro ao editar é essa: Validation failed for one or more entities. See 'EntityValidationErrors' property for more details. O objeto Area area vem vazio no parametro de entrada. Mas porque vazio? Na inclusão grava legal.

Resolvi o edit. Refiz o html. tinha algum detalhe no razor que passava os dados vazios para a controller. Marcelo, obrigada pela ajuda de hoje.