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

Modelagem com problemas

Eu tenho 3 models: Caixa, CaixaMovimento e SolicitacaoRecurso.

Há vários caixas e cada um tem seus Movimentos e suas Solicitações de Recursos. Um caixa terá muitos movimentos e solicitações mas um Movimento será só de um caixa , da mesma forma acontecendo com as Solicitações.

public class Caixa 
    {
        public Caixa ()
        {
            SolicitacoesRecurso = new List<SolicitacaoRecurso>();
            Movimentos = new List<CaixaMovimento >();
        }

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

        [Required, StringLength(30)]
        public string Nome { get; set; }

        [Required, StringLength(128)]
        public string ApplicationUserId { get; set; }
        public ApplicationUser Responsavel { get; set; }

        [Required, DataType(DataType.Currency)]
        public decimal SaldoInicial { get; set; }

        [Required, DataType(DataType.Currency)]
        public decimal SaldoAtual { get; set; }

        public virtual IList<CaixaMovimento> Movimentos { get; set; }
        public virtual IList<SolicitacaoRecurso> SolicitacoesRecurso { get; set; }
    }

    public class CaixaMovimento 
    {
        public CaixaMovimento()
        {
            Caixas = new List<Caixa>();
        }
        [Key]
        public int ID { get; set; }

        [Required, DataType(DataType.Date)]
        public DateTime Data { get; set; }

        [Required, StringLength(200)]
        public string Historico { get; set; }

        [Required, DataType(DataType.Currency)]
        public decimal Valor { get; set; }

        [Required]
        public virtual MovimentacaoEnum Movimentacao { get; set; }

        public virtual IList<Caixa> Caixas { get; set; }
    }

    public class SolicitacaoRecurso 
    {
        [Key]
        public int ID { get; set; }

        [Required, DataType(DataType.Date)]
        public DateTime Data { get; set; }

        [Required, StringLength(128)]
        public string ApplicationUserId { get; set; }
        public ApplicationUser Usuario { get; set; }

        [Required]
        public int CaixaID { get; set; }
        public virtual Caixa Caixa { get; set; }

        [Required, DataType(DataType.Currency)]
        public decimal Valor { get; set; }

        [Required]
        public string Motivo { get; set; }

        public bool Entregue { get; set; }
    }

Quando fiz o update-database, o EF criou uma tabela para os Caixas, uma para CaixaMovimentos, outra para SolicitacoesRecurso e criou também uma tabela que ele nomeou de CaixaCaixaMovimentos que relaciona ID de caixa com ID de CaixaMovimento.E só. Não criou a mesma coisa para Caixas com SolicitacoesRecurso.

Criei caixas sem problemas.

Criei Movimentos sem problemas. Mas eles não se ligam com o Caixa na tal tabela relacional CaixaCaixaMovimento.

Não ficou claro como relacionar Caixas com Movimentos. Pelo visto vou ter de pegar os dois IDs e inserir nessa tabela CaixaCaixaMovimentos. O sistema não faz sozinho já que ele é que entender que havia essa necessidade de relacionar?

E também não ficou claro como vão ser preenchidas aquelas listas que são criadas nos models. A classe Caixa tem uma lista de CaixaMovimento.

Cada vez que cadastro um CaixaMovimento tenho que adicionar ele na lista? Ou tenho que relacionar ele com o Caixa através daquela tabela de relacionamento?

E as Solicitações de Recurso? Tenho que ao criar uma nova SolciitacaoRecurso adicionar ela na lista do Caixa?

Na tabela Caixa há um campo SaldoAtual que eu pretendia que fosse atualizado toda vez que um movimento for adicionado ao Caixa. Então fiz um método no DAO que soma esses movimentos e edita a coluna SaldoAtual do Caixa. O problema é que quando vou gravar essa edição ele reclama uma exceção lançada justamente pela lista de SolicitacoesRecurso (que está vazia) que nada tem a ver com isso.

Com se não pudesse Salvar a edição por que a lista de SolicitacoesRecurso não existe (está vazia) para quele caixa.

Vou colocar abaixo a estrutura das tabelas:

CREATE TABLE [dbo].[Caixas] (
    [ID]                INT             IDENTITY (1, 1) NOT NULL,
    [Nome]              NVARCHAR (30)   NOT NULL,
    [SaldoInicial]      DECIMAL (18, 2) NOT NULL,
    [SaldoAtual]        DECIMAL (18, 2) NOT NULL,
    [ApplicationUserId] NVARCHAR (128)  NOT NULL,
    CONSTRAINT [PK_dbo.Caixas] PRIMARY KEY CLUSTERED ([ID] ASC),
    CONSTRAINT [FK_dbo.Caixas_dbo.AspNetUsers_ApplicationUserId] FOREIGN KEY ([ApplicationUserId]) REFERENCES [dbo].[AspNetUsers] ([Id])
);

