Obscure Unity Firebase Realtime Database Error: "Custom Run loops are not supported"? - firebase

I am currently attemping to build a custom Editor tool for Unity that utilizes the Firebase Realtime Database. This tool would allow someone to right-click on a scene asset in the inspector and select to 'lock' or 'unlock' the scene. Within our Firebase Database, this locking is represented by a dictionary, with each scene name as a key and each value being either "locked" or "unlocked". This funcitonality will be expanded later, but for now, I'm just trying to get things set up so that I can actually connect to and use the Firebase Realtime Database.
I had looked at the Firebase Quickstart Unity project for the Realtime Database (the one that functions like a leaderboard) and saw that it worked fine. I could replace the database URL in the project with the URL for my app's database, and when I entered in values, they appeared inside my realtime database.
So, I based the code for my custom editor script on the code from the quickstart. In fact, I copy-pasted most of it. I will post the script itself, and then describe the errors I receive, as well as the lines that give the errors:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using Firebase;
using Firebase.Unity.Editor;
using Firebase.Database;
[CustomEditor(typeof(SceneAsset))]
[ExecuteInEditMode]
public class SceneLockingEditor : Editor
{
static string sceneName;
DependencyStatus dependencyStatus = DependencyStatus.UnavailableOther;
protected virtual void OnEnable()
{
Debug.Log("OnEnable Called");
sceneName = target.name;
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task => {
dependencyStatus = task.Result;
if (dependencyStatus == DependencyStatus.Available)
{
InitializeFirebase();
}
else
{
Debug.LogError(
"Could not resolve all Firebase dependencies: " + dependencyStatus);
}
});
}
// Initialize the Firebase database:
protected virtual void InitializeFirebase()
{
Debug.Log("Initializing Firebase");
FirebaseApp app = FirebaseApp.DefaultInstance;
app.SetEditorDatabaseUrl(CENSORING MY DATABASE SORRY);
if (app.Options.DatabaseUrl != null) app.SetEditorDatabaseUrl(app.Options.DatabaseUrl);
}
static TransactionResult SceneLockTransaction(MutableData mutableData)
{
List<object> sceneLocks = mutableData.Value as List<object>;
if (sceneLocks == null)
{
sceneLocks = new List<object>();
}
if(mutableData.ChildrenCount > 0)
{
//Look at every child in the scene locks directory.
foreach (var child in sceneLocks)
{
Debug.Log("Checking next child.");
if (!(child is Dictionary<string, object>))
continue;
//If we find the scene we're looking for...
Debug.Log("Checking if the scene has the name we want");
foreach(string key in ((Dictionary<string, object>)child).Keys)
{
Debug.Log("Key: " + key);
}
if(((Dictionary<string, object>)child).ContainsKey(sceneName))
{
string childLockStatus = (string)((Dictionary<string, object>)child)["lockStatus"];
//If the scene is already locked, just abort.
if (childLockStatus == "locked")
{
Debug.Log("Scene is already locked. Abort.");
return TransactionResult.Abort();
}
else
{
Debug.Log("Scene existed in the database and was not locked. Locking it.");
// If the scene existed in the database but was not locked, we will lock it.
((Dictionary<string, object>)child)[sceneName] = "locked";
// You must set the Value to indicate data at that location has changed.
mutableData.Value = sceneLocks;
return TransactionResult.Success(mutableData);
}
}
}
}
Debug.Log("Scene did not exist in the database. Adding it as locked.");
// If the scene didn't exist in the database before, we will add it as locked.
Dictionary<string, object> newSceneLock = new Dictionary<string, object>();
newSceneLock[sceneName] = "locked";
sceneLocks.Add(newSceneLock);
// You must set the Value to indicate data at that location has changed.
mutableData.Value = sceneLocks;
return TransactionResult.Success(mutableData);
}
static TransactionResult SceneUnlockTransaction(MutableData mutableData)
{
List<object> sceneLocks = mutableData.Value as List<object>;
if (sceneLocks == null)
{
sceneLocks = new List<object>();
}
if (mutableData.ChildrenCount > 0)
{
//Look at every child in the scene locks directory.
foreach (var child in sceneLocks)
{
Debug.Log("Checking next child.");
if (!(child is Dictionary<string, object>))
continue;
//If we find the scene we're looking for...
Debug.Log("Checking if the scene has the name we want");
foreach (string key in ((Dictionary<string, object>)child).Keys)
{
Debug.Log("Key: " + key);
}
if (((Dictionary<string, object>)child).ContainsKey(sceneName))
{
string childLockStatus = (string)((Dictionary<string, object>)child)["lockStatus"];
//If the scene is already locked, just abort.
if (childLockStatus == "unlocked")
{
Debug.Log("Scene is already unlocked. Abort.");
return TransactionResult.Abort();
}
else
{
Debug.Log("Scene existed in the database and was locked. Unlocking it.");
// If the scene existed in the database but was not locked, we will lock it.
((Dictionary<string, object>)child)[sceneName] = "unlocked";
// You must set the Value to indicate data at that location has changed.
mutableData.Value = sceneLocks;
return TransactionResult.Success(mutableData);
}
}
}
}
Debug.Log("Scene did not exist in the database. Adding it as unlocked.");
// If the scene didn't exist in the database before, we will add it as locked.
Dictionary<string, object> newSceneLock = new Dictionary<string, object>();
newSceneLock[sceneName] = "unlocked";
sceneLocks.Add(newSceneLock);
// You must set the Value to indicate data at that location has changed.
mutableData.Value = sceneLocks;
return TransactionResult.Success(mutableData);
}
static public void AddSceneLock()
{
Debug.Log("Attempting to add scene lock to database.");
DatabaseReference reference = FirebaseDatabase.DefaultInstance.GetReference("SceneLocks");
Debug.Log("Running Transaction...");
// Use a transaction to ensure that we do not encounter issues with
// simultaneous updates that otherwise might create more than MaxScores top scores.
reference.RunTransaction(SceneLockTransaction)
.ContinueWith(task => {
if (task.Exception != null)
{
Debug.Log(task.Exception.ToString());
}
else if (task.IsCompleted)
{
Debug.Log("Transaction complete.");
}
});
}
static public void RemoveSceneLock()
{
Debug.Log("Attempting to add scene lock to database.");
DatabaseReference reference = FirebaseDatabase.DefaultInstance.GetReference("SceneLocks");
Debug.Log("Running Transaction...");
// Use a transaction to ensure that we do not encounter issues with
// simultaneous updates that otherwise might create more than MaxScores top scores.
reference.RunTransaction(SceneUnlockTransaction)
.ContinueWith(task => {
if (task.Exception != null)
{
Debug.Log(task.Exception.ToString());
}
else if (task.IsCompleted)
{
Debug.Log("Transaction complete.");
}
});
}
[MenuItem("CONTEXT/SceneAsset/Lock Scene", false, 0)]
public static void LockScene()
{
Debug.Log("LockScene Called for scene " + sceneName + ".");
AddSceneLock();
}
[MenuItem("CONTEXT/SceneAsset/Unlock Scene", false, 0)]
public static void UnlockScene()
{
Debug.Log("UnlockScene Called for scene " + sceneName + ".");
RemoveSceneLock();
}
}
The errors always come from this line:
FirebaseDatabase.DefaultInstance.GetReference("SceneLocks");
Any line that has to do with "FirebaseDatabase.DefaultInstance" will throw one of the following two errors
Error 1:
InvalidOperationException: SyncContext not initialized.
Firebase.Unity.UnitySynchronizationContext.get_Instance ()
Firebase.Platform.PlatformInformation.get_SynchronizationContext ()
Firebase.FirebaseApp.get_ThreadSynchronizationContext ()
Firebase.Database.DotNet.DotNetPlatform+SynchronizationContextTarget..ctor ()
Firebase.Database.DotNet.DotNetPlatform.NewEventTarget (Firebase.Database.Internal.Core.Context c)
Firebase.Database.Internal.Core.Context.EnsureEventTarget ()
Firebase.Database.Internal.Core.Context.InitServices ()
Firebase.Database.Internal.Core.Context.Freeze ()
Firebase.Database.Internal.Core.RepoManager.CreateLocalRepo (Firebase.Database.Internal.Core.Context ctx, Firebase.Database.Internal.Core.RepoInfo info, Firebase.Database.FirebaseDatabase firebaseDatabase)
Firebase.Database.Internal.Core.RepoManager.CreateRepo (Firebase.Database.Internal.Core.Context ctx, Firebase.Database.Internal.Core.RepoInfo info, Firebase.Database.FirebaseDatabase firebaseDatabase)
Firebase.Database.FirebaseDatabase.EnsureRepo ()
Firebase.Database.FirebaseDatabase.get_RootReference ()
SceneLockingEditor.OnInspectorGUI () (at Assets/Bitloft/SCRIPTS/Editor/SceneLockingEditor.cs:37)
UnityEditor.InspectorWindow.DrawEditor (UnityEditor.Editor[] editors, Int32 editorIndex, Boolean rebuildOptimizedGUIBlock, System.Boolean& showImportedObjectBarNext, UnityEngine.Rect& importedObjectBarRect) (at C:/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:1242)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)
Error 2:
Exception: Custom Run loops are not supported!
Firebase.Database.Internal.Core.Context.GetExecutorService ()
Firebase.Database.Internal.Core.Context.GetConnectionContext ()
Firebase.Database.Internal.Core.Context.NewPersistentConnection (Firebase.Database.Internal.Connection.HostInfo info, IDelegate delegate_)
Firebase.Database.Internal.Core.Repo..ctor (Firebase.Database.Internal.Core.RepoInfo repoInfo, Firebase.Database.Internal.Core.Context ctx, Firebase.Database.FirebaseDatabase firebaseDatabase)
Firebase.Database.Internal.Core.RepoManager.CreateLocalRepo (Firebase.Database.Internal.Core.Context ctx, Firebase.Database.Internal.Core.RepoInfo info, Firebase.Database.FirebaseDatabase firebaseDatabase)
Firebase.Database.Internal.Core.RepoManager.CreateRepo (Firebase.Database.Internal.Core.Context ctx, Firebase.Database.Internal.Core.RepoInfo info, Firebase.Database.FirebaseDatabase firebaseDatabase)
Firebase.Database.FirebaseDatabase.EnsureRepo ()
Firebase.Database.FirebaseDatabase.get_RootReference ()
SceneLockingEditor.OnInspectorGUI () (at Assets/Bitloft/SCRIPTS/Editor/SceneLockingEditor.cs:37)
UnityEditor.InspectorWindow.DrawEditor (UnityEditor.Editor[] editors, Int32 editorIndex, Boolean rebuildOptimizedGUIBlock, System.Boolean& showImportedObjectBarNext, UnityEngine.Rect& importedObjectBarRect) (at C:/buildslave/unity/build/Editor/Mono/Inspector/InspectorWindow.cs:1242)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)
It's always one error or the other, and I can't determine what causes one error to appear rather than the other. Both errors stop whatever operation I'm trying to do on the database, which means I can't interact with my database at all.
I've taken a look at the quickstart projects and watched some videos of people setting up Firebase to work with their projects, and I can't seem to determine what I have messed up during the process. I have imported my google-services.json into the unity project. The quickstart projects worked just fine interacting with my database. It's just this particular script that won't work. I cannot find any mention of these two errors anywhere on Google. I even contacted the official Firebase support and they couldn't give me any advice on what the errors mean or what could be causing them.
I considered one problem might be in my initialization function. Instead of doing:
FirebaseApp app = FirebaseApp.DefaultInstance;
I figured that maybe I am supposed to use FirebaseApp.Create() with a custom name passed in, but that resulted in the same errors being thrown on the same line. I am at a loss for how to proceed with this problem. I don't know of anyone else who has had these particular errors, and I've done very much experimentation with different ways to access the database over the past several days. If anybody has an idea of what I am doing wrong here, or what causes these errors (and, how to fix them), I would really appreciate it.

