Relationships in Viewmodel with EF

Asked

Viewed 569 times

3

I am developing a project where a user will have several data. To not extend the class I thought to separate by categories. Example of classes:

  • User
  • Data
  • Dadosfamily
  • Etc..

To do this on the site I thought of creating like a Carousel with boostrap and create a ViewModel to store all candidate data:

ex:

public class DadosCandidatoViewModel
{
    public int CandidatoId { get; set; }

    public CandidatoViewModel CandidatoViewModel { get; set; }
    public DadosPessoaisViewModel DadosPessoaisViewModel { get; set; }
    //ETC

}

Then the doubt begins, because I do not know if it is necessary, since the Model Candidate already has the virtual tables for these tables, as well as these other tables also have candidate since the relationship is 1 for 1 or 0.

public class Candidato
{
    public int CandidatoId { get; set; }
    public string Nome { get; set; }
    public string CPF { get; set; }

    public DateTime DataCadastro { get; set; }
    public bool Ativo { get; set; }

    public virtual DadosPessoais DadosPessoais { get; set; }
    public virtual DadosFamiliares DadosFamiliares { get; set; }

    public virtual ICollection<Cargo> Cargos { get; set; }

}

public class DadosPessoais
{
    public int CandidatoId { get; set; }

    public string Sexo { get; set; }
    public string Endereco { get; set; }

    public virtual Candidato Candidato { get; set; }
}

My View Model Went Like This:

public class CandidatoViewModel
{

    [Key]
    public int CandidatoId { get; set; }

    [Required(ErrorMessage = "Preencher o campo Nome")]
    [MaxLength(150, ErrorMessage = "Máximo {1} caracteres")]
    [MinLength(2, ErrorMessage = "Mínimo {1} caracteres")]
    public string Nome { get; set; }

    [Required(ErrorMessage = "Preencher o campo CPF")]
    [MaxLength(15, ErrorMessage = "Máximo {1} caracteres")]
    [MinLength(2, ErrorMessage = "Mínimo {1} caracteres")]
    //Criar Datatype de CPF
    public string CPF { get; set; }

    [ScaffoldColumn(false)]
    public DateTime DataCadastro { get; set; }

    public virtual DadosPessoaisViewModel DadosPessoais { get; set; }

    public bool Ativo { get; set; }
}


public class DadosPessoaisViewModel
{
    [Required(ErrorMessage = "Preencher o campo Endereço")]
    [MaxLength(1, ErrorMessage = "Máximo {0} caracteres")]
    [MinLength(1, ErrorMessage = "Mínimo {0} caracteres")]
    public string Sexo { get; set; }

    [Required(ErrorMessage = "Preencher o campo Endereço")]
    [MaxLength(500, ErrorMessage = "Máximo {0} caracteres")]
    [MinLength(2, ErrorMessage = "Mínimo {0} caracteres")]
    public string Endereco { get; set; }
}

Well, first I need to know if I’m doing it right, or at least going the right way, because the way I’m facing various problems when it comes to persisting data.

Below is the View I created for control DadosCandidatoViewModel where I intend to call the Partial Views for the Candidate Data:

@model Gestao_RH.MVC.ViewModels.DadosCandidatoViewModel


@{
    ViewBag.Title = "DadosCandidato";
}

<h2>Edit</h2>

<!-- Div na qual o "carousel" será aplicado. -->
<div id="div-carousel" class="carousel slide">
    <div class="carousel-inner">
        <!-- Divs com efeito de transição. -->
        <div class="item active">

            @Html.Partial("~/Views/DadosPessoais/Edit.cshtml", Model)
        </div>
        <div class="item">
            Conteúdo da DIV 2.
        </div>
    </div>
</div>
<div class="row">
    <!-- Botões de navegação -->
    <div id="div-1" class="span2">
        <a id="a-1" class="btn" href="#div-carousel" data-slide="prev"><i class="icon-chevron-left"></i>Voltar para DIV 1</a>
    </div>
    <div id="div-2" class="span2">
        <a id="a-2" class="btn" href="#div-carousel" data-slide="next">Avançar para DIV 2<i class="icon-chevron-right"></i></a>
    </div>
