2011-12-16 9 views
8

En .NET Entity Framework, ¿cuál es la mejor manera de tener una tabla de combinación (personalizada) con atributos adicionales (distintos de los identificadores) y/o asociar esta tabla de unión con otros a través de un modelo separado? En Ruby on Rails podemos tener un modelo para la tabla de unión, como:Rails has-many-through equivalent en ASP.NET MVC3

Item.rb (model) 
:has_many => :buyers, :through=>:invoice 
... 

Buyers.rb (model) 
:has_many => :items, :through=>:invoice 
... 

Invoice.rb (model) 
:belongs_to :item 
:belongs_to :buyer 
.... 

entonces podemos utilizar: Item.first.buyers, Buyers.first.items y Buyer.create(:items=>Item.create(:name=>'random')) etc al igual que cuando nos unen uso automatizado tabla sin modelo (utilizando has_and_belongs_to_many).

En el cuadro de diálogo "Agregar asociación" de Visual Studio 2010, si seleccionamos multiplicidad como * (Muchos) no hay opción para seleccionar una tabla de unión (con modelo). ¿Hay alguna manera de hacerlo manualmente?

Respuesta

6

Sí, puede obtener algo muy cerca. No estoy seguro de cómo configurar esto en el diseñador ya que solo trabajo con Codefirst.

He aquí un ejemplo:

Estudiante -> StudentFloor < - Planta

public class Student 
{ 
    public int Id { get; set; } 
    // ... properties ... 

    // Navigation property to your link table 
    public virtual ICollection<StudentFloor> StudentFloors { get; set; } 

    // If you wanted to have a property direct to the floors, just add this: 
    public IEnumerable<Floor> Floors 
    { 
     get 
     { 
      return StudentFloors.Select(ft => ft.Floor); 
     } 
    } 
} 

La tabla de vinculación:

public class StudentFloor 
{ 
    #region Composite Keys 

    // Be sure to set the column order and key attributes. 
    // Convention will link them to the navigation properties 
    // below. The database table will be created with a 
    // compound key. 

    [Key, Column(Order = 0)] 
    public int StudentId { get; set; } 

    [Key, Column(Order = 1)] 
    public int FloorId { get; set; } 

    #endregion 

    // Here's the custom data stored in the link table 

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

    [Required] 
    public DateTime Checkin { get; set; } 

    // Navigation properties to the outer tables 
    [Required] 
    public virtual Student Student { get; set; } 

    [Required] 
    public virtual Floor Floor { get; set; } 

} 

Por último, al otro lado de los muchos-a-muchos:

public class Floor 
{ 
    public int Id { get; set; } 
    // ... Other properties. 

    public virtual ICollection<StudentFloor> StudentFloors { get; set; } 
} 
+0

Gracias por su entrada. Por favor revisa la sección de actualización y avísame si el código en el controlador puede hacerse más delgado y elegante. –

+0

Estoy buscando la sección de actualización con un controlador que mencionas ... ¿no lo encuentras? – Leniency

+0

Eso es malo. Parece que ha sido rechazado por otros usuarios, aunque es solo una continuación de su respuesta. Déjame ponerlo en una respuesta separada. –

0

ACTUALIZACIÓN a la respuesta de Leniency:

También podemos crear dos relaciones uno a muchos con el primer enfoque del modelo. De cualquier manera, no podemos tener el enlace del modelo como ocurre en la relación pura de M2M (sin tablas de carga útil o tablas de combinación pura - PJT).

Además, en el controlador (andamio), podemos utilizar un modelo de vista para las operaciones CRUD según el requisito. Supuestamente, tenemos una FloorViewModel con la siguiente definición:

public class FloorViewModel 
{ 
    private Model2Container context = new Model2Container(); 

    [Display(Name = "Student List")] 
    [Required(ErrorMessage = "Please select atleast one student from the list.")] 
    public int[] MyStudents { get; set; } 

    public Floor MyFloor { get; set; } 

    public MultiSelectList StudentsList { get; set; } 

    public StudentFloorJoin Join { get; set; } 

} 

La acción de crear en el controlador sería:

// 
// GET: /Floor/Create 

public ActionResult Create() 
{ 
    var model = new FloorViewModel() { StudentsList = new MultiSelectList(context.Students, "Id", "Name") }; 
    return View(model); 
} 