At first you should initialize firebase with new instance of FirebaseApp with unique name. I do it like this:
FirebaseApp firebaseApp = FirebaseApp.Create(
FirebaseApp.DefaultInstance.Options,
"FIREBASE_EDITOR");
The second is setup references (DatabaseReference, StorageReference etc.) with this firebaseApp instance and use it only after FirebaseApp.CheckAndFixDependenciesAsync()
Overall code will look like this:
public static void Initialize(bool isEditor = false)
{
if (isEditor)
{
FirebaseApp firebaseApp = FirebaseApp.Create(
FirebaseApp.DefaultInstance.Options,
"FIREBASE_EDITOR");
firebaseApp.SetEditorDatabaseUrl("https://project.firebaseio.com/");
FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task =>
{
if (task.Result == DependencyStatus.Available)
{
database = FirebaseDatabase.GetInstance(firebaseApp).RootReference;
storage = FirebaseStorage.GetInstance(firebaseApp).RootReference;
auth = FirebaseAuth.GetAuth(firebaseApp);
}
else
{
Debug.LogError(
"Could not resolve all Firebase dependencies: " + task.Result);
}
});
}
else
{
FirebaseApp.DefaultInstance.SetEditorDatabaseUrl("https://project.firebaseio.com/");
database = FirebaseDatabase.DefaultInstance.RootReference;
storage = FirebaseStorage.DefaultInstance.RootReference;
auth = FirebaseAuth.DefaultInstance;
}
IsInitialized = true;
}
I had the same errors. I spent several hours solving this and it worked for me

