0

I have been searching all over the place, found many questions but neither of them answer my.

Thing I am trying to do is simple, let's say we have something like that:

public class Parent
{
    public int ID { get; set; }

    public List<Child> Children { get; set; }
}

public class Child
{
    public int ID { get; set; }

    public int ParentID { get; set; }
    public Parent Parent { get; set; }
}

EF takes care of creating db and via scaffolding I get controllers and views. Everything works fine, I can create Parent. I can also create Child with ParentID chosen from drop down.

What I really want to do is remove drop down and pass ParentID as parameter like that:

    public IActionResult Create(int id)
    {
        if (id == 0)
        {
            return NotFound();
        }

        ViewData["ID"] = id; //that a ParentID
        return View();
    }

And in view we got this:

<form asp-action="Create">
    <div class="form-horizontal">
        <h4>Child</h4>
        <hr />
        <div asp-validation-summary="ModelOnly" class="text-danger"></div>
        <input type="hidden" asp-for="ParentID" value=@ViewData["ID"] />
        <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>
</form>

When submitting this I got "Cannot insert explicit value for identity column in table 'Children' when IDENTITY_INSERT is set to OFF." Now I looked all over the place and tried various attributes yet still I can't get this to work.

I will be grateful for any sort of help.


Update

Create method from controller:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create([Bind("ID,ParentID")] Child child)
    {
        if (ModelState.IsValid)
        {
            _context.Add(child);
            await _context.SaveChangesAsync();
            return RedirectToAction("Index");
        }
        return View(book);
    }

Just to show more about problem. Here I am using default scaffolded version with select control in view.

    public IActionResult Create(int id)
    {
        if (id == 0)
        {
            return NotFound();
        }

        ViewData["ParentID"] = new SelectList(_context.Parents.Where(x => x.ID == 1), "ID", "ID"); //Works
        ViewData["ParentID"] = new SelectList(_context.Parents.Where(x => x.ID == id), "ID", "ID"); // Doesn't work
        return View();
    }
Bielik
  • 922
  • 2
  • 14
  • 25
  • Can you post the code that composes the actual `Child` object? The error seems to indicate that you are trying to explicitly set a value for a field that EF has marked as an identity columns. Unless it scaffolded incorrectly, `ParentID` shouldn't be an `Identity` column in `Child` – stephen.vakil Sep 08 '16 at 17:33
  • @stephen.vakil Thank you for your respond, tomorrow I will be at work and I will be able to post it but I didn't change anything apart from what I showed. It throws exception at this line "await _context.SaveChangesAsync();". – Bielik Sep 08 '16 at 17:50
  • store the parentID as hidden on the view inside the form and by this it will return with the post – Monah Sep 08 '16 at 19:19
  • @HadiHassan I don't understand... I think I already do that and parameter goes well into controller, gets through model validation and throws exception when saving changes to db. – Bielik Sep 08 '16 at 19:27
  • I didn't look to your question details deeply, is the ID field marked as DatabaseGenerated as Identity? – Monah Sep 08 '16 at 19:56
  • @HadiHassan This is code first, all I have is showed here, so It's not marked explicitly. I have tried different configuration but I can't say for sure If I did this one. If you could please expand this idea what attributes which properties should exactly have. – Bielik Sep 08 '16 at 20:03
  • which version of EF are you using. I would remove the navigation properties on both parent and child first to see if that works. – devfric Sep 09 '16 at 10:52
  • @firste It does work, just to show you why this is confusing I will update question. As for EF it's EntityFramework Core. – Bielik Sep 09 '16 at 10:57
  • @HadiHassan I checked and ID is IdentityColumn. – Bielik Sep 09 '16 at 10:57
  • EF Core 1.0 has some issues, you can use it just have to figure out another way. For safety I would add `[ForeignKey("ParentID")]` in front of `public Parent Parent { get; set; }` then I would first try add the word `virtual` in front of the Children Collection in parent. If that doesn't work I would remove the Children collection from Parent and create a linq method to load any children via a separate method – devfric Sep 09 '16 at 17:26
  • @firste virtual keyword didn't do anything, haven't tried linq yet but I think I have found dirty work around. I will post it as answer. – Bielik Sep 10 '16 at 11:42

1 Answers1

0

After talking with people in comments I start to think that problem is caused by a bug in EF Core but I think I have found 'dirty' work around.

Here is Child model:

public class Child
{
    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int ID { get; set; }

    [DatabaseGenerated(DatabaseGeneratedOption.None)]
    public int ParentID { get; set; }
    [ForeignKey("ParentID")]
    public Parent Parent { get; set; }
}

Now (for some reason) this solves "Cannot insert explicit value for identity column in table 'Children' when IDENTITY_INSERT is set to OFF." issue but creates new, we don't get unique id. I fixed that by making it myself, here is create method:

    [HttpPost]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Create([Bind("ID,ParentID")] Child child)
    {
        if (ModelState.IsValid)
        {
            int _id;
            do
            {
                _id = new Random().Next();
            } while (await _context.Children.Where(b => b.ID == _id).AnyAsync());

            child.ID = _id;
            _context.Add(child);
            await _context.SaveChangesAsync();
            return RedirectToAction("Index");
        }
        ViewData["ID"] = id;
        return View(child);
    }

Now doWhile makes sure that we don't assign the same id to two different objects. Alternatives to Random option are to use Guid (when id is string) or GetHashCode (and increment in case of collision perhaps).

Bielik
  • 922
  • 2
  • 14
  • 25