0

I am building a web application using ASP .NET MVC5 and EF6. I have an one-to-one relationship defined as follows:

public class Client 
{
  public int ClientId { get; set; }  
  [Required]
  public string Name { get; set; }

  public Address Address { get; set; }
}

public class Address 
{
  [ForeignKey("Client")]
  public int AddressId { get; set; }  
  [Required]
  public string StreetName { get; set; }

  public Client Client { get; set; }
}

Where Address is the dependent end. After enabling Migrations and updating the database, the two tables are created, in which, the Address table contains a ClientId column, as a result of the Foreign Key.

I then proceeded to generate the Controllers and Views for these classes.

For the Address, I'm trying to modify the Create/Edit methods and views to be able to add a new address and associate it to an existing Client, which is exactly what is done in this tutorial:

https://learn.microsoft.com/en-us/aspnet/mvc/overview/getting-started/getting-started-with-ef-using-mvc/updating-related-data-with-the-entity-framework-in-an-asp-net-mvc-application

My AddressController was customized exactly as the tutorial suggests as well as the respective views. But upon accessing the Create page while running the web app, I am able to write a street name and choose from a drop down list a name for the client (which is what I want, display the client name and not the Id).

When hitting the create button, I get the following error:

The INSERT statement conflicted with the FOREIGN KEY constraint "FK_dbo.Addresses_dbo.Clients_AddressId". The conflict occurred in database "myDb", table "dbo.Clients", column 'ClientId'. The statement has been terminated.

This leads me to believe that the Foreign Key ClientId is not being updated as it was supposed to be by selecting a name in the drop down list. I tried adding new clients without addresses associated to them but even by selecting a client without an associated address the error persists.

I also know that, ideally, the drop down list should actually only show the clients without any address associated to them but I don't know how to accomplish this.

Because I followed every step of the mentioned tutorial, I don't really know what I am doing wrong.

Any help would be extremely appreciated! If there is a need to post more code, I will do it, just didn't do it in order to shorten this already long post.

Update Code

AddressesController.cs

public class AddressesController : Controller
    {
        private ApplicationDbContext db = new ApplicationDbContext();

        // GET: Addresses
        public ActionResult Index()
        {
            var addresses = db.Addresses.Include(m => m.Client);
            return View(addresses.ToList());
        }

        // GET: Addresses/Details/5
        public ActionResult Details(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            Address address = db.Addresses.Find(id);
            if (address == null)
            {
                return HttpNotFound();
            }
            return View(address);
        }

    // GET: Address/Create
        public ActionResult Create()
        {
            PopulateClientsDropDownList(); 
            return View();
        }

        // POST: Addresses/Create
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Create([Bind(Include = "AddressId,StreetName,ClientId")] Address address)
        {
            try
            {
                if (ModelState.IsValid)
                {

                    db.Addresses.Add(addresses);
                    db.SaveChanges();
                    return RedirectToAction("Index");
                }
            }
            catch (DataException dex)
            {

                ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
                ErrorSignal.FromCurrentContext().Raise(dex);
            }
            PopulateClientsDropDownList(address.ClientId);
        return View(address);
        }

    // GET: Addresses/Edit/5
        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }

        Address address = db.Address.Find(id);
            if (address == null)
            {
                return HttpNotFound();
            }
            PopulateClientsDropDownList(address.ClientId); 

            return View(address);
        }

        // POST: Addresses/Edit/5
        // To protect from overposting attacks, please enable the specific properties you want to bind to, for 
        // more details see http://go.microsoft.com/fwlink/?LinkId=317598.
        [HttpPost, ActionName("Edit")]
        [ValidateAntiForgeryToken]
        public ActionResult EditPost(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }
            var addressToUpdate = db.Addresses.Find(id);
            if (TryUpdateModel(addressToUpdate, "",
                new string[] { "StreetName", "ClientId" }))
            {
                try
                {
                    db.SaveChanges();
                    return RedirectToAction("Index");
                }
                catch (RetryLimitExceededException dex)
                {
                    ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists, see your system administrator.");
                    ErrorSignal.FromCurrentContext().Raise(dex);
                }
            }
            PopulateAddressesDropDownList(addressToUpdate.ClientId);
            return View(addressToUpdate);
        }

    private void PopulateClientsDropDownList(object selectedClient = null)
        {
            var clientsQuery = from d in db.Clients
                               orderby d.Name
                               select d;
            ViewBag.ClientId = new SelectList(clientsQuery, "ClientId", "Name", selectedClient);
        }
}

