Adding a new object to context - vb.net

So apparently I don't understand EF well enough yet. Someone here at SO suggested I shouldn't keep a context object open for the life of my application, so I changed things slightly and now here is how it is currently working:
I read required data from the DB using EF and store it in an ObservableCollection(Of MyEntity).
The application keeps adding new objects to this collection, modifying/deleting existing objects in the ObservableCollection.
Upon application exit or Save button, I try to submit changes back to the DB, but apparently cannot. Here's the submission code.
For Each entity In MyObservableCollection
If entity.EntityState = EntityState.Added OrElse entity.EntityState = EntityState.Detached Then
Dim key = context.CreateEntityKey(entitySetName, entity)
entity.EntityKey = key
context.FilmTypes.Attach(entity)
ElseIf entity.EntityState = System.Data.EntityState.Modified Then
Dim originalItem As Object = Nothing
Dim key = context.CreateEntityKey(entitySetName, entity)
If context.TryGetObjectByKey(key, originalItem) Then
context.ApplyCurrentValues(key.EntitySetName, entity)
End If
End If
Next
context.SaveChanges(SaveOptions.AcceptAllChangesAfterSave)
EF tells me that there is already an object with the same key. My PK column is defined as Identity in the Model, so I expect it to auto-generate a temporary key (just like old good DataSets did).
How exactly do we work in "disconnected" mode in EF?

Found it myself. The code for Modified state is correct. For new objects, it should be like this:
If entity.EntityState = EntityState.Added OrElse entity.EntityState = EntityState.Detached Then
context.FilmTypes.AddObject(entity)
No need to specify key parameter. EF will take care of it.

Related

Can I call SaveChanges when using raw SQL Query in EF6

I want to use sql raw query for better performance.
I was wondering if I could use context the same way as if I committed a query with linq. If I bind collection to dgv.DataSource and made some (update) could I call db.SaveChanges() to store the data in the database.
Example:
Using context = New BloggingContext()
Dim blogs = context.Blogs.SqlQuery("SELECT * FROM dbo.Blogs").ToList()
datagridview1.dataSource = blogs
' if I made some changes in datagridview1 could I
' use SaveChanges to commit any changes on button click
context.SaveChanges()
End Using
The DbSet.SqlQuery method returns object that are tracked by the context :
By default, the entities returned are tracked by the context (MSDN)
(note that Database.SqlQuery results never tracked by the context)
If you change your entities using Entity Framework the SaveChanges method will persist changes.
Using context = New BloggingContext()
Dim blogs = context.Blogs.SqlQuery("SELECT * FROM dbo.Blogs").ToList()
blogs.First().Title = "new Title"
context.SaveChanges() ' new Title will be persisted on database
End Using
By the way, depending on the kind of datagridview you are using (webform, winform, WPF, etc.), you will have to attach the modified entities to the context that will be used to save changes.

linq can not find the value from database, although it exists in database

I've noticed some irregularities while using linq in mvc3.
I have a database table with a "PIN" column. Column must have a 28 characters and completly random characters can be stored. Then I have a controller action:
Function Unlock() As ActionResult
Dim key As String = Request("token")
If key IsNot Nothing Then
Dim user As Users = db.Users.SingleOrDefault(Function(u) u.PIN = key)
If user IsNot Nothing Then
user.PIN = Nothing
db.SaveChanges()
Return View()
End If
End If
Return RedirectToAction("Index", "Home", New With {.Area = ""})
End Function
For example when PIN holds "Co/5c1mmil2e+clGK3c6JvdrGpQ=" key string reads that full string from querystring, but user reference always ends up nothing, even though that same value is stored in database.
On other hand when PIN holds "GbgI4QAaYWanaKWUm6j7Jg5IpA8=" everything works just fine. So I figured that maybe linq has problems with some characters like / or +, but how to solve this issue?
try
u.PIN.Equals(key)
Use intelliTrace while debuging it will help you a lot with linq.
Also FirtsOrDefault seams more reasonable for me in this case instead of SingleOrDefault

Active Record with Entity Framework