Related

Blazor WebAssembly **Microsoft.JSInterop.JSException** Error: The value 'sessionStorage.length' is not a function

From a basic standpoint what I am trying to do is get a list of keys (key names) from session storage.
The way I am trying to do this is by calling the JsRuntime.InvokeAsync method to:
Get the number of keys in session storage, and
loop thought the number of items in session storage and get the key name.
public async Task<List<string>> GetKeysAsync()
{
var dataToReturn = new List<string>();
var storageLength = await JsRuntime.InvokeAsync<string>("sessionStorage.length");
if (int.TryParse(storageLength, out var slength))
{
for (var i = 1; i <= slength; i++)
{
dataToReturn.Add(await JsRuntime.InvokeAsync<string>($"sessionStorage.key({i})"));
}
}
return dataToReturn;
}
When calling the JsRuntime.InvokeAsync($"sessionStorage.length")) or JsRuntime.InvokeAsync($"sessionStorage.key(0)")) I am getting an error "The value 'sessionStorage.length' is not a function." or The value 'sessionStorage.key(0)' is not a function.
I am able to get a single items using the key name from session storage without issue like in the following example.
public async Task<string> GetStringAsync(string key)
{
return await JsRuntime.InvokeAsync<string>("sessionStorage.getItem", key);
}
When I use the .length or .key(0) in the Chrome console they work as expected, but not when using the JsRuntime.
I was able to get this to work without using the sessionStorage.length property. I am not 100% happy with the solution, but it does work as needed.
Please see below code. The main thing on the .key was to use the count as a separate variable in the InvokeAsync method.
I think the reason for this is the JsRuntime.InvokeAsync method adds the () automatically to the end of the request, so sessionStorage.length is becoming sessionStorage.length() thus will not work. sessionStorage.key(0) was becoming sessionStorage.key(0)(). etc. Just that is just a guess.
public async Task<List<string>> GetKeysAsync()
{
var dataToReturn = new List<string>();
var dataPoint = "1";
while (!string.IsNullOrEmpty(dataPoint) )
{
dataPoint = await JsRuntime.InvokeAsync<string>($"sessionStorage.key", $"{dataToReturn.Count}");
if (!string.IsNullOrEmpty(dataPoint))
dataToReturn.Add(dataPoint);
}
return dataToReturn;
}

