1

I work on an Entity Framework 6.1.3, ASP.NET MVC 5, Code First with an existing databse project. My database (SQL Server 2016) includes a table that has a “rowguid” (uniqueidentifier, ROWGUIDCOL) column (as well as an ID primary key column) and a “ModifiedDate” (datetime, DEFAULT (getdate)) column. When trying to create a new record in browser the user is asked to type the rowguid and ModifiedDate values instead of the server applying them automatically. I tried a few tricks I could think of (I even created an AFTER UPDATE trigger in the table to set the date) but without success.

I then removed these fields from the table’s model class and created new views. This did the trick as now SQL Server inserts the values automatically. Unfortunately, I’m not anymore able to see these values in index, edit and details views.

What should I do so the column values are inserted automatically and still be able to see these values?

Well this the code for the "Title" table EF model (the applications name is HSEFCF):

namespace HSEFCF
{
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Spatial;

[Table("Person.Title")]
public partial class Title
{
    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
    public Title()
    {
        People = new HashSet<Person>();
        TitleLocales = new HashSet<TitleLocale>();
    }

    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public byte TitleID { get; set; }

    public Guid rowguid { get; set; }

    public DateTime ModifiedDate { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<Person> People { get; set; }

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
    public virtual ICollection<TitleLocale> TitleLocales { get; set; }
}
}

The SQL code is this:

CREATE TABLE [Person].[Title](
[TitleID] [tinyint] IDENTITY(1,1) NOT NULL,
[rowguid] [uniqueidentifier] ROWGUIDCOL  NOT NULL,
[ModifiedDate] [datetime] NOT NULL,
CONSTRAINT [PK_PersonTitle_PersonTitleID] PRIMARY KEY CLUSTERED 
(
[TitleID] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY =     OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

ALTER TABLE [Person].[Title] ADD  CONSTRAINT [DF_PersonTitle_rowguid]  DEFAULT (newid()) FOR [rowguid]
GO

ALTER TABLE [Person].[Title] ADD  CONSTRAINT [DF_PersonTitle_ModifiedDate]  DEFAULT (getdate()) FOR [ModifiedDate]
GO

I don't mind not being able to see the rowguid values but I'd like to be able to see the ModifiedDate values.

I removed the rowguid column from the model so now SQL insert the value itself (I don't mind being unable to see the actual values). I could do the same with ModifiedDate but then I wouldn't be able to see the values which it doesn't fit me. I then inserted the following code in my DbContext file but that it doesn't work. I get no error messages other than "The ModifiedDate field is required."

public override int SaveChanges()
{
    DateTime saveTime = DateTime.Now;
    foreach (var entry in this.ChangeTracker.Entries()
.Where(e => e.State == EntityState.Added))
    {
if (entry.Property("ModifiedDate").CurrentValue == null)
    entry.Property("ModifiedDate").CurrentValue = saveTime;
    }
    return base.SaveChanges();
}

This is the controller code:

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Net;
using System.Web;
using System.Web.Mvc;
using HSEFCF;

namespace HSEFCF.Controllers
{
public class TitleController : Controller
{
    private HSEFCFContext db = new HSEFCFContext();

    // GET: Title
    public ActionResult Index()
    {
        return View(db.Titles.ToList());
    }

    // GET: Title/Details/5
    public ActionResult Details(byte? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Title title = db.Titles.Find(id);
        if (title == null)
        {
            return HttpNotFound();
        }
        return View(title);
    }

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

    // POST: Title/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 = "TitleID,ModifiedDate")] Title title)
    {
        if (ModelState.IsValid)
        {
            db.Titles.Add(title);
            db.SaveChanges();
            return RedirectToAction("Index");
        }

        return View(title);
    }

    // GET: Title/Edit/5
    public ActionResult Edit(byte? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Title title = db.Titles.Find(id);
        if (title == null)
        {
            return HttpNotFound();
        }
        return View(title);
    }

    // POST: Title/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]
    [ValidateAntiForgeryToken]
    public ActionResult Edit([Bind(Include = "TitleID,ModifiedDate")] Title title)
    {
        if (ModelState.IsValid)
        {
            db.Entry(title).State = EntityState.Modified;
            db.SaveChanges();
            return RedirectToAction("Index");
        }
        return View(title);
    }

    // GET: Title/Delete/5
    public ActionResult Delete(byte? id)
    {
        if (id == null)
        {
            return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
        }
        Title title = db.Titles.Find(id);
        if (title == null)
        {
            return HttpNotFound();
        }
        return View(title);
    }

    // POST: Title/Delete/5
    [HttpPost, ActionName("Delete")]
    [ValidateAntiForgeryToken]
    public ActionResult DeleteConfirmed(byte id)
    {
        Title title = db.Titles.Find(id);
        db.Titles.Remove(title);
        db.SaveChanges();
        return RedirectToAction("Index");
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing)
        {
            db.Dispose();
        }
        base.Dispose(disposing);
    }
}
}

This is the Create view code:

@model HSEFCF.Title

@{
ViewBag.Title = "Create";
}

<h2>Create</h2>


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

<div class="form-horizontal">
    <h4>Title</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    <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>
}

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

@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}

And this is the Edit view code:

@model HSEFCF.Title

@{
ViewBag.Title = "Edit";
}

<h2>Edit</h2>


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

<div class="form-horizontal">
    <h4>Title</h4>
    <hr />
    @Html.ValidationSummary(true, "", new { @class = "text-danger" })
    @Html.HiddenFor(model => model.TitleID)

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

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

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

@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}

Thanks in advance, galaf.

galaf
  • 37
  • 8

2 Answers2

2

For EF to use the server-side generated values, you need to set the DatabaseGenerated attribute on properties.

Additionally, for the MVC model binding to consider a value type as optional, it needs to be nullable.

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public Guid? rowguid { get; set; }

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime? ModifiedDate { get; set; }

Although this may affect your data model. Personally, I prefer splitting view and data models, since it allows for more flexibility and decoration on either sides.

ESG
  • 8,988
  • 3
  • 35
  • 52
  • Thanks for the answer ESG. This works fine for creating new records but when editing a record still asks to fill the ModifiedData column. Regards, galaf. – galaf Sep 09 '16 at 19:08
  • @galaf then I suspect the issue is with your view / view model, and not in EF – ESG Sep 10 '16 at 02:38
  • I added the code for controller as well as the code for Create and Edit views. Thanks for your patience, galaf. – galaf Sep 10 '16 at 08:30
  • I followed your instructions, changed the model and readded controller and views. It's fine when I create a new record but it doesn't update the ModifiedDate value when I edit an existing record. Instead it applies the old value as shown in the textbox. The ModifiedDate textbox (as the rowguid textbox) should be inactive so the user shouldn't be allowed to enter a value. – galaf Sep 21 '16 at 03:14
0

I came up with this solution:

  1. I set the ModifiedDate property as:

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime ModifiedDate { get; set; }
    
  2. I created a trigger in database to update ModifiedDate:

    CREATE TRIGGER [dbo].[trg_UpdateModifiedDate]
    ON [dbo].[Title]
    AFTER UPDATE
    AS
    UPDATE dbo.Title
    SET ModifiedDate = GETDATE()
    WHERE TitleID IN (SELECT DISTINCT TitleID FROM Inserted)
    
  3. I set the rowguid property as:

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public Guid rowguid { get; set; }
    
  4. Both ModifiedDate and rowguid properties are not included in create view (they are handled by SQL Server). ModifiedDate is included in Index and Details views.

galaf
  • 37
  • 8