Eval Knievel
Eval Knievel

Reputation: 372

Entity Framework 6 EntityDataSource not calling SaveChanges in DbContext

I am developing in an asp.net web site project. It does not have a very consistent data layer, so I am trying to implement code first entity framework.

I have recently discovered that the people who make entity framework have created a new version of EntityDataSource that works with Entity Framework 6.

http://blogs.msdn.com/b/webdev/archive/2014/02/28/announcing-the-release-of-dynamic-data-provider-and-entitydatasource-control-for-entity-framework-6.aspx

This new datasource seems really good and it updates the database. It does not seem to call any of the methods on my DbContext class though.

As part of some basic change tracking requirements, I have overridden the DbContext SaveChanges() method and put some code in to update tracking fields on each record (CreatedBy,CreatedDate,ModifiedBy,ModifiedDate). The strange thing is that when I work with the DBContect directly, the SaveChanges() method is called. But when I use this EntityDataSource it does not call SaveChanges().

How does the Entity Framework EntityDataSource above go about updating the dbsets?

Is there any way I can make this EntityDataSource call the DbContext SaveChanges() method?

Is there an alternative to overriding the DBContext SaveChanges Method?

Here is an example of my entity framework EntityDataSource control definition

<asp:ListView ID="FormView" runat="server" DataKeyNames="RecordId" DataSourceID="ApplicantDataSource" DefaultMode="Edit">
    <EditItemTemplate>
        <asp:TextBox runat="server" ID="SomeField" Text='<%# Bind("SomeField")%>' ></asp:TextBox>
    </EditItemTemplate>
</asp:ListView>

<ef:EntityDataSource runat="server" 
                     ID="ApplicantDataSource"
                     ContextTypeName="Organisation.Application.MyDbContext"
                     EntitySetName="Applicants"
                     Where="it.RecordId=@RecordId"
                     EnableUpdate="true">
    <WhereParameters>
        <asp:QueryStringParameter Name="RecordId" QueryStringField="RecordId" DbType="String" DefaultValue=""/>
    </WhereParameters>
</ef:EntityDataSource>

And Here is my DbContext (cut down). When update is called on the EntityDataSource, it does not pass through SaveChanges(). It does not even call the getter in the Applicants property to access the DBSet of Applicants. It still manages to save the information somehow!

Public Class MyDbContext
    Inherits DbContext

    Public Shared Sub MyDbContext()
        Database.SetInitializer(Of MyDbContext)(Nothing)
    End Sub

    Public Sub New()
        MyBase.New("Name=ConnectionString")
    End Sub

    Public Property Applicants() As DbSet(Of Applicant)

    Protected Overrides Sub OnModelCreating(modelBuilder As DbModelBuilder)

        modelBuilder.Conventions.Remove(Of PluralizingTableNameConvention)()

        MyBase.OnModelCreating(modelBuilder)
    End Sub

    Public Overrides Function SaveChanges() As Integer
        Try
            Dim entities = Me.ChangeTracker.Entries().Where(Function(x) TypeOf x.Entity Is MyBase AndAlso (x.State = EntityState.Added OrElse x.State = EntityState.Modified))
            Dim currentUsername As String

            If HttpContext.Current IsNot Nothing And HttpContext.Current.User IsNot Nothing Then
                currentUsername = HttpContext.Current.User.Identity.Name
            Else
                currentUsername = "System"
            End If

            For Each entity In entities
                If entity.State = EntityState.Added Then
                    DirectCast(entity.Entity, MyBase).CreatedDate = DateTime.Now
                    DirectCast(entity.Entity, MyBase).CreatedBy = currentUsername
                ElseIf entity.State = EntityState.Modified Then
                    entity.Property("CreatedBy").IsModified = False
                    entity.Property("CreatedDate").IsModified = False
                End If

                DirectCast(entity.Entity, MyBase).ModifiedDate = DateTime.Now
                DirectCast(entity.Entity, MyBase).ModifiedBy = currentUsername
            Next

            Return MyBase.SaveChanges()
        Catch ex As DbEntityValidationException
            ' Retrieve the error messages as a list of strings.
            Dim errorMessages = ex.EntityValidationErrors.SelectMany(Function(x) x.ValidationErrors).[Select](Function(x) x.ErrorMessage)

            ' Join the list to a single string.
            Dim fullErrorMessage = String.Join("; ", errorMessages)

            ' Combine the original exception message with the new one.
            Dim exceptionMessage = String.Concat(ex.Message, " The validation errors are: ", fullErrorMessage)

            ' Throw a new DbEntityValidationException with the improved exception message.
            Throw New DbEntityValidationException(exceptionMessage, ex.EntityValidationErrors)
        End Try
    End Function