How to use runTransaction with firebase_database 9.0.3

Can anyone give where to look, or what I am missing, for how to use runTransaction in 9.0.3? (Flutter, Firebase realtime DB)
I updated firebase_database ^7.0.0 to ^9.0.3. Had to change runTransaction(MutableData mutable) to runTransaction(Object object), and the Object is null for Map data. Does return a String when point to single node of a String.
For something to work I placed + ‘/sap’ at end of .ref(…), and it gave ‘DROID’
Code Updated for Comment below:
bool bCanWeStartGame = false;
bool bIfiOS = false;
bIfiOS = Theme.of(referencedData.mngGamesPageMaterialContext)
.platform == TargetPlatform.iOS;
DatabaseReference playersRef = referencedData.database.ref(
TABLENAME_STARTS + '/' + referencedData.strFBGameUniqueIDKey);
if(bIfiOS){
//apparently iPhone needs to 'open line to DB record' to work
playersRef.once()
.then((DatabaseEvent event) {//dont need to do anything??
});
}
TransactionResult txResult;
//while loop to repeat runTransaction until solved
int iWhileLoopCheck = 0; //so whileLoop does not go forever
bool bTransactionCommittedOrGetOutAnyWay = false;
while(!bTransactionCommittedOrGetOutAnyWay
&& (iWhileLoopCheck<30)){
iWhileLoopCheck++;
txResult = await playersRef
.runTransaction(
(Object post) {
if (post == null) {
if(bIfiOS){sleep(Duration(milliseconds: 3));}
return Transaction.abort(); //out, but will retry transaction
}
Map<String, dynamic> _post = Map<String, dynamic>.from(post as Map);
bTransactionCommittedOrGetOutAnyWay = true; //whichever of
//options below take effect, dont want to continue while loop
if (_post[STARTS_TABLE_ALL_PLAYERS] !=
startingGames[iIndexInStartingGames].allplayers) {
//someone has joined since trying to start
bCanWeStartGame = false;
//Transaction.abort() as dont want to change data
return Transaction.abort();
} else {
//Nobody has joined, so can Start. Tell others Game is starting
_post['stg'] = true;
bCanWeStartGame = true;
return Transaction.success(_post);
}
});
} //while runtransaction doesnot get at data```