GO
CREATE NONCLUSTERED INDEX [IX_ApplicationUserId]
    ON [dbo].[Caixas]([ApplicationUserId] ASC);

CREATE TABLE [dbo].[CaixaMovimentos] (
    [ID]                INT             IDENTITY (1, 1) NOT NULL,
    [Data]              DATETIME        NOT NULL,
    [Historico]         NVARCHAR (200)  NOT NULL,
    [Valor]             DECIMAL (18, 2) NOT NULL,
    [Movimentacao]      INT             NOT NULL,
    [ApplicationUserId] NVARCHAR (128)  NOT NULL,
    CONSTRAINT [PK_dbo.CaixaMovimentos] PRIMARY KEY CLUSTERED ([ID] ASC),
    CONSTRAINT [FK_dbo.CaixaMovimentos_dbo.AspNetUsers_ApplicationUserId] FOREIGN KEY ([ApplicationUserId]) REFERENCES [dbo].[AspNetUsers] ([Id]) ON DELETE CASCADE
);


GO
CREATE NONCLUSTERED INDEX [IX_ApplicationUserId]
    ON [dbo].[CaixaMovimentos]([ApplicationUserId] ASC);

CREATE TABLE [dbo].[SolicitacaoRecursos] (
    [ID]                INT             IDENTITY (1, 1) NOT NULL,
    [Data]              DATETIME        NOT NULL,
    [CaixaID]           INT             NOT NULL,
    [Valor]             DECIMAL (18, 2) NOT NULL,
    [Motivo]            NVARCHAR (MAX)  NOT NULL,
    [Entregue]          BIT             NOT NULL,
    [ApplicationUserId] NVARCHAR (128)  NOT NULL,
    CONSTRAINT [PK_dbo.SolicitacaoRecursos] PRIMARY KEY CLUSTERED ([ID] ASC),
    CONSTRAINT [FK_dbo.SolicitacaoRecursos_dbo.Caixas_CaixaID] FOREIGN KEY ([CaixaID]) REFERENCES [dbo].[Caixas] ([ID]) ON DELETE CASCADE,
    CONSTRAINT [FK_dbo.SolicitacaoRecursos_dbo.AspNetUsers_Usuario_Id] FOREIGN KEY ([ApplicationUserId]) REFERENCES [dbo].[AspNetUsers] ([Id]) ON DELETE CASCADE
);


GO
CREATE NONCLUSTERED INDEX [IX_CaixaID]
    ON [dbo].[SolicitacaoRecursos]([CaixaID] ASC);


GO
CREATE NONCLUSTERED INDEX [IX_ApplicationUserId]
    ON [dbo].[SolicitacaoRecursos]([ApplicationUserId] ASC);

CREATE TABLE [dbo].[CaixaCaixaMovimentos] (
    [Caixa_ID]          INT NOT NULL,
    [CaixaMovimento_ID] INT NOT NULL,
    CONSTRAINT [PK_dbo.CaixaCaixaMovimentos] PRIMARY KEY CLUSTERED ([Caixa_ID] ASC, [CaixaMovimento_ID] ASC),
    CONSTRAINT [FK_dbo.CaixaCaixaMovimentos_dbo.Caixas_Caixa_ID] FOREIGN KEY ([Caixa_ID]) REFERENCES [dbo].[Caixas] ([ID]) ON DELETE CASCADE,
    CONSTRAINT [FK_dbo.CaixaCaixaMovimentos_dbo.CaixaMovimentos_CaixaMovimento_ID] FOREIGN KEY ([CaixaMovimento_ID]) REFERENCES [dbo].[CaixaMovimentos] ([ID]) ON DELETE CASCADE
);


GO
CREATE NONCLUSTERED INDEX [IX_Caixa_ID]
    ON [dbo].[CaixaCaixaMovimentoes]([Caixa_ID] ASC);


GO
CREATE NONCLUSTERED INDEX [IX_CaixaMovimento_ID]
    ON [dbo].[CaixaCaixaMovimentoes]([CaixaMovimento_ID] ASC);
2 respostas

Olá Jaqueline,

a diferença das tabelas que ele criou é justamente pela diferença nas relações entre as classes Caixa, CaixaMovimento e SolicitacoesRecurso.

