4

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
EvalKenEval
  • 292
  • 5
  • 21
  • I don't see your MyDbContext inherit from DbContext, which I think is essential. – Grimace of Despair Mar 07 '16 at 14:32
  • Thats in my code, it did not make it into the question text somehow. I have just updated the question. I must have over simplified the example when I pasted it in! – EvalKenEval Mar 07 '16 at 14:35
  • Could you include the (sample) code that calls your DbContext? I am looking for how you are executing an update or create (depending on which of those 2 (or both) are failing). – Igor Mar 07 '16 at 14:48
  • I'm doing the update declaratively by linking the different controls in the aspx page. There is a ListView or a GridView controlling the EntityDataSource. There is no code in the codebehind pages. When the update command on the bound control is hit, it just passes whatever it passes to the entity datasource and like magic it uses the DBContext and the database is updated. – EvalKenEval Mar 07 '16 at 14:54
  • Ok, I see. And you have the latest version installed from [NuGet](https://www.nuget.org/packages/Microsoft.AspNet.EntityDataSource/)? Also, does this [SO answer](http://stackoverflow.com/a/25852483/1260204) apply to you? – Igor Mar 07 '16 at 15:00
  • Yes, I have the latest version. I have had a look at that answer, I do not use the visual designer. I do all my aspx editing by hand. The answer you posted does give me some faith that another human being has found this control and tried to use it :) – EvalKenEval Mar 07 '16 at 15:14

1 Answers1

1

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/

Community
  • 1
  • 1
Igor
  • 60,821
  • 10
  • 100
  • 175
  • Cheers Igor! I'll have a look into this and pop back on tomorrow to set this as an answer if nothing else comes in. Seems like a pretty definitive answer though! – EvalKenEval Mar 07 '16 at 15:24
  • Cool. I even added a couple of lines of Vb code, haven't done that in ages :) Good luck! – Igor Mar 07 '16 at 15:29
  • @Alex - I added additional code based on what you were trying to do. Hopefully this is something that will work for you. – Igor Mar 07 '16 at 16:12
  • Thats good stuff! If I can find a way to hive that off into a seperate class it is a winner. I like the way it works without it becoming a dbcontext. At the moment I am having a brief look into inheriting the data source into a new class and finding a way to stuff an event handler directly into the object each time. Then I just stick that chunk of yours in and off we go. Not sure if that is actually possible yet. I'm pushing the boundaries of my oop knowledge. – EvalKenEval Mar 07 '16 at 16:57
  • 1
    Got it! I inherited the EntityDataSource into a new class and added an event handler there, I reckon I have a solid solution from here on in. Thank you for all your help Igor I had almost given up! 50 Rep well earned :D – EvalKenEval Mar 07 '16 at 18:09