How do I check whether a Linq-to-SQL object is already attached to a DataContext? - vb.net

I have an object that may have been inflated via a plain old DataContext, or may just have been new-ed up with just its .ID property set. There's no way to know for sure. I'm looking to rehydrate the entire object from whatever is in the database. If the object was new-ed up, I can call .Attach() on the table object and refresh from the Data Context with no trouble. But, if the object was already inflated from the DataContext I get the error: "Cannot attach an entity that already exists.". There's no timestamp field or anything like that - just an integer primary key being used to control the rehydration. I'd like to know if there's a way to conditionally attach. Here's the code - it works the way I want it to, but this seems a hackish way to go about it:
' myDC is a shared instance of a vanilla DataContext...
' myObj is an instance of a linqed-up `SomeLinqObject`
Dim tbl = myDC.GetTable(Of SomeLinqObject)()
Try
tbl.Attach(myObj) ' <-- Wish I could just TryAttach() here!
Catch ex As Exception
If ex.Message = "Cannot attach an entity that already exists." Then
' Do nothing
Else
Throw
End If
End Try
myDC.Refresh(RefreshMode.OverwriteCurrentValues, myObj) ' Rehydrate
-- EDIT --
Thanks to Isaac's answer, here's the revised code:
Dim tbl = myDC.GetTable(Of SomeLinqObject)()
Dim isAttached = (tbl.GetOriginalEntityState(myObj) IsNot Nothing)
If Not isAttached Then tbl.Attach(myObj)
myDC.Refresh(RefreshMode.OverwriteCurrentValues, myObj) ' Rehydrate

GetOriginalEntityState(T entity) on Table -might- be what you're looking for. If you pass it an entity that you've loaded from the context, it returns the original version of the entity held in the context. If you pass it a new entity (or I believe one simply not sourced from that context), it returns null.
var context = new DataClasses1DataContext();
var person = context.Person.First();
var isAttachedToContext = context.Person.GetOriginalEntityState(person) != null; // returns true
var isNewEntityAttachedToContext = context.Peoples.GetOriginalEntityState(new Person()) != null; // returns false
Apologies - answer is in C# but I hope you get the gist!

Related

Call System.IDisposable.Dispose on object 'cboUltra' before all references to it are out of scope

when I try to run the below code i'm getting the error through code analysis.
//Code
For Each UltraGridRow In transactionFieldsGrid.Rows.GetAllNonGroupByRows()
If (Field.FieldTypeId = 1000) Then
Dim cboUltra = New UltraCombo()
cboUltra.DataSource = LoadLookupMulticolumn(Field.LookUpCode)
UltraGridRow.Cells("FieldValue").ValueList = cboUltra
EndIf
Next
//Error
CA2000 Dispose objects before losing scope In method 'TransactionFieldsController.LoadTransactionFieldsGrid(UltraGridBase, Collection(Of TransactionField), WorkflowsController, Boolean)', object 'cboUltra' is not disposed along all exception paths. Call System.IDisposable.Dispose on object 'cboUltra' before all references to it are out of scope.
To overcome this I rewritten the code as:
For Each UltraGridRow In transactionFieldsGrid.Rows.GetAllNonGroupByRows()
If (Field.FieldTypeId = 1000) Then
Using cboUltra As New UltraCombo()
cboUltra.DataSource = LoadLookupMulticolumn(Field.LookUpCode)
UltraGridRow.Cells("FieldValue").ValueList = cboUltra
End Using
EndIf
Next
But when I tried like the above code. I'm getting an Object reference error when the below line gets executed.
transactionFieldsGrid.ActiveRow = transactionFieldsGrid.Rows.GetRowAtVisibleIndex(0)
Note: The grid and rows contain values.
Any solutions?
In this case, you can't dispose the UltraCombo instances in the method because the grid has references to the UltraCombo. For disposing the UltraCombo's that you are creating, you would need to store them in a collection scoped to the form and dispose them when the form is disposed. This is necessary because within the method is too soon since the grid still needs access to the combo.
Note that if you need to use the same data for multiple drop downs in the grid, then it would be better to only have one instance of that drop down and reuse it. For this you could create a helper object that would track the instances of the UltraCombo and return the correct instance for a specific LookUpCode and if it hasn't already created the instance it would when it is requested. If these are stored in a dictionary in the helper object, you could implement IDisposable on the helper and dispose all of the UltraCombos when dispose is called on the helper. You could then have a single instance of this helper on your form and call its dispose when the form is disposed.