I'm working on a project that was built using ADO.NET (raw sql) and Active Record pattern. I am slowly moving it away from ADO.NET to Entity Framework Code First 4.3.
Here is an example of the pattern. The interface is a static Load and an instance Save (and Delete -- not shown).
Public Class Part
Public Property Id as Integer
Public Shared Function Load(_id As Integer) As Part
Using context As New DataContext()
Return context.Find(_id)
End Using
End Function
Public Sub Save()
Using context As New DataContext()
If Id = 0 Then
context.Parts.Add(Me)
Else
context.Entry(Me).State = Data.EntityState.Modified
End If
context.SaveChanges()
End Using
End Sub
End Class
I realize Active Record is not ideal for EF but I'd like to make it work to remove all of the ADO.NET code while not touching the rest of the code.
This mostly works, but I've run into an issue I don't know how to solve. In order to keep Foreign Keys in sync we handle it like such:
Public Sub Save()
ParentPart = Part.Load(ParentPartId)
ChildPart = Part.Load(ChildPartId)
Using context = New iTracContext()
If bid = 0 Then
context.BillOfMaterials.Add(Me)
Else
context.Entry(Me).State = Data.EntityState.Modified
End If
context.SaveChanges()
End Using
End Sub
This makes sure EF doesn't complain that we have non-matching relationships -- the Id always wins.
The issue is that its throwing an exception now when I save.
An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.
This is thrown from the line:
context.Entry(Me).State = Data.EntityState.Modified
How is anything in the ObjectStateManager for this context? It is brand new and should be empty, no?
If I remove the two Part.Load(...) lines it works fine.
Is there some type of change tracker that lives outside the context that I'm not aware of? That seems like it would kill any attempt at the Active Record pattern.
I'm also open to any suggestions on how to make Active Record work with EF. The context.Entry line is terrible but I don't know what else to do.
Telling me not to do Active Record isn't helpful, but feel free.
I believe Entity Framework may still be tracking the object from the context you loaded it from, because you create a new context for each Load and Save call. If this is the case, try detaching the objects after you load them:
Public Shared Function Load(_id As Integer) As Part
Using context As New DataContext()
Part part = context.Find(_id)
context.Entry(part).State = EntityState.Detached ' Detach from the initial context
Return part
End Using
End Function

Updating child collection of POCO (adding/updating/deleting) in Entity Framework 4.1

I have a webpage with a form that is used to edit some object. This object contains a Collection of other objects defined like this:
Public Overridable Property Employees As List(Of Employee)
On a form I can delete an employee, add a new one or modify existing one. When I click save new values are sent to the server. On a server I check if the user exists. If exists then I modify its values, if it does not exist then I add it. All employees that exist on the server and were not sent are marked as deleted (State changed to EntityState.Deleted). I try to use the following code (dbCollection = database entities, newCollection = collection sent from the form):
For Each item In dbCollection
Dim dbItem = item
Dim newTask = newCollection.FirstOrDefault(Function(i) i.Id = dbItem.Id)
If newTask Is Nothing Then
Me.Entry(item).State = EntityState.Deleted
Else
Me.Entry(item).CurrentValues.SetValues(newTask)
newCollection.Remove(newTask)
End If
Next
For Each item In newCollection
dbCollection.Add(item)
Next
Me.SaveChanges()
This code does not work, because changing to EntityState.Deleted removes the object from collection, and for each loop breaks, since the collection is modified...
I know that I can overcome this problem by using a for loop or adding objects to delete to some other list first, but I hope maybe there is a pattern that would make my code nicer.
Thanks in advance for all suggestions.
Replace...
For Each item In dbCollection
...by:
For Each item In dbCollection.ToList()
ToList() will create a copy of the collection (only the references, not the objects themselves). dbCollection.ToList() is another collection than dbCollection so that you safely can modify the dbCollection without getting the "collection has been modified" exception in the For Each loop.

Creating An Insert Statement -- Windows application Vb.Net

