Reputation: 372
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.
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
Reputation: 62228
EntityDataSource uses the underlying ObjectContext, not the DbContext. This is why you never see anything go directly through your SaveChanges()
overloads on your DbContext.
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();
}
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