Creating new smartform data using Ektron ContentTypes

Ektron 8.0.1 SP1
I am using SmartForms and Content Types to read (and hopefully write) data. I can read data but now I am attempting to write a new record similar to the following.
ContentTypeManager<member> contentTypeManager = new ContentTypeManager<member>();
ContentType<member> newmem = new ContentType<member>();
newmem.SmartForm.details.field1 = "Chuck"; // This line throws 'Object reference not set to an instance of an object.' error
newmem.SmartForm.details.field2 = "Norris";
contentTypeManager.Update(newmem);
I get the error "Object reference not set to an instance of an object." for that first assignment line. What am I missing?
I am having trouble finding good documentation on ContentTypes for 8.0.1 now that the Ektron website has been redesigned.
Thx.
Thanks for clarifying, to ADD content to a folder that has a smartform assigned to it, the basic code block should get you started: (Note: the Html attribute of the content is simply the xml matched to the schema you created)
Ektron.Cms.Framework.Content.ContentManager cmanager = new Cms.Framework.Content.ContentManager();
Ektron.Cms.ContentData cdata = new ContentData();
cdata.FolderId = 0;
cdata.XmlConfiguration.Id = 0; //SMARTFORM ID HERE
cdata.Html = "<root><field1>field1 value</field1><field2>field2 value</field2></root>";
cmanager.Add(cdata);
You could update ContentTypes.cs to include an Add method. Just copy the Update method and change contentManager.Update to contentManager.Add.
public void Add(ContentType<T> contentType)
{
Initialize();
contentType.Content.Html = Ektron.Cms.EkXml.Serialize(typeof(T), contentType.SmartForm);
contentManager.Add(contentType.Content);
}
Unfortunately, contentManager.Add returns void. Ideally it should return the new content ID.

Entity Model Not Being Updated on SaveChanges

I have a misunderstanding somewhere with the Entity Framework. This code is from my unit testing:
Public Sub UpdateRosterLinkTest()
Dim target As PlayerAdmin = New PlayerAdmin()
target.PlayerAdminManager = playerAdminTestManager
target.Team = playerAdminTestManager.GetAirForceMensBB()
playerAdminTestManager.resetRosterLink(target)
Assert.IsNull(target.Team.RosterLink)
Dim playerAdmin As PlayerAdmin = New PlayerAdmin()
playerAdmin.TeamId = 12434
playerAdmin.RosterLink = "Roster Link"
playerAdmin.UpdateRosterLink()
Dim team As DAL.Team = playerAdminTestManager.GetAirForceMensBB()
Assert.AreEqual("Roster Link", team.RosterLink)
End Sub
I'm creating a PlayerAdmin, which is a model class. target.Team is an Entity object. What I do is reset the RosterLink field in the Team just to make sure our test starts out at the same place. Then I call the UpdateRosterLink() function. That looks like:
Function UpdateRosterLink() As Integer
If (PlayerAdminManager Is Nothing) Then
PlayerAdminManager = New PlayerAdminManager()
End If
Team = PlayerAdminManager.GetTeamByTeamId(TeamId)
Team.RosterLink = RosterLink
Dim numberOfChanges As Integer = PlayerAdminManager.SaveChanges()
Return numberOfChanges
End Function
When I run this code, I can see the changes saved to the SQL Server this pulls from (RosterLink = Roster Link, like I set in the unit test).
However, my unit test is failing, because team.RosterLink is still Nothing. The function GetAirForceMensBB() returns the Team with TeamId = 12434:
Function GetAirForceMensBB() As DAL.Team
Return (From team In Container.Teams Where team.TeamId = 12434).SingleOrDefault
End Function
I'm sure I'm using the entity framework incorrectly and it probably has something to do with the fact that I am calling the PlayerAdminTestManager in different places, but I don't understand why. Although, I set the PlayerAdminManager to be the PlayerAdminTestManager. PlayerAdminTestManager extends PlayerAdminManager, fyi.
Why is team.RosterLink not showing the update from UpdateRosterLink?
Thanks
EDIT
Container is my ObjectContext. This is how I access the information stored in the database. Container.Teams represents my Teams table.
The problem was I was referencing different instantiations of the Container (each manager created its own). Thus, the entity items were not attached to anything.
Doh!