// 
// POST: /Floor/Create 

[HttpPost] 
public ActionResult Create(FloorViewModel floor) 
{ 
    if (ModelState.IsValid) 
    { 
     context.Floors.Add(floor.MyFloor); 
     context.SaveChanges(); 
    } 
    foreach (var student in floor.MyStudents) 
    { 
     context.StudentFloorJoins.Add(new StudentFloorJoin() { Student = context.Students.Find(student), Floor = floor.MyFloor, Room = floor.Join.Room }); 
    } 
    if (ModelState.IsValid) 
    { 
     context.SaveChanges(); 
     return RedirectToAction("Index"); 
    } 
    context.Floors.Remove(floor.MyFloor); 
    floor.StudentsList = new MultiSelectList(context.Students, "Id", "Name", floor.MyStudents); 
    return View(floor); 
} 

y la acción Editar sería algo así como:

// 
// GET: /Floor/Edit 

public ActionResult Edit(int id) 
{ 
    Floor floor = context.Floors.Single(x => x.Id == id); 
    int[] ids = (from i in floor.StudentFloorJoins select i.Student.Id).ToArray(); 
    var model = new FloorViewModel() { StudentsList = new MultiSelectList(context.Students, "Id", "Name", ids), MyFloor = floor, Join = new StudentFloorJoin() { Room = floor.StudentFloorJoins.Count == 0 ? "" : floor.StudentFloorJoins.First().Room } }; 
    return View(model); 
} 

// 
// POST: /Floor/Edit 

[HttpPost] 
public ActionResult Edit(FloorViewModel floor) 
{ 
    if (ModelState.IsValid) 
    { 
     var item = floor.MyFloor; 
     var itemEntry1 = context.Entry<Floor>(item); 
     itemEntry1.State = EntityState.Modified; 
     var query = (from i in context.StudentFloorJoins where i.Floor.Id == item.Id select i.Id); 
     foreach (var studentfloor in query) 
     { 
      context.StudentFloorJoins.Remove(context.StudentFloorJoins.Find(studentfloor)); 
     } 
     context.SaveChanges(); 

     foreach (var student in floor.MyStudents) 
     { 
      context.StudentFloorJoins.Add(new StudentFloorJoin() { Student = context.Students.Find(student), Floor = floor.MyFloor, Room = floor.Join.Room }); 
     } 
     context.SaveChanges(); 
     return RedirectToAction("Index"); 
    } 
    floor.StudentsList = new MultiSelectList(context.Students, "Id", "Name", floor.MyStudents); 
    return View(floor); 
} 

En Ver, podemos enviar el objeto de FloorModelView como:

@model ManyToManyAutoGen.Models.FloorViewModel 

@{ 
    ViewBag.Title = "Create"; 
} 

<h2>Create</h2> 

<script src="@Url.Content("~/Scripts/jquery.validate.min.js")" type="text/javascript"></script> 
<script src="@Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js")" type="text/javascript"></script> 

@using (Html.BeginForm()) { 
    @Html.ValidationSummary(true) 
    <fieldset> 
     <legend>Floor</legend> 

     @Html.Partial("_CreateOrEdit", Model) 

     <p> 
      <input type="submit" value="Create" /> 
     </p> 
    </fieldset> 
} 

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

y, finalmente, _CreateOrEdit miradas parciales como:

@model ManyToManyAutoGen.Models.FloorViewModel 

@* This partial view defines form fields that will appear when creating and editing entities *@ 

<div class="editor-label"> 
    @Html.LabelFor(model => model.MyFloor.FloorName) 
</div> 
<div class="editor-field"> 
    @Html.EditorFor(model => model.MyFloor.FloorName) 
    @Html.ValidationMessageFor(model => model.MyFloor.FloorName) 
</div> 

<div class="editor-label"> 
    @Html.LabelFor(model => model.MyStudents) 
</div> 
<div class="editor-field"> 
    @Html.ListBoxFor(model => model.MyStudents, Model.StudentsList) 
    @Html.ValidationMessageFor(model => model.MyStudents) 
</div> 

<div class="editor-label"> 
    @Html.LabelFor(model => model.Join.First().Room) 
</div> 
<div class="editor-field"> 
    @Html.EditorFor(model => model.Join.First().Room) 
    @Html.ValidationMessageFor(model => model.Join) 
</div>