Unity3d with Firebase: Can't Start Coroutine

Greeting,
I'm currently facing a problem that my coroutine can't start. This is the first time I facing this issues and I can't find a proper solution online. Much appreciated if anyone can point me to the right direction to solve this issue.
Here are the code.
path_reference.GetDownloadUrlAsync().ContinueWith((Task<Uri> task) => {
if (!task.IsFaulted && !task.IsCanceled)
{
Debug.Log("Download URL: " + task.Result);
StartCoroutine(DownloadStuff(task.Result));
}
else
{
Debug.Log(task.Exception.ToString());
}
});
}
IEnumerator DownloadStuff(Uri uri)
{
Debug.Log("Start Download");
using (var www = UnityWebRequestTexture.GetTexture(uri))
{
yield return www.SendWebRequest();
if (www.isNetworkError || www.isHttpError)
{
Debug.Log(www.error);
}
else
{
var texture = DownloadHandlerTexture.GetContent(www);
//Texture2D texture = new Texture2D(1, 1);
//if you need sprite for SpriteRenderer or Image
Sprite sprite = Sprite.Create(texture, new Rect(0.0f, 0.0f, texture.width,
texture.height), new Vector2(0.5f, 0.5f), 100.0f);
Debug.Log("Finished downloading!");
}
Debug.Log(www.downloadProgress);
}
}'
The task returned by Firebase probably finishes execution on a thread other than the main thread, and Unity coroutines can only run on the main thread.
Unity's support of multithreading and async is pretty spotty, including "eating" some errors if the continuations of those errors would execute on another thread other than the main thread.
To fix this, you need to change the function that starts your coroutine:
try {
// Important: ConfigureAwait(true) ensures the code after this will run in the
// same thread as the code before (which is the main thread!)
var url = await path_reference.GetDownloadUrlAsync().ConfigureAwait(true);
StartCoroutine(DownloadStuff(url));
} catch (Exception ex) {
// Tip: when logging errors, use LogException and pass the whole exception,
// that way you will get pretty links to the error line in the whole stack trace.
Debug.LogException(ex);
}
As an aside, I usually have a few extension methods on all my projects to deal with that while staying in async-world instead of coroutine-world (because at least with async I can catch errors and not just "halt and catch fire" like Unity's coroutines)
The main ones are:
public static Task ToTask(this YieldInstruction self, MonoBehaviour owner) {
var source = new TaskCompletionSource<object>();
IEnumerable Routine() {
yield return self;
source.SetResult(null);
}
return source.Task;
}
private static Task SendAsync(this UnityWebRequest self, MonoBehaviour owner) {
var source = new TaskCompletionSource<object>();
await self.SendWebRequest().ToTask(owner);
if (
self.isHttpError
|| self.isNetworkError
) {
source.SetException(new Exception(request.error));
yield break;
}
source.SetResult(null);
}
Which you can use like this, inside a MonoBehaviour:
await new WaitForSeconds(0.2f).ToTask(this);
UnityWebRequest request = /* etc */;
await request.SendAsync(this);
var texture = DownloadHandlerTexture.GetContent(request);
Note that these methods do not require ConfigureAwait, since their SetResult/SetException invocations are ran from Unity-provided coroutine continuations.

NHibernate (Fluent) not creating database

I don't know if this is NHibernate in general, or fluent... basically I'm trying to make it create a database based on the mappings.
Originally, I tried a Postgres connection, but that wasn't working, so I thought I'd switch to an in memory SQLite one, this didn't work either.
I'm assuming there is something fundamentally wrong with my assumptions on how to make this work, or when it works.
here is the code...
if (_sessionFactory == null)
{
FluentConfiguration configuration;
var rawConfig = new NHibernate.Cfg.Configuration();
configuration = Fluently.Configure(rawConfig)
.Database(SQLiteConfiguration.Standard.InMemory())
.Cache(c => c.ProviderClass<SysCacheProvider>().UseSecondLevelCache())
.ExposeConfiguration(cfg =>
{
cfg.SetProperty("current_session_context_class", "web");
cfg.AddMapping(MapIdentityModels());
var schema = new SchemaUpdate(cfg);
schema.Execute(true, true);
});
_sessionFactory = configuration.BuildSessionFactory();
}
return _sessionFactory;
private static HbmMapping MapIdentityModels()
{
var baseEntityToIgnore = new[] {
typeof(NHibernate.AspNet.Identity.DomainModel.EntityWithTypedId<int>),
typeof(NHibernate.AspNet.Identity.DomainModel.EntityWithTypedId<string>),
};
var allEntities = new[] {
typeof(IdentityUser),
typeof(ApplicationUser),
typeof(IdentityRole),
typeof(IdentityUserLogin),
typeof(IdentityUserClaim),
};
var mapper = new ConventionModelMapper();
DefineBaseClass(mapper, baseEntityToIgnore);
mapper.IsComponent((type, declared) => typeof(NHibernate.AspNet.Identity.DomainModel.ValueObject).IsAssignableFrom(type));
mapper.AddMapping<IdentityUserMap>();
mapper.AddMapping<IdentityRoleMap>();
mapper.AddMapping<IdentityUserClaimMap>();
return mapper.CompileMappingFor(allEntities);
}
private static void DefineBaseClass(ConventionModelMapper mapper, System.Type[] baseEntityToIgnore)
{
if (baseEntityToIgnore == null) return;
mapper.IsEntity((type, declared) =>
baseEntityToIgnore.Any(x => x.IsAssignableFrom(type)) &&
!baseEntityToIgnore.Any(x => x == type) &&
!type.IsInterface);
mapper.IsRootEntity((type, declared) => baseEntityToIgnore.Any(x => x == type.BaseType));
}
I'm getting an error message when anything tries to use the database that it doesn't exist or the table doesn't exist within it.
My goal here is to have the code create tables in a Postgres database on first run.
UPDATE
I've managed to fix this by changing the line
cfg.AddMapping(MapIdentityModels())
to
cfg.AddDeserializedMapping(MapIdentityModels())
Not sure why, but this works, if someone can explain why, and potentially how to achieve the same result without using the ExposeConfiguration route, then I'll mark it as the answer.
It seems that the issue is to do with the mappings not being added properly, so it probably was trying to create it but there weren't any mappings to create.
To fix it, I added the mappings using:
cfg.AddDeserializedMapping(MapIdentityModels())

ASP.NET Entity Framework DataContext use issue

I 've built an ASP.NET website using EF. I created a DataContext class which implements the singleton pattern. My DAO classes (singletons too) instanciate this datacontext and store it in a property. They use it in order to query the SQLServer DataBase. This worked ok for 3 months but I suddenly got exception messages like :"Connection must be valid and open / connection already open". It seemed that datacontext was not disposed. The only change, according to me, was the data size and number of users increasing.
I then found multiple posts saying that singleton was a bad idea foe datacontext, so I tried to instanciate datacontext in a using statement in every request and that resolved the problem, except for update queries which had no effects in database. I had to attach the db object to the context and then set its EntityState to "modified" to have my SaveChanges work.
Like this :
public bool DoucheXpsu(as_headers session) {
using (MyDBEntities MyContext = new MyDBEntities()) {
try {
as_status status = GetStatus(session);
if (status != null) {
if (status.mainstatusvalue == 300) {
status.DateDoucheXpsu = DateTime.Now;
status.DoucheXpsu = 1;
MyContext.as_status.Attach(status);
MyContext.ObjectStateManager.ChangeObjectState(status, EntityState.Modified);
MyContext.SaveChanges();
return true;
} else {
return false;
}
} else {
return false;
}
} catch (OptimisticConcurrencyException) {
return false;
} catch (Exception) {
return false;
}
}
}
The problem is that it actually didn't work for ONE method (which has nothing different from the other update method) !
The exception occured as I tried to attach the object : "The object cannot be attached because it is already in the object context. An object can only be reattached when it is in an unchanged state. " So I had to comment the attach and ChangeObjectState methods to have it work as expected :
public bool SetSessionToDelete(string numSession) {
using (MyDBEntities MyContext = new MyDBEntities()) {
try {
view_headerStatus view = (from v in MyContext.view_headerStatus
where v.CodeSession == numSession
where v.lastinserted == 1
select v).First();
if (view != null) {
as_status status = (from s in MyContext.as_status
where s.jobclsid == view.jobclsid
where s.lastinserted == 1
select s).First();
if (status != null) {
status.DeleteSession = 1;
//MyContext.as_status.Attach(status);
//MyContext.ObjectStateManager.ChangeObjectState(status, EntityState.Modified);
MyContext.SaveChanges();
return true;
} else {
return false;
}
} else {
return false;
}
} catch (OptimisticConcurrencyException) {
return false;
} catch (Exception) {
return false;
}
}
}
The question is WHY should this one behave differently ???
I've read many posts about EF and dataContext but I feel I'm missing something. I would be glad if anyone can help.
Thanks.
In your first example, this line here:
as_status status = GetStatus(session);
I would assume this populates using a DIFFERENT context, and when it leaves the GetStatus() method the context it used to load is disposed. That is why your subsequent Attach() works. However in your second example you do not need to attach because it was loaded using the current (connected) context.
To solve you may want to either pass the context to your methods like GetStatus() resulting in no need to reattach. I don't typically reattach unless I am resurrecting an object over the wire or from a file.

Resources