Error returning objects to VBA from Visual Foxpro COM server

I am getting the following message when trying to return a new object to VBA from my Visual Foxpro COM server.
"Run-time error '-2147417851 (80010105)':
Method 'ReturnObject' of object 'Itestclass' failed"
If I remove the "Dim ... As" line the error goes away but then I lose intellisense for the COM object.
This is the VBA code:
Sub Test()
'' Removing the following line gets rid of the error but loses intellisense for the COM object
Dim objTest As testcom.TestClass
Set objTest = CreateObject("TestCOM.TestClass")
Set objNew = objTest.ReturnObject '' This is the line that causes the error
End Sub
I have created a link to the TestCOM type library in Tools > References
Here is the Visual Foxpro (VFP) code:
The COM server is being built as an out of process EXE. If I build it as an inprocess .DLL then the VBA code causes Excel to crash.
DEFINE CLASS ObjectToReturn AS SESSION OLEPUBLIC
ENDDEFINE
DEFINE CLASS TestClass AS SESSION OLEPUBLIC
FUNCTION ReturnObject
RETURN CREATEOBJECT("ObjectToReturn")
ENDFUNC
ENDDEFINE
I have tried changing the RETURN CREATEOBJECT("ObjectToReturn") to RETURN CREATEOBJECT("CUSTOM") but the problem persists.
Please advise how I can get rid of this error without losing the intellisense for the COM object in VBA. Thanks
I don't know why you are going through such difficulties... This should be able to help you... You can define your class as OlePublic and set some properties on it like the samples at the top. You can set these properties anywhere through the other functions that are not HIDDEN.
If you need to are trying to get at certain elements OF some other "object", try creating an instance of the object and stick it into a property on the OlePublic class... see my method
DoSomethingElse
which does a simple scatter NAME call to the "SomeObject" property of the class. Even though you are not explicitly returning it, it should be visible from within your creation of it from VB...
DEFINE CLASS VFPClassForVB as Session OLEPublic
cTmpFiles = ""
cCOMUser = ""
SomeObject = ""
FUNCTION Init()
*/ Who is user... always ignore the machine....
This.cCOMUser = SUBSTR( SYS(0), AT( "#", SYS(0)) +1 )
This.cTmpFiles = "somepath\"
*/ Unattended mode... any "MODAL" type dialog will throw error / exception
SYS(2335, 0 )
*/ ALWAYS HAVE EXCLUSIVE OFF FOR COM!!!
SET EXCLUSIVE OFF
*/ ALWAYS HIDE DELETED RECORDS!!!
SET DELETED ON
ENDFUNC
*/ Error handler at the CLASS level will always be invoked
*/ instead of explicit ON ERROR or TRY/CATCH handlers...
FUNCTION xError(nError, cMethod, nLine)
lcMsg = "User: " + SYS(0) + " Tmp:" + SYS(2023);
+ " Method: " + cMethod + " Error: " + STR( nError,5);
+ " Line: " + STR( nLine, 6 )
STRTOFILE( lcMsg, This.cTmpFiles + "COMLog.txt" )
*/ NOW, throw the COM Error...
COMReturnError( cMethod + ' Error:' + str(nError,5);
+ ' Line:' + str(nline,6);
+ ' Msg:' + message(), _VFP.ServerName )
RETURN
HIDDEN FUNCTION SomeOtherFunction( lcWhat String,;
lnThing as Integer ) as String
*/ Do something
RETURN 1
ENDFUNC
*/ Another completely visible function direct form VB
FUNCTION DoSomethingElse( SomeParameter as String ) as String
USE SomeTable
*/ Now, this object should be visible as a direct property in VB
SCATTER MEMO NAME This.SomeObject
ENDFUNC
ENDDEFINE
Your VB side, even from your sample...
Sub Test()
Set objTest = CreateObject("MySampleProject.VFPClassForVB")
objTest.DoSomethingElse( "I dont care" )
dim Something as objTest.SomeObject.ColumnFromTable
End Sub
You can create as many OlePublic classes in your in-code class libraries that you want to expose and just create those instances as needed. Let me know if this helps you get closer and we'll try to keep working it out.
I've tried all sorts of samples, but looking at what you have of the object where both are VFP OleObject entries, each is exposed, and can be created individually. You don't need to create one to create the other.
Is there some reason SPECIFIC you are trying to create one object from another? You can have one object expose a bunch of methods and properties to perform whatever you need from VFP.
If you want to have multiple object classes exposed, and under a central control, you can always create your primary object for communication and have IT create an instance of each "other" class on it. Then, expose methods on your main class to handle the communications between them to act out whatever it is you need.