</div>


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

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")

    <script type="text/javascript">
        $(document).ready(function () {

            // Aplica o efeito de "carousel" nas divs que possuirem a classe carousel.
            $('.carousel').carousel({
                interval: false
            });

            // Oculta o botão de navegação para a div 1.
            $('#div-1').hide();

            // Aplica a transição quando houver click no link AVANÇAR.
            $("#a-1").click(function () {
                $('#div-carousel').carousel(0);
                $('#div-1').hide();
                $('#div-2').show();
                return false;
            });

            // Aplica a transição quando houver click no link VOLTAR.
            $("#a-2").click(function () {
                $('#div-carousel').carousel(1);
                $('#div-1').show();
                $('#div-2').hide();
                return false;
            });
        });
    </script>
}

Below just an example of Viewmodel Save or edit Personal Data:

@model Gestao_RH.MVC.ViewModels.DadosCandidatoViewModel

@using (Html.BeginForm("SaveDadosPessoais", "DadosCandidato", FormMethod.Post, new { }))
{
    @Html.AntiForgeryToken()

    @Html.DisplayFor(model => model.CandidatoViewModel.Nome);
    @Html.HiddenFor(model => model.CandidatoId);

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

        <div class="form-group">
            @Html.LabelFor(model => model.DadosPessoaisViewModel.Sexo, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.DadosPessoaisViewModel.Sexo, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.DadosPessoaisViewModel.Sexo, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            @Html.LabelFor(model => model.DadosPessoaisViewModel.Endereco, htmlAttributes: new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.DadosPessoaisViewModel.Endereco, new { htmlAttributes = new { @class = "form-control" } })
                @Html.ValidationMessageFor(model => model.DadosPessoaisViewModel.Endereco, "", new { @class = "text-danger" })
            </div>
        </div>

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

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

This isn’t working either:

public class DadosCandidatoController : Controller
{
    //  private readonly ProdutoRepository _ProdutoRepository = new ProdutoRepository();
    private readonly ICandidatoAppService _candidatoApp;
    private readonly IDadosPessoaisAppService _dadosPessoasApp;
    // GET: Produtos

    public DadosCandidatoController(ICandidatoAppService candidatoApp, IDadosPessoaisAppService dadosPessoaisApp)
    {
        _candidatoApp = candidatoApp;
        _dadosPessoasApp = dadosPessoaisApp;
    }


    [HttpGet]
    public ActionResult DadosCandidato(int id)
    {
        var candidato = _candidatoApp.GetById(id);
        var DadosCandidatoViewModel = Mapper.Map<Candidato, DadosCandidatoViewModel>(candidato);


        return View("DadosCandidato", DadosCandidatoViewModel);
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult SaveDadosPessoais(DadosCandidatoViewModel model)
    {

        if (ModelState.IsValid)
        {
            var DadosPessoaisDomain = Mapper.Map<DadosPessoaisViewModel, DadosPessoais>(model.DadosPessoaisViewModel);


            if (_dadosPessoasApp.GetById(model.CandidatoId) == null)
            {

                _dadosPessoasApp.Add(DadosPessoaisDomain);
            }
            else
            {
                _dadosPessoasApp.Update(DadosPessoaisDomain);
            }


            return View("DadosCandidato", model);
        }


        return View("DadosCandidato", model);
    }
}

Any help is very welcome, my head is tying the knot with MVC.

  • On your controller, what’s not working?

2 answers

2

If the idea is to separate by Viewmodels so that each Viewmodel is represented by a Partial, the reasoning is wrong.

The idea of separating Models (or Viewmodels) is for the principle of cardinality: usually the separation serves for certain associations or aggregations. In your case, there is no need for this separation.

If the Models are equal to Viewmodels, use Viewmodels in its application seems to me something without much sense.

You might as well set your whole Model in this way:

public class Candidato
{
    [Key]
    public int CandidatoId { get; set; }
    public string Nome { get; set; }
    public string CPF { get; set; }

    public DateTime DataCadastro { get; set; }
    public bool Ativo { get; set; }

    public string Sexo { get; set; }
    public string Endereco { get; set; }

    public virtual DadosPessoais DadosPessoais { get; set; }
    public virtual DadosFamiliares DadosFamiliares { get; set; }

    public virtual ICollection<Cargo> Cargos { get; set; }
}

Apparently you are using Automapper to map your Viewmodels in Models. I don’t know what your layer of App Service, but a tip is to use debug to see what is happening.

  • Morrison’s candidate data will be about 100 and maybe in the future it will change. what you suggest to me is to create a candidate class and throw everything in there? I didn’t understand...from what I saw in your suggestion you got the candidate’s personal data which are sex and address, for example, and I play everything in the Candidate Model. In fact the idea is that these data belong to the class Data People and the same has a relationship of 1 to 1 or 0 with Candidate!

  • @Paulojardim So the candidates will not necessarily have personal data? Yes, my suggestion is to leave everything in one Model only when the cardinality is 0 or 1 for the purpose of simplifying the business rule under application.

  • Exactly, imagine that the registration process will be in stages, I imagine it will be very stressful to give an error in the middle of item 99 and he have to redo everything.

-1

You’re doing it right, Paulo. And it’s very nice to see this level of organization and separation of responsibilities, congratulations.

Work with ViewModels is essential. What is missing in its implementation is to understand that you can work with the concept of PageObject - the same concept derived from the Selenium testing framework - and do PageViews, where these views will represent the entire template of your page, thus:

// Modelo de representação da sua view DadosCandidato
public class DadosCandidatoPageViewModel
{
    public int CandidatoId { get; set; }

    public CandidatoViewModel Candidato { get; set; }
    public DadosPessoaisViewModel DadosPessoais { get; set; }
    // ...    
}

And in his view:

@model Gestao_RH.MVC.ViewModels.DadosCandidatoPageViewModel;

Thus it is defined which data will be passed to your view.

About the question of sending/creating ViewModels semantics to your entities: It’s not a problem. Remember that your entities/models should be isolated from your environments. Thus, too, it avoids "soiling" its entities with notations such as Required, MinLength, etc. which are specific ratings for views and shall not exist in your Domain.

About the virtual ones, remember that the data coming out of your AppServices must already be disconnected. The fact that you glimpse that, from inside your view, can consume virtual properties in order to take advantage of the resource of Lazy Load of Entity Framework already indicates a bad implementation. Because you will be creating a scenario where, from your view, you will trigger an event that will query your repository. And this is much bad. The data must leave your AppService ready and disconnected.

Below a review in your Controller that I found relevant:

public class DadosCandidatoController : Controller
{
    private readonly ICandidatoAppService _candidatoApp;
    private readonly IDadosPessoaisAppService _dadosPessoasApp;
    // GET: Produtos

    public DadosCandidatoController(ICandidatoAppService candidatoApp, IDadosPessoaisAppService dadosPessoaisApp)
    {
        _candidatoApp = candidatoApp;
        _dadosPessoasApp = dadosPessoaisApp;
    }


    [HttpGet] // URL: /DadosCandidato/Candidato/123
    public ActionResult Candidato(int id)
    {
        var candidato = _candidatoApp.GetById(id);
        // Nas ultimas versoes do AutoMapper, 
        // não é mais necessário passa os tipos de origem e destino do mapeamento.
        // Basta por o destino, que o tipo de origim ele recupera pelo
        // objeto no parametro.
        var DadosCandidatoViewModel = Mapper.Map<DadosCandidatoViewModel>(candidato);

        // Por convenção, quando o nome da View é tem o mesmo nome da Action
        // não é necessário ser explícito, basta retornar os dados.
        return View(DadosCandidatoViewModel);
    }

    [HttpPost] // URL: /DadosCandidato/Candidato/123
    [ValidateAntiForgeryToken]
    public ActionResult Candidato([FromUri]int id, [FromBody]DadosCandidatoViewModel model)
    {    
        if (ModelState.IsValid)
        {
            // Mesma obs sobre AutoMapper
            var DadosPessoaisDomain = Mapper.Map<DadosPessoais>(model.DadosPessoaisViewModel);

            // Aqui voce deve apenar soliciar a inserção do dado
            // Checar se o dados já existe, e se exisitir atualizar é uma regra de negócio
            // Sendo regra de negócio, deve então ficar dentro de sua camada
            // de Domínio
            // A Controller deve ser a camada mais BURRA de sua aplicação
            // Ela apenas recebe dados e envia para o AppService
            // Ou recebe dados do AppService e transforma em ViewModels para exibição   
            _dadosPessoasApp.Add(DadosPessoaisDomain);
        }    

        return View("DadosCandidato", model);
    }
}
  • I would like to comment on the reason for the negative?

Browser other questions tagged

You are not signed in. Login or sign up in order to post.