I am doing windows appliction in vb.net. i have customer object contains save method. how do i generate insert query?
I need to save the object in relational database (SQL server). I need to know which is the correct way of doing the insertion ie,. Inside the save method i have written the SQL statement to save the object. Is it the correct way?
Thanks
A simple INSERT statement for SQL takes this basic form:
INSERT INTO [tablename] ( [column1], [column2], ... ) VALUES ( [value1], [value2], ...)
So, we obviously need to know about the database table you are using: what columns it has. We also need to know about the class: what properties it has. Finally, we need to know about the data types for the table columns and class properties, and how the properties will map to the columns. For very simple objects the names and types will just line up. But in other cases your class may itself contain a collection (or several) that would mean inserting data into more than one table.
After all this is determined, we still need two things: connection information for the database (usually distilled down into a single connection string) and whether or not you are concerned that your class instance may have been saved previously, in which case you want to build an UPDATE statement rather than INSERT.
Assuming you can answer all of that in a satisfactory manner, your VB.Net code will look something like this (of course substituting your specific column, property, type, and connection information where appropriate):
Public Class Customer
Public Sub Save()
DAL.SaveCustomer(Me)
End Sub
' ...'
End Class
.
' a VB Module is a C# static class'
Public Module DAL
Private ConnString As String = "Your connection string here"
Public Sub SaveCustomer(ByVal TheCustomer As Customer)
Dim sql As String = "" & _
"INSERT INTO [MyTable] (" & _
"[column1], [column2], ..." & _
") VALUES (" & _
"#Column1, #Column2, ... )"
Using cn As New SqlConnection(ConnString), _
cmd As New SqlCommand(sql, cn)
cmd.Parameters.Add("#column1", SqlDbTypes.VarChar, 50).Value = TheCustomer.Property1
cmd.Parameters.Add("#column2", SqlDbTypes.VarChar, 1000).Value = TheCustomer.Property2
cn.Open()
cmd.ExecuteNonQuery()
End Using
End Sub
End Module
I know you've already heard that separating out your database code is the "right thing to do"tm, but I thought you might also want some more specific reasons why you would want to structure your code this way:
Your connection string is kept in one place, so if your database server moves you only need to make one change. Even better if this is it's own assembly or config file.
If you ever move to a completely different database type you only need to change one file to update the program.
If you have one developer or a DBA who is especially good with sql, you can let him do most of the maintenance on this part of the app.
It makes the code for your "real" objects simpler, and therefore easier to spot when you make a logical design error.
The DAL code might eventually be re-usable if another application wants to talk to the same database.
If you use an ORM tool most of the DAL code is written for you.
There's a few issues here. First, exactly where are you saving this? You say SQL, but is it a SQL Server, an instance of SQL Express, a Local Data Cache (SQL CE 3.5) or saving via a Web Service to talk to your SQL SERVER. These different data sources have different connectivity options/requirements, and in the case of SQL CE there's a few other "gotchas" involved in the SQL itself.
Second, are you sure you want to save data into a relational datastore like SQL Server? Consider, you could use XML, a data file (text, CSV. etc) or even a custom binary file type instead.
Since you're working on a windows application, you have a bunch of options on where and how to save the data. Until you know where you want to put the data, we'd be hard pressed to help you do so.
I agree with Mike Hofer. Keeping your class that does your retrieval and persisting of object separate from your business classes is key to having a flexible and robust design. This is the kind of code you want to be seeing in your GUI or Business layer:
//Populate Customer Objects List with data
IList<Customer> customerList = new List<Customer>()
Customer newCustomer1 = new Customer();
newCustomer.Name = "New Name"
newCustomer.email ="abcd#abcd.com"
customerList.Add(newCustomer1)
//DAL calls
DataAccessClass dalClass = new DataAccessClass ();
dalClass.InsertCustomers(customerList);
Inside your DALClass there should be a method called InsertCustomers(IList customers) and it should have the following code:
Public Function InsertCustomers(ByVal objectList As IList(Of Customer)) As Integer
Dim command As IDbCommand = Nothing
Dim rowsAffected As Integer = 0
Dim connection As IDbConnection = New System.Data.SqlClient.SqlConnection(Me.ConnectionString)
Try
connection.Open
Dim e As IEnumerator = objectList.GetEnumerator
Do While e.MoveNext
command = connection.CreateCommand
command.CommandText = "insert into dbo.Customer(CustomerID,CustomerGUID,RegisterDate,Password,SiteID,Las"& _
"tName,FirstName,Email,Notes,BillingEqualsShipping,BillingLastName) values (#Cust"& _
"omerID,#CustomerGUID,#RegisterDate,#Password,#SiteID,#LastName,#FirstName,#Email"& _
",#Notes,#BillingEqualsShipping,#BillingLastName)"
System.Console.WriteLine("Executing Query: {0}", command.CommandText)
Dim paramCustomerID As IDbDataParameter = command.CreateParameter
paramCustomerID.ParameterName = "#CustomerID"
command.Parameters.Add(paramCustomerID)
Dim paramCustomerGUID As IDbDataParameter = command.CreateParameter
paramCustomerGUID.ParameterName = "#CustomerGUID"
command.Parameters.Add(paramCustomerGUID)
Dim paramRegisterDate As IDbDataParameter = command.CreateParameter
paramRegisterDate.ParameterName = "#RegisterDate"
command.Parameters.Add(paramRegisterDate)
Dim paramPassword As IDbDataParameter = command.CreateParameter
paramPassword.ParameterName = "#Password"
command.Parameters.Add(paramPassword)
Dim paramSiteID As IDbDataParameter = command.CreateParameter
paramSiteID.ParameterName = "#SiteID"
command.Parameters.Add(paramSiteID)
Dim paramLastName As IDbDataParameter = command.CreateParameter
paramLastName.ParameterName = "#LastName"
command.Parameters.Add(paramLastName)
Dim paramFirstName As IDbDataParameter = command.CreateParameter
paramFirstName.ParameterName = "#FirstName"
command.Parameters.Add(paramFirstName)
Dim paramEmail As IDbDataParameter = command.CreateParameter
paramEmail.ParameterName = "#Email"
command.Parameters.Add(paramEmail)
Dim paramNotes As IDbDataParameter = command.CreateParameter
paramNotes.ParameterName = "#Notes"
command.Parameters.Add(paramNotes)
Dim paramBillingEqualsShipping As IDbDataParameter = command.CreateParameter
paramBillingEqualsShipping.ParameterName = "#BillingEqualsShipping"
command.Parameters.Add(paramBillingEqualsShipping)
Dim paramBillingLastName As IDbDataParameter = command.CreateParameter
paramBillingLastName.ParameterName = "#BillingLastName"
command.Parameters.Add(paramBillingLastName)
Dim modelObject As Customer = CType(e.Current,Customer)
paramCustomerID.Value = modelObject.CustomerID
paramCustomerGUID.Value = modelObject.CustomerGUID
paramRegisterDate.Value = modelObject.RegisterDate
If IsNothing(modelObject.Password) Then
paramPassword.Value = System.DBNull.Value
Else
paramPassword.Value = modelObject.Password
End If
paramSiteID.Value = modelObject.SiteID
If IsNothing(modelObject.LastName) Then
paramLastName.Value = System.DBNull.Value
Else
paramLastName.Value = modelObject.LastName
End If
If IsNothing(modelObject.FirstName) Then
paramFirstName.Value = System.DBNull.Value
Else
paramFirstName.Value = modelObject.FirstName
End If
If IsNothing(modelObject.Email) Then
paramEmail.Value = System.DBNull.Value
Else
paramEmail.Value = modelObject.Email
End If
If IsNothing(modelObject.Notes) Then
paramNotes.Value = System.DBNull.Value
Else
paramNotes.Value = modelObject.Notes
End If
paramBillingEqualsShipping.Value = modelObject.BillingEqualsShipping
If IsNothing(modelObject.BillingLastName) Then
paramBillingLastName.Value = System.DBNull.Value
Else
paramBillingLastName.Value = modelObject.BillingLastName
End If
rowsAffected = (rowsAffected + command.ExecuteNonQuery)
Loop
Finally
connection.Close
CType(connection,System.IDisposable).Dispose
End Try
Return rowsAffected
End Function
It is painful to write the DAL code by hand, but you will have full control of your DAL, SQL and Mapping code and changing any of those will be a breeze in the future.
If you don't feel like to write all the DAL Code by hand, you can get a CodeGenerator like Orasis Mapping Studio to generate exactly the same code shown without writing anything. You just need to build your SQL in the tool, map the properties to the paramaters and you are done. It will generate all the rest for you.
Good luck and happy DAL coding!
I'm with Stephen Wrighton. There are a LOT of variables here, and a lot of unanswered questions. If it's SQL, is it even a Microsoft dialect of SQL? Is it Oracle? MySQL? Something else?
In any event, my personal preference is to avoid building SQL in an application if I can, and invoke a stored procedure, even for inserts and updates. Then I pass the arguments for the procedure to the ADO.NET command object. I have this insane idea in my head that SQL belongs in the database. Perhaps that comes from all that time I spent debugging horrifically written ASP code that spliced SQL strings together back in the Dot Com era. (Never again.)
If you feel it's absolutely necessary to do so, meet the System.Text.StringBuilder class. Learn it. Love it. Make it your best friend.
UPDATE:
Seeing your response, I see now that you are working with SQL Server. That makes things much better.
I'd recommend separating your SQL code into a separate class, away from the actual business class. Some might not agree with that, but it will keep the PURPOSE of the classes clear. (See Separation of Concerns.)
You want to have your business object handle the business logic, and a separate class that handles the work of getting data into and out of the database. That way, if you have a problem with the serialization logic, you have a far better idea of where to look, and your chances of hosing the business logic are greatly reduced. It also makes your application much easier to understand.
A little up front effort in writing a few more classes has a HUGE payoff down the road.
But that's just my opinion.
I prefer the idea of Mike Hofer, to have a Stored Proc in the SQL Server side to handle the actual data updates, and having a separate class to wrap calls to those stored procs.
Just my 0.02$
Not quite sure what the OP is asking.
You need to define exactly what you are doing in the "Save" method
If you are creating a new record in the Save method you need to use an INSERT statement.
If you are updating an existing record in the Save method then you need to use an UPDATE statement.
"Save" methods generally imply that both cases are handled by the procedure.
A better method would be to have ("Create" or "Insert") and ("Update" or "Save") methods.
Or perhaps have one procedure which handles both.