0

I'm a beginner in C# and I would like some help. I'm working on a web site project with ASP.NET MVC and Entity Framework 6. So far, I never have to ask for help but I reached a deadlock.

My project's purpose is to list documents stored in a folder, register them in a database, and display every documents on a web page with a sortable table. Each document has a 'Referentiel' (a department or a categorie if you prefer) and should be editable. This is where I'm stuck. I made a view which contains a form to edit each field of a document. But, my model has a virtual attribute "Referentiel" which can't be update as easily as string attributes (the model fits the database with those attributes).

Here's my models, I've got Documents which own a Referentiel and Referentiels which own many Documents : a one-to-many relation. I implemented it with a list of Documents in the class Referentiel and a Referentiel attribute in Document.

public class Document
{
    public int Id { get; set; } // unique identifier
    public string Code { get; set; } // identifier of a document inside the company
    public string Titre { get; set; } // name of the saved file (pdf, word...)
    public string Type { get; set; } // specify the nature of the document
    public int Ref_Id {get; set;} // foreign identifier with the linked entity in a one-to-many relation.
    [ForeignKey("Id_Ref")]
    public virtual Referentiel Referentiel { get; set; } // company's department where the document belongs to. 
}

public class Referentiel
{
    public int Id { get; set; } // unique identifier
    public string Nom { get; set; } // name of a company's department
    public virtual List<Document> Documents { get; set; } // collection of object Document (N.B. : doesn't appear in the database)
}

I had to use a ViewModel to access both models in my view, so I made DocFormViewModel :

public class DocFormViewModel
{
    public List<Models.Referentiel> Referentiels { get; set; }
    public Models.Document Doc { get; set; }

    [Display(Name = "Référentiel")]
    public int SelectedReferentielId { get; set; }
}

My controller has two methods, one for GET requests and one for POST requests.

// GET: Documents/Modifier/5
    [HttpGet]
    public ActionResult Modifier(int? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }

        vm.Doc = db.Documents.Find(id);

        if (vm.Doc == null)
        {
            return HttpNotFound();
        }
        return View(vm);
    }

    // POST: Documents/Modifier/5
    [HttpPost]
    [ValidateAntiForgeryToken]
    public ActionResult Modifier([Bind(Include = "Id,Code,Titre,Type,Referentiel")] Document doc)
    {
        if (ModelState.IsValid)
        {
            db.Entry(doc).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index", "Accueil");
        }

        DocFormViewModel documentReferentielViewModel = new DocFormViewModel
        {
            Doc = db.Documents.Find(doc.Id),
            Referentiels = db.Referentiels.ToList(),
            SelectedReferentielId = doc.Referentiel.Id,

        };
        ViewBag.referentiel_Id = new SelectList(db.Referentiels.ToList(), "Id", "Referentiels", doc.Referentiel.Id);

        return View(documentReferentielViewModel);
    }

Finally, I use a dropdownList to collect existing Referentiel. The selected Referentiel would be chose for updating the edited Document. But when I submit the form my document keep its default Referentiel (In the database and in the view)

@model BaseDoc_OI_GRC.ViewModels.DocFormViewModel

<!--Some html-->
@using (Html.BeginForm())
{
    <div class="form-group">
        @Html.LabelFor(model => model.SelectedReferentielId, htmlAttributes: new { @class = "control-label col-md-2" })
        <div class="col-md-10">        
            @Html.DropDownListFor(m => m.Doc.Referentiel.Id, Model.ReferentielItems)
                @Html.ValidationMessageFor(m => m.Doc.Referentiel, "", new { @class = "text-danger" })
        </div>
    </div>

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

I hope you understood me, I'm not a native speaker but I tried my best to stay clear. (some variable name are in French, it shouldn't be a concern but ask me if you want a better translation).

Thanks for reading, I would be very grateful for any solution. ;)

  • Try to add `db.Entry(doc.Referentiel).State = EntityState.Modified;` before `db.SaveChanges();` You may need to flag the child entity as modified as well. – Eric Apr 22 '16 at 16:37
  • @Eric I thought about that too but didn't try. So, I changed my code but it throws an exception saying : `Attaching an entity of type 'BaseDoc.Models.Referentiel failed because another entity has the same primary key value.` If I ignore model's validation, it simply insert another model Referentiel into the database rather than updating each model involved. –  Apr 22 '16 at 20:57
  • There are multiple issues with your code. First your `Document` does not even appear to have a property to define the relationship (e.g. `int ReferentielId`). Next you have a view model that contains a data model (never do that and suggest you read [What is ViewModel in MVC?](http://stackoverflow.com/questions/11064316/what-is-viewmodel-in-mvc)). Next your `Modifier()` GET method returns an instance of `Document` to the view, but the view you have shown has `@model DocFormViewModel` so that would throw and exception. –  Apr 23 '16 at 08:15
  • Next your binding the dropdown to Doc.Referentiel.Id` but it needs to be `SelectedReferentielId` (ditto for `ValidationMessageFor()`) and the second parameter is `Model.ReferentielItems` which does not exist. Then you POST method has parameter `Document doc` when it needs to be `DocFormViewModel doc` and the list goes on. Its fairly clear you have not even shown the correct code because what you have shown would be throwing exceptions. –  Apr 23 '16 at 08:21

1 Answers1

0

I fixed my issue yesterday. Thanks for your answers. I just used a method in my Dal class (Data Access Layer) for linking an entity Document to an entity Referentiel. It is much more easier this way and nothing throws exception (even if it didn't throw any before).

To sum up my new Modifier or "Edit" method in DocumentsController :

[HttpPost]
public ActionResult Modifier(DocFormViewModel viewModel){
    dal.ModifierType(viewModel.Doc.Id, viewModel.SelectedTypeString);
    dal.AjouterDocumentAReferentiel(viewModel.Doc.Id, viewModel.SelectedReferentielId); // English : AddDocumentToReferentiel(id_Document, id_Referentiel)
    return RedirectToAction("Index", "Accueil");
}

Here is a sample of the view with corrected parameters :

<!--some-html-->
@using(Html.BeginForm())
{
    <!--some-html-->
    <div class="form-group">
        @Html.LabelFor(model => model.SelectedReferentielId, htmlAttributes: new {@class = "control-label col-md-2" })
        @Html.DropDownListFor(model => model.SelectedReferentielId, Model.ReferentielItems)
        @Html.ValidationMessageFor(model => model.SelectedReferentielId, "", new { @class = "text-danger" })
    </div>
}

My Dal class has methods to select and update entities, such as :

// Dal implements an interface IDal which extends IDisposable
public class Dal : Idal 
{
    private BddContext bdd; // BddContext extends DbContext with DbSet<Document> Documents and DbSet<Referentiel> Referentiels attributes

    // there are some attributes to fetch files from a folder but there're irrelevant to this issue so I skip them and their methods.
    // Obviously there are a lot more of method to edit, create, remove,
    // fetch or test existence in database/folder

    /**
     * This is the method I used in DocumentController instead of 
     * a direct access to the database with BddContext, now Dal does it instead
     */
    public void AjouterDocumentAReferentiel(int idDoc, int idRef)
    {
        Document document = bdd.Documents.FirstOrDefault(doc => doc.Id == idDoc);
        Referentiel referential = bdd.Referentiels.FirstOrDefault(r => r.Id == idRef)
        referential.Documents.Add(document);
        bdd.SaveChanges();
    }
}

Finally, this is the final result : (screenshots of both primary pages)

Homepage where every documents in a folder are registered in the database Edit page for document with id = 1