LINQ SQL Attach, Update Check set to Never, but still Concurrency conflicts

In the dbml designer I've set Update Check to Never on all properties. But i still get an exception when doing Attach: "An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported." This approach seems to have worked for others on here, but there must be something I've missed.
using(TheDataContext dc = new TheDataContext())
{
test = dc.Members.FirstOrDefault(m => m.fltId == 1);
}
test.Name = "test2";
using(TheDataContext dc = new TheDataContext())
{
dc.Members.Attach(test, true);
dc.SubmitChanges();
}
The error message says exactly what is going wrong: You are trying to attach an object that has been loaded from another DataContext, in your case from another instance of the DataContext. Dont dispose your DataContext (at the end of the using statement it gets disposed) before you change values and submit the changes. This should work (all in one using statement). I just saw you want to attach the object again to the members collection, but it is already in there. No need to do that, this should work just as well:
using(TheDataContext dc = new TheDataContext())
{
var test = dc.Members.FirstOrDefault(m => m.fltId == 1);
test.Name = "test2";
dc.SubmitChanges();
}
Just change the value and submit the changes.
Latest Update:
(Removed all previous 3 updates)
My previous solution (removed it again from this post), found here is dangerous. I just read this on a MSDN article:
"Only call the Attach methods on new
or deserialized entities. The only way
for an entity to be detached from its
original data context is for it to be
serialized. If you try to attach an
undetached entity to a new data
context, and that entity still has
deferred loaders from its previous
data context, LINQ to SQL will thrown
an exception. An entity with deferred
loaders from two different data
contexts could cause unwanted results
when you perform insert, update, and
delete operations on that entity. For
more information about deferred
loaders, see Deferred versus Immediate
Loading (LINQ to SQL)."
Use this instead:
// Get the object the first time by some id
using(TheDataContext dc = new TheDataContext())
{
test = dc.Members.FirstOrDefault(m => m.fltId == 1);
}
// Somewhere else in the program
test.Name = "test2";
// Again somewhere else
using(TheDataContext dc = new TheDataContext())
{
// Get the db row with the id of the 'test' object
Member modifiedMember = new Member()
{
Id = test.Id,
Name = test.Name,
Field2 = test.Field2,
Field3 = test.Field3,
Field4 = test.Field4
};
dc.Members.Attach(modifiedMember, true);
dc.SubmitChanges();
}
After having copied the object, all references are detached, and all event handlers (deferred loading from db) are not connected to the new object. Just the value fields are copied to the new object, that can now be savely attached to the members table. Additionally you do not have to query the db for a second time with this solution.
It is possible to attach entities from another datacontext.
The only thing that needs to be added to code in the first post is this:
dc.DeferredLoadingEnabled = false
But this is a drawback since deferred loading is very useful. I read somewhere on this page that another solution would be to set the Update Check on all properties to Never. This text says the same: http://complexitykills.blogspot.com/2008/03/disconnected-linq-to-sql-tips-part-1.html
But I can't get it to work even after setting the Update Check to Never.
This is a function in my Repository class which I use to update entities
protected void Attach(TEntity entity)
{
try
{
_dataContext.GetTable<TEntity>().Attach(entity);
_dataContext.Refresh(RefreshMode.KeepCurrentValues, entity);
}
catch (DuplicateKeyException ex) //Data context knows about this entity so just update values
{
_dataContext.Refresh(RefreshMode.KeepCurrentValues, entity);
}
}
Where TEntity is your DB Class and depending on you setup you might just want to do
_dataContext.Attach(entity);