End Class

Upvotes: 4

Views: 1107

Answers (1)

Igor
Igor

Reputation: 62228

How does the Entity Framework EntityDataSource above go about updating the dbsets?

EntityDataSource uses the underlying ObjectContext, not the DbContext. This is why you never see anything go directly through your SaveChanges() overloads on your DbContext.

Is there any way I can make this EntityDataSource call the DbContext SaveChanges() method?

Yes and no, you can overload the Updating method, and whith the EntityDataSourceChangingEventArgs you can get access to the DbContext and then call it. This would be necessary for all your views though, I do not see a way to easily centralize this and its bad design (hence the no).

protected void x_Updating(object sender, EntityDataSourceChangingEventArgs e) {
        e.Cancel = true;
        DbContext dbc = new DbContext(e.Context, true);
        new YourDbContext(dbc).SaveChanges();
    }

Is there an alternative to overriding the DBContext SaveChanges Method?

Only what I described above. You can hook into the ObjectContext though and get passed events but you need an instance of it when the context is created (see that event). You can then subscribe to the SavingChanges event as your hook although this is somewhat more limited than SaveChanges on DbContext. If all you want to do is

Here is a Vb.Net code sample of h ow to hook in. Don't shoot me if the syntax is off, it has been ages since I wrote anything in Vb. You probably need to register onContextCreated in the designer (aspx code).

Disclaimer - I have not tested the code below. It is based in part on your code and the sample code in the SavingChanges documentation.

Protected Sub onContextCreated(ByVal sender As Object, ByVal args As EntityDataSourceContextCreatedEventArgs)
   AddHandler args.Context.SavingChanges, AddressOf context_SavingChanges
End Sub

Private Sub context_SavingChanges(ByVal sender As Object, ByVal e As EventArgs)
    Dim context As ObjectContext = TryCast(sender, ObjectContext)

    If context IsNot Nothing Then
        If HttpContext.Current IsNot Nothing And HttpContext.Current.User IsNot Nothing Then
            currentUsername = HttpContext.Current.User.Identity.Name
        Else
            currentUsername = "System"
        End If

        For Each entity As ObjectStateEntry In context.ObjectStateManager.GetObjectStateEntries(EntityState.Added Or EntityState.Modified)
            If entity.State = EntityState.Added Then
                DirectCast(entity.Entity, MyBase).CreatedDate = DateTime.Now
                DirectCast(entity.Entity, MyBase).CreatedBy = currentUsername
            ElseIf entity.State = EntityState.Modified Then
                entity.Property("CreatedBy").IsModified = False
                entity.Property("CreatedDate").IsModified = False
            End If

            DirectCast(entity.Entity, MyBase).ModifiedDate = DateTime.Now
            DirectCast(entity.Entity, MyBase).ModifiedBy = currentUsername
        Next
    End If
    
End Sub

Source for part of the answer: http://w3stack.org/question/how-can-i-override-dbcontext-savechanges-when-using-entitydatasource-and-database-generated-model-edmx-ef-5/

Upvotes: 1

Related Questions