Entre Caixa e CaixaMovimento, a relação que você criou diz que Caixa tem uma IList<CaixaMovimento> Movimentos e em CaixaMovimento tem uma IList<Caixa> Caixas. Ou seja, uma Caixa pode ter várias CaixaMovimentos e uma CaixaMovimento pode ter várias Caixas. Isso é uma relação no banco de dados conhecida como Many to Many ou Muitos para Muitos. A única forma no banco de representar tal relação é justamente criando uma terceira tabela, no caso CaixaCaixaMovimentos, que tem o id de Caixa e de CaixaMovimento.

Já no caso do entre Caixa e SolicitacoesRecurso a relação é um pouco diferente. No caso, a classe Caixa tem IList<SolicitacaoRecurso> SolicitacoesRecurso, porém a SolicitacaoRecurso tem apenas Caixa Caixa. Ou seja, uma Caixa tem várias SolicitacoesRecurso, mas uma SolicitacoesRecurso tem apenas uma Caixa. Em bancos de dados esta é uma relação Many to One ou Muitos para Um. Para evitar ficar ocupando muito espaço no banco com mais uma tabela, o jeito mais elegante de fazer relações Many to One é apenas guardando na tabela One uma coluna com a foreign key da tabela Many. Neste caso, note que na sua tabela de SolicitacoesRecurso ele criou uma coluna CaixaId para fazer isso:

CREATE TABLE [dbo].[SolicitacaoRecursos] (
    [ID]                INT             IDENTITY (1, 1) NOT NULL,
    [Data]              DATETIME        NOT NULL,
    [CaixaID]           INT             NOT NULL,
    [Valor]             DECIMAL (18, 2) NOT NULL,
    [Motivo]            NVARCHAR (MAX)  NOT NULL,
    [Entregue]          BIT             NOT NULL,
    [ApplicationUserId] NVARCHAR (128)  NOT NULL,
    CONSTRAINT [PK_dbo.SolicitacaoRecursos] PRIMARY KEY CLUSTERED ([ID] ASC),
    CONSTRAINT [FK_dbo.SolicitacaoRecursos_dbo.Caixas_CaixaID] FOREIGN KEY ([CaixaID]) REFERENCES [dbo].[Caixas] ([ID]) ON DELETE CASCADE,
    CONSTRAINT [FK_dbo.SolicitacaoRecursos_dbo.AspNetUsers_Usuario_Id] FOREIGN KEY ([ApplicationUserId]) REFERENCES [dbo].[AspNetUsers] ([Id]) ON DELETE CASCADE
);

Então aparentemente o Entity criou as tabelas certinho, seguindo os padrões adotados em bancos relacionais para casos Many to Many e Many to One

Só que a grande vantagem de se trabalhar com o Entity Framework ou qualquer outro framework ORM é justamente não ter que se preocupar com como as coisas serão armazenadas no banco e ter que trabalhar com estas tabelas que ele cria para representar os relacionamentos.

Vamos fazer o seu exemplo de guardar uma CaixaMovimentacao com uma Caixa dentro dela. Primeiro você cria a Caixa e salva no banco de dados. Isso aparentemente você conseguiu fazer. Então depois de salvar você deveria ter um objeto do tipo Caixa com a propriedade ID preenchida . O próximo passo é apenas criar a CaixaMovimentacao, adicionando na sua IList<Caixa> Caixas o objeto Caixa com o ID preenchido. Quando você salvar a CaixaMovimentacao com a Caixa na sua lista, o próprio Entity vai se virar para armazenar as informações certinho naquela terceira tabela CaixaCaixaMovimentacao. Então você não precisa ter que fazer isso na mão, o entity resolve este problema todo. O mesmo vale para Caixa e SolicitacaoRecursos.

Nesta aula do curso de Entity Framework mostra um exemplo de como trabalhar com uma relação Many to One, igual ao caso de Caixa e SolicitacaoRecursos. E nesta aula do curso de Entity mostra a relação Many to Many igual a Caixa e CaixaMovimentacao.

Salvando as coisas certinho no banco e fazendo as consultas com o Include como mostrado nestas aulas acima, você deve resolver também o problema do SolicitacaoRecursos estar vazia na Caixa.

solução!

Olá, Jaqueline

Na entidade CaixaMovimento que você passou, acho problemática essa linha:

public virtual IList<Caixa> Caixas { get; set; }

Justamente por causa da regra que você descreveu no início: "um Movimento será só de um caixa".

Então acho que é correto trocar essa linha por uma propriedade do tipo Caixa (tal como você fez com SolicitacaoRecurso):

        public virtual Caixa Caixa { get; set; }

O que acha?

Quer mergulhar em tecnologia e aprendizagem?

Receba a newsletter que o nosso CEO escreve pessoalmente, com insights do mercado de trabalho, ciência e desenvolvimento de software