1

Can you please tell me why Entity Framework 6 needs the "IsModified" line of code for an update with attach, else my code will "Silent Fail" vs Entity Framework 4? In other words, in Entity Framework 4, I do an update with attach it works. But in EF6, if I do similar, the db will not update and no exception thrown (silent fail). If I put the "IsModified" line in my code it works, but this is unacceptable, as developers could leave the "IsModified" code out and updates will fail, and nobody will know.

This problem will occur/not occur in EF6 under the following conditions set in the db: 1. If active is set to default 1 and AllowNulls=false, update fails 2. If active is not set to a default and AllowNulls=false, update fails 3. If active is not set to a default and AllowNulls=true, update works 4. If active is set to default 1 and AllowNulls=true, update works

This is similar to: EntityFramework not saving null and false value but not exact. I will walk you through the problem:

1) If you have Visual Studio 2010, you can follow along, else, you can trust me that EF4 works as described.

In Visual Studio 2010, new project ASP.NET MVC 2 Web Application, using .NET Framework 4, and name it something like EF4Works. Do not create a unit test.

2) Be sure to add the default and not allow nulls, it is important

    CREATE TABLE [dbo].[Document](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [DocumentName] [varchar](10) NULL,
    [Active] [bit] NOT NULL CONSTRAINT [DF_Document_Active]  DEFAULT ((1)),
 CONSTRAINT [PK_Document] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

3) Insert row "DocName" 1(true)

4) Add edmx with this table.

5) In Home Index Controller (add this controller if necessary) add and run this code (similar) and check db that IT WORKS!(updates db) (put a breakpoint before the view is called):

    public ActionResult Index()
    {
        BreazEntities entities = new BreazEntities();
        Document document = new Document { Id = 1 };
        entities.Documents.Attach(document);
        document.Active = false;
        entities.SaveChanges();

        return View();
    }

6) Put back active flag to 1

7) Add new solution and project in Visual Studio 2013. Web, .NET Framework 4.5.1 ASP.NET WebApplication called EF6Fails, next wizard page MVC, change authentication to no authentication. In package manager console: uninstall-package EntityFramework -project ef6fails -force then install-package EntityFramework -version 6.1.3 -project ef6fails

8) Add edmx in ef6fails to the Document table.

9) Run this code in the controller and put a breakpoint before the view is called:

    public ActionResult Index()
    {
        BreazEntities6 entities = new BreazEntities6();
        Document document = new Document { Id = 1 };
        entities.Documents.Attach(document);
        document.Active = false;
        entities.SaveChanges();

        return View();
    }
  1. This is NOT Working. I will have to do the following, which is unacceptable because new developers could exclude the following and not know that code is not working. Is there something, I can add globally to the solution to not make developers add the following? I will research and try to add something myself until I get answers from SO:

        BreazEntities6 entities = new BreazEntities6();
        Document document = new Document { Id = 1 };
        entities.Documents.Attach(document);
        /*  The following line needs to be added in EF6, BUT not in EF4 */
        entities.Entry(document).Property(e => e.Active).IsModified = true;
        document.Active = false;
        entities.SaveChanges();
    
        return View();
    
Community
  • 1
  • 1
kblau
  • 2,094
  • 1
  • 8
  • 20

1 Answers1

1

You are pointing out the difference in code generation between EF 4.1 with ObjectContext and EF 6 (4.1 and later, actually) with DbContext.

In the ObjectContext API, one single database field, like Active, would generate no less code than this:

/// <summary>
/// No Metadata Documentation available.
/// </summary>
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=false)]
[DataMemberAttribute()]
public global::System.Boolean Active
{
    get
    {
        return _Active;
    }
    set
    {
        OnActiveChanging(value);
        ReportPropertyChanging("Active");
        _Active = StructuralObject.SetValidValue(value);
        ReportPropertyChanged("Active");
        OnActiveChanged();
    }
}
private global::System.Boolean _Active;
partial void OnActiveChanging(global::System.Boolean value);
partial void OnActiveChanged();

Meaning that EF's change tracker would be informed immediately of each property change.

The DbContext API wasn't only a change to a more developer-friendly API, but also to a simpler class model and a move to real persistence ignorance. I believe that in the beginning, still this self-tracking code was generated, but in the end, this was abandoned and a database field would get represented by a simple generated auto-property:

public bool Active { get; set; }

(No doubt, this was also done to have one unified code base underlying the code-first and database-first approaches).

Inevitably, this put more responsibility on the shoulders of the change tracker. It had to detect changes in stead of just register them. For this reason, the change-tracker method DetectChanges is executed "al the time".

Back to your code.

In the "old" ObjectContext API ...

document.Active = false;

... would immediately inform the change tracker that the property was set. Even when it already was false, an UPDATE would be emitted on SaveChanges.

In the DbContext API, the change tracker will never detect this as a change because Active already has the default value for booleans, false. Nothing changes.

In short, it used to be "what you set is what you update" and it turned into "what you change is what you update".

Fine, now what?

You probably want to go back to what you had.

But do you?

Try to change the perspective a bit. Suppose you'd work code-first, would you write property code as in the first code fragment? Would you really? For one, this is tight coupling, no single-responsibility. And in a disconnected scenario (i.e. reattaching deserialized entities) it may lead to many unnecessary updates.

However, even if you would write (or generate) similar code, EF doesn't listen to the changes yet. You'd have to subscribe materialized entities to some change-propagation code in the context all by yourself. It's not built-in anymore, unless you return to the ObjectContext API (which you shouldn't want).

I'd say: live with it. You lose some, but you win a lot (more SOLID code). Get used to being in control of what is updated in the database and what isn't. You may want to set Active to true as a default value in the entity's constructor, so the change tracker does notice the change to false.

But I don't think developers forgetting it is a valid argument. Developers are supposed to be flexible. The world changes all the time, not moving along with it is not an option. If they can remember to include the EF-specific statementAttach (managing entity state), why not statements that manage property state?

Community
  • 1
  • 1
Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
  • Nice read. appreciate the response. It is worth noting that column active defaults to true not false, as in the article referenced and this response. Can you please tell me if there is anything I can change globally to undo this new behavior? – kblau Aug 02 '16 at 19:50
  • From what I understand, you talk about a default in the database. It should be a default value of the C# property. If it is `true` at the moment the change tracker starts tracking the entity, it will notice the change to `false`. – Gert Arnold Aug 02 '16 at 19:56