none
Entity Framework Code First - ¿Como mapear dos llaves foraneas a una misma entidad? RRS feed

  • Pregunta

  • Hola, tengo una duda, tengo una entidad Rol y una entidad Usuario, cada usuario tiene asignado un rol pero en la tabla roles quiero tener un el usuario que creo el rol y el que lo modifico, el codigo es el sig:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    using System.ComponentModel.DataAnnotations;
    namespace Entidades
    {
        [Table("Usuarios")]
        public class Usuario
        {
            public Usuario() { }
    
            [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
            public int Id { get; set; }
    
            [StringLength(50), Required]
            public string NombreUsuario { get; set; }
            
            [StringLength(50), Required]
            public string Contrasena { get; set; }
    
            //Llaves foraneas
            public int FKRolId { get; set; }
            [ForeignKey("FKRolId")]
            public virtual Rol Rol { get; set; }
    
            public int? FKPerfilId { get; set; }
            [ForeignKey("FKPerfilId")]
            public virtual Perfil Perfil { get; set; }
    
    
        }
    
    
    }
    
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    using System.ComponentModel.DataAnnotations;
    namespace Entidades
    {
        [Table("Roles")]
        public class Rol 
        {
            public Rol() { }
    
            [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
            public int Id { get; set; }
    
            [StringLength(50), Required]
            public string Nombre { get; set; }
    
            [ConcurrencyCheck, StringLength(200)]
            public string Descripcion { get; set; }
    
            //Enlaces foraneos
            public virtual ICollection<Usuario> Usuarios { get; set; }
    
    
            //test
            public int? FKUsuarioCreacionId { get; set; }
            [ForeignKey("FKUsuarioCreacionId")]
            public virtual Usuario UsuarioCreacion { get; set; }
    
            public int? FKUsuarioEdicionId { get; set; }
            //[ForeignKey("FKUsuarioEdicionId")]
            //public virtual Usuario UsuarioEdicion { get; set; }
    
            [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
            public DateTime FechaCreacion { get; set; }
    
            [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
            public DateTime FechaEdicion { get; set; }
    
    
        }
    }

    Con el codigotodo se genera bien pero en la tabla de Usuarios en la base de datos me agrega una columna llamada Rol_Id, alguien sabe como resolverlo?

    sábado, 14 de abril de 2012 2:23

Todas las respuestas

  • Hola,

    Pero es que tienes que hacerlo con tres clases y la razón es la siguiente un Usuario puede tener varios Roles asociados, el problema es que estás utilizando DataAnotations y eso lógicamente tiene ciertas limitaciones. Te aconsejo que utilices FluentApi.

    Code First Fluent API

    Fijate en el ejemplo que te voy a pasar que tienes que necesitas una tercera tabla que relaciones un usuario con con sus posibles Roles.

    public class Usuario
        {
            public int Id { get; set; }
            public string Nombre { get; set; }
            public virtual ICollection<UsuarioRole> Roles { get; set; }
        }
        public class Rol
        {
            public int Id { get; set; }
            public string Nombre { get; set; }
        }
        public class UsuarioRole
        {
            public int Id { get; set; }
            public int UsuarioId { get; set; }
            public int RolId { get; set; }      
            public Rol Rol { get; set; }
    
        }

    y el contexto de esta forma.

    public class UsuariosContext : DbContext
        {
            public DbSet<Usuario> Usuarios { get; set; }
            public DbSet<Rol> Roles { get; set; }
    
            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
                modelBuilder.Entity<Usuario>().HasMany(c => c.Roles).WithRequired().HasForeignKey(c => c.UsuarioId);
                modelBuilder.Entity<UsuarioRole>().HasRequired(c => c.Rol).WithMany().HasForeignKey(c => c.RolId).WillCascadeOnDelete(false);
            }
        }

    Esto genera la siguiente sentencia sql para crear la bb.dd.

    create table [dbo].[Rol] (
        [Id] [int] not null identity,
        [Nombre] [nvarchar](max) null,
        primary key ([Id])
    );
    create table [dbo].[Usuario] (
        [Id] [int] not null identity,
        [Nombre] [nvarchar](max) null,
        primary key ([Id])
    );
    create table [dbo].[UsuarioRole] (
        [Id] [int] not null identity,
        [UsuarioId] [int] not null,
        [RolId] [int] not null,
        primary key ([Id])
    );
    alter table [dbo].[UsuarioRole] add constraint [Usuario_Roles] foreign key ([UsuarioId]) references [dbo].[Usuario]([Id]) on delete cascade;
    alter table [dbo].[UsuarioRole] add constraint [UsuarioRole_Rol] foreign key ([RolId]) references [dbo].[Rol]([Id]);
    

    No se si esto cubre tus necesidades, sino es así comentalo y vemos otra alternativa, pero así es como yo veo Usuarios y Roles, es decir un usuario puede pertenecer a varios Roles.

    Si lo que quieres controlar es el Usuario que modifico el Rol, no tienes más que agregar a UsuarioRole un usuario y configurarlo igual  como yo he hecho con Roles.

    Te paso un ejemplo.

     public class UsuariosContext : DbContext
        {
            public DbSet<Usuario> Usuarios { get; set; }
            public DbSet<Rol> Roles { get; set; }
    
            protected override void OnModelCreating(DbModelBuilder modelBuilder)
            {
                modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
                modelBuilder.Entity<Usuario>().HasMany(c => c.Roles).WithRequired().HasForeignKey(c => c.UsuarioId);
                modelBuilder.Entity<UsuarioRole>().HasRequired(c => c.Rol).WithMany().HasForeignKey(c => c.RolId).WillCascadeOnDelete(false);
                modelBuilder.Entity<UsuarioRole>().HasRequired(c => c.Usuario).WithMany().HasForeignKey(c => c.UsuarioUpdateId).WillCascadeOnDelete(false);
            }
        }
        public class Usuario
        {
            public int Id { get; set; }
            public string Nombre { get; set; }
            public virtual ICollection<UsuarioRole> Roles { get; set; }
        }
        public class Rol
        {
            public int Id { get; set; }
            public string Nombre { get; set; }
        }
        public class UsuarioRole
        {
            public int Id { get; set; }
            public int UsuarioId { get; set; }
            public int RolId { get; set; }      
            public Rol Rol { get; set; }
    
            public int UsuarioUpdateId { get; set; }
            public Usuario Usuario { get; set; }
    
        }

    y esto es lo que te genera en bb.dd

    create table [dbo].[Rol] (
        [Id] [int] not null identity,
        [Nombre] [nvarchar](max) null,
        primary key ([Id])
    );
    create table [dbo].[Usuario] (
        [Id] [int] not null identity,
        [Nombre] [nvarchar](max) null,
        primary key ([Id])
    );
    create table [dbo].[UsuarioRole] (
        [Id] [int] not null identity,
        [UsuarioId] [int] not null,
        [RolId] [int] not null,
        [UsuarioUpdateId] [int] not null,
        primary key ([Id])
    );
    alter table [dbo].[UsuarioRole] add constraint [Usuario_Roles] foreign key ([UsuarioId]) references [dbo].[Usuario]([Id]) on delete cascade;
    alter table [dbo].[UsuarioRole] add constraint [UsuarioRole_Rol] foreign key ([RolId]) references [dbo].[Rol]([Id]);
    alter table [dbo].[UsuarioRole] add constraint [UsuarioRole_Usuario] foreign key ([UsuarioUpdateId]) references [dbo].[Usuario]([Id]);

    Como consejo yo me plantearía utilizar FluentApi y si puedes olvidarte de las DataAnotations.

    Saludos,



    phurtado
    Mi Blog Blog
    Sigueme en Twitter

    sábado, 14 de abril de 2012 10:14
  • Gracias por el aporte pero no siempre suelo usar datanotations solo en este caso me interesa que sea con datanotations y no con fluent.

    Si entiendo que si un usuario puede tener muchos roles entonces hay una 3 tabla pero en este caso no, cada usuario unicamente puede tener un rol.

    Tengo la tabla roles, usuarios y muchas mas, pero en todas las tablas o almenos en muchas quiero guardar un historico de el usuario que creo el registro, la fecha en la que se creo el registro, el usuario que modifico y la fecha de modificacion, uso datanotation y ademas en algunos casos uso fluent api, la razon por la que me interesa resolverlo con datanotations es por que si en 50 clases voy a tener el registro del historico no me gustaria tener que crear mucho codigo y pues un ejemplo tengo la clase Historico

        public class HistoricoCompletoNull
        {
            public int? FKUsuarioCreacionId { get; set; }
            [ForeignKey("FKUsuarioCreacionId")]
            public virtual Usuario UsuarioCreacion { get; set; }
    
            public int? FKUsuarioEdicionId { get; set; }
            [ForeignKey("FKUsuarioEdicionId")]
            public virtual Usuario UsuarioEdicion { get; set; }
    
            [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
            public DateTime FechaCreacion { get; set; }
            
            [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
            public DateTime FechaEdicion { get; set; }
        }

    y tengo la clase Roles asi:

        [Table("Roles")]
        public class Rol 
        {
            public Rol() { }
    
            [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
            public int Id { get; set; }
    
            [StringLength(50), Required]
            public string Nombre { get; set; }
    
            [ConcurrencyCheck, StringLength(200)]
            public string Descripcion { get; set; }
    
            //Enlaces foraneos
            public virtual ICollection<Usuario> Usuarios { get; set; }
    
    
        }
    

    Ahora si quiero que esa clase tenga el registro pues solo le digo que herede de el historico y listo.

    public class Rol : Core.HistoricoCompletoNull

    Que es como si yo tubiera mi clase rol asi:

        public class Rol
        {
            public Rol() { }
    
            [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]
            public int Id { get; set; }
    
            [StringLength(50), Required]
            public string Nombre { get; set; }
    
            [ConcurrencyCheck, StringLength(200)]
            public string Descripcion { get; set; }
    
            //Enlaces foraneos
            public virtual ICollection<Usuario> Usuarios { get; set; }
    
    
            public int? FKUsuarioCreacionId { get; set; }
            [ForeignKey("FKUsuarioCreacionId")]
            public virtual Usuario UsuarioCreacion { get; set; }
    
            public int? FKUsuarioEdicionId { get; set; }
            [ForeignKey("FKUsuarioEdicionId")]
            public virtual Usuario UsuarioEdicion { get; set; }
    
            [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
            public DateTime FechaCreacion { get; set; }
            
            [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
            public DateTime FechaEdicion { get; set; }
    
    
    
    
        }

    Cuando se crean las bases de datos la tabla roles queda bien

    Pero en la tabla Usuarios se crea un campo que se llama Rol_Id y no se por que.

    sábado, 14 de abril de 2012 15:59
  • Hola,

    Pero en la tabla Usuarios se crea un campo que se llama Rol_Id y no se por que. 

    Pero no dices que el usuario pertenece a un rol, lo normal es que esta tengas una fk a Roles.

    Revista este link y para el tema del historico lo puedes utilizar y de esa forma no tienes que escribir mucho:)

    http://weblogs.asp.net/manavi/archive/2011/03/28/associations-in-ef-4-1-code-first-part-2-complex-types.aspx 

    Saludos,


    phurtado
    Mi Blog Blog
    Sigueme en Twitter

    sábado, 14 de abril de 2012 16:29
  • Hola necesito dos fk a una misma tabla, ejemplo:

    Cliente y ClienteUsuario hacen referencia a una misma tabla Usuarios.

    Las FK en la BD no dan problema.

    Pero en el modelo no puedo ponerlo siguiente ya que da error.

        <ForeignKey("Cliente")>
        <Required()>
        Public Property IdCliente As Integer
        Public Property Cliente As Cliente

        <ForeignKey("Cliente")>
        <Required()>
        Public Property IdClienteUsuario As Integer
        Public Property ClienteUsuario As Cliente

    Me podrían ayudar con esto?

    Gracias

    jueves, 25 de octubre de 2012 16:45