Create.cshtml (Shortened)

@using (Html.BeginForm())
{
    @Html.AntiForgeryToken()

    <div class="form-horizontal">
        <h4>Addresses</h4>
        <hr />
        @Html.ValidationSummary(true, "", new { @class = "text-danger" })
        @*<div class="form-group">
                @Html.LabelFor(model => model.AddressId, "AddressId", htmlAttributes: new { @class = "control-label col-md-2" })
                <div class="col-md-10">
                    @Html.DropDownList("AddressId", null, htmlAttributes: new { @class = "form-control" })
                    @Html.ValidationMessageFor(model => model.AddressId, "", new { @class = "text-danger" })
                </div>
            </div>*@

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

        <div class="form-group">
            <label class="control-label col-md-2" for="ClientId">Client Name</label>
            <div class="col-md-10"> 
                @Html.DropDownList("ClientId", null, htmlAttributes: new { @class = "form-control" })
                @Html.ValidationMessageFor(model => model.ClientId, "", new { @class = "text-danger" })
            </div>
        </div>

        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}
Sfmar
  • 159
  • 1
  • 13
  • It may be that the correct, or any, `ClientID` is not being added to the new address.Without seeing the update code it's hard to know. – Peter Smith Oct 24 '18 at 16:19
  • Is not good idea to add the foreign key with attributes. maybe you can use fluent Api. – Llazar Oct 24 '18 at 16:20
  • @PeterSmith which code do you need exactly? Should I only post the controller's code or also the Create's view? – Sfmar Oct 24 '18 at 16:28
  • Can you show the code where you add the new address to the database and therefore add the `ClientID` – Peter Smith Oct 24 '18 at 16:40
  • @PeterSmith just updated my question with the rest of the code. – Sfmar Oct 24 '18 at 16:45
  • 1
    You need to trace your code for the `ActionResult Create` and check to see what values are coming back. I suspect that the problem is in the returned values. – Peter Smith Oct 24 '18 at 17:27
  • Could be an issue where AddressId is both PK and required FK which is [technically impossible](https://stackoverflow.com/questions/10292355/how-do-i-create-a-real-one-to-one-relationship-in-sql-server/10292449) in SQL Server. Try one of the workarounds like [this](http://www.entityframeworktutorial.net/code-first/configure-one-to-one-relationship-in-code-first.aspx). When you get further along I would consider using viewmodels that hold everything rather than ViewBag and bind statements. – Steve Greene Oct 24 '18 at 20:02
  • @SteveGreene just checked the workarounds you mentioned but I believe that was exactly what I did, by using Data Annotations. And I'm getting really confused because according to that site: **"A one-to-zero-or-one relationship happens when a primary key of one table becomes PK & FK in another table in a relational database such as SQL Server.** So, doesn't this contradict what you are saying about AddressId being both PK and FK? – Sfmar Oct 25 '18 at 09:15
  • @Sfmar why do you have Foreign key annotation on PK AddressId? Change your model, add **Id** property in Address class that will be your PK. Or try with nullable FK, add **int?** with AddressId property. Although, that's not the way you should create your models. Always have separate primary key and foreign key properties. – SushiDynamite Oct 26 '18 at 07:52
  • @SushiDynamite I followed the guidelines provided by Entity Framework on how to configure one-to-zero-or-one relationship using data annotation attributes. If you check this url [link](http://www.entityframeworktutorial.net/code-first/configure-one-to-one-relationship-in-code-first.aspx), this is exactly what they do. – Sfmar Oct 26 '18 at 09:38
  • @Sfmar you forgot **virtual** keyword as I can see from your code and one from link. – SushiDynamite Oct 26 '18 at 11:19
  • 1
    @SushiDynamite **virtual** only enables lazy loading, which should not have impact on the problem I'm having. But anyway, I also tried with **virtual** and results are the same, unfortunately. – Sfmar Oct 26 '18 at 11:26
  • @Sfmar sry, you are right about virtual keyword, totally forgot that one :) edit: did you try mapping tables using Fluent API? – SushiDynamite Oct 26 '18 at 11:37

0 Answers0