Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[v11] 3. Remove obsolete WriteAsync overloads #3234

Merged
merged 3 commits into from
Feb 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,22 @@
* `RealmObjectBase.DynamicApi.CreateObject(string)` can be used to create an object without a primary key.
* `RealmObjectBase.DynamicApi.CreateObject(string, string/long?/ObjectId?/Guid?)` can be used to create an object with a primary key of the corresponding type.
* The API exposed by `Realm.DynamicApi` no longer return `dynamic`, instead opting to return concrete types, such as `IRealmObject`, `IEmbeddedObject`, and so on. You can still cast the returned objects to `dynamic` and go through the dynamic API, but that's generally less performant than using the string-based API, such as `IRealmObjectBase.DynamicApi.Get/Set`, especially on AOT platforms such as iOS or Unity. (Issue [#2391](https://github.com/realm/realm-dotnet/issues/2391))
* Removed `Realm.WriteAsync(Action<Realm>)` in favor of `Realm.WriteAsync(Action)`. The new `WriteAsync` method introduced in 10.14.0 is more efficient and doesn't require reopening the Realm on a background thread. While not recommended, if you prefer to get the old behavior, you can write an extension method like:
```csharp
public static async Task WriteAsync(this Realm realm, Action<Realm> writeAction)
{
await Task.Run(() =>
{
using var bgRealm = Realm.GetInstance(realm.Config);
bgRealm.Write(() =>
{
writeAction(bgRealm);
});
});

await realm.RefreshAsync();
}
```

### Enhancements

Expand Down
257 changes: 0 additions & 257 deletions Realm/Realm/Realm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -887,54 +887,6 @@ public async Task<Transaction> BeginWriteAsync(CancellationToken cancellationTok
return _activeTransaction = new Transaction(this);
}

/// <summary>
/// Execute an action inside a temporary <see cref="Transaction"/> on a worker thread, <b>if</b> called from UI thread. If no exception is thrown,
/// the <see cref="Transaction"/> will be committed.
/// </summary>
/// <remarks>
/// Opens a new instance of this Realm on a worker thread and executes <paramref name="action"/> inside a write <see cref="Transaction"/>.
/// <see cref="Realm"/>s and <see cref="RealmObject"/>s/<see cref="EmbeddedObject"/>s are thread-affine, so capturing any such objects in
/// the <c>action</c> delegate will lead to errors if they're used on the worker thread. Note that it checks the
/// <see cref="SynchronizationContext"/> to determine if <c>Current</c> is null, as a test to see if you are on the UI thread
/// and will otherwise just call Write without starting a new thread. So if you know you are invoking from a worker thread, just call Write instead.
/// </remarks>
/// <example>
/// <code>
/// await realm.WriteAsync(tempRealm =&gt;
/// {
/// var pongo = tempRealm.All&lt;Dog&gt;().Single(d =&gt; d.Name == "Pongo");
/// var missis = tempRealm.All&lt;Dog&gt;().Single(d =&gt; d.Name == "Missis");
/// for (var i = 0; i &lt; 15; i++)
/// {
/// tempRealm.Add(new Dog
/// {
/// Breed = "Dalmatian",
/// Mum = missis,
/// Dad = pongo
/// });
/// }
/// });
/// </code>
/// <b>Note</b> that inside the action, we use <c>tempRealm</c>.
/// </example>
/// <param name="action">
/// Action to execute inside a <see cref="Transaction"/>, creating, updating, or removing objects.
/// </param>
/// <returns>An awaitable <see cref="Task"/>.</returns>
[Obsolete("Use Realm.WriteAsync(Action action) instead.")]
public Task WriteAsync(Action<Realm> action)
{
ThrowIfDisposed();

Argument.NotNull(action, nameof(action));

return WriteAsync(tempRealm =>
{
action(tempRealm);
return true;
});
}

/// <summary>
/// Execute an action inside a temporary <see cref="Transaction"/>. If no exception is thrown, the <see cref="Transaction"/> will be committed.
/// <b>If</b> the method is not called from a thread with a <see cref="SynchronizationContext"/> (like the UI thread), it behaves synchronously.
Expand Down Expand Up @@ -970,70 +922,6 @@ public Task WriteAsync(Action action, CancellationToken cancellationToken = defa
}, cancellationToken);
}

/// <summary>
/// Execute a delegate inside a temporary <see cref="Transaction"/> on a worker thread, <b>if</b> called from UI thread. If no exception is thrown,
/// the <see cref="Transaction"/> will be committed.
/// </summary>
/// <remarks>
/// Opens a new instance of this Realm on a worker thread and executes <paramref name="function"/> inside a write <see cref="Transaction"/>.
/// <see cref="Realm"/>s and <see cref="RealmObject"/>s/<see cref="EmbeddedObject"/>s are thread-affine, so capturing any such objects in
/// the <c>action</c> delegate will lead to errors if they're used on the worker thread. Note that it checks the
/// <see cref="SynchronizationContext"/> to determine if <c>Current</c> is null, as a test to see if you are on the UI thread
/// and will otherwise just call Write without starting a new thread. So if you know you are invoking from a worker thread, just call Write instead.
/// </remarks>
/// <example>
/// <code>
/// var dog = await realm.WriteAsync(tempRealm =&gt;
/// {
/// return tempRealm.Add(new Dog
/// {
/// Breed = "Dalmatian",
/// });
/// });
/// </code>
/// <b>Note</b> that inside the action, we use <c>tempRealm</c>.
/// </example>
/// <param name="function">
/// Delegate with one return value to execute inside a <see cref="Transaction"/>, creating, updating, or removing objects.
/// </param>
/// <typeparam name="T">The type returned by the input delegate.</typeparam>
/// <returns>An awaitable <see cref="Task"/> with return type <typeparamref name="T"/>.</returns>
[Obsolete("Use Realm.WriteAsync(Func<T> function) instead.")]
public async Task<T> WriteAsync<T>(Func<Realm, T> function)
{
ThrowIfDisposed();

Argument.NotNull(function, nameof(function));

// If running on background thread, execute synchronously.
if (!AsyncHelper.TryGetValidContext(out _))
{
return Write(() => function(this));
}

// If we are on UI thread the SynchronizationContext will be set (often also set on long-lived workers to use Post back to UI thread).
var result = await Task.Run(() =>
{
using var realm = GetInstance(Config);
var writeAction = realm.Write(() => function(realm));
if (writeAction is IRealmObjectBase rob && rob.IsManaged && rob.IsValid)
{
return (object)ThreadSafeReference.Create(rob);
}

return writeAction;
});

await RefreshAsync();

if (result is ThreadSafeReference.Object<IRealmObjectBase> tsr)
{
return (T)(object)ResolveReference(tsr);
}

return (T)result;
}

/// <summary>
/// Execute a delegate inside a temporary <see cref="Transaction"/>. If no exception is thrown, the <see cref="Transaction"/> will be committed.
/// <b>If</b> the method is not called from a thread with a <see cref="SynchronizationContext"/> (like the UI thread), it behaves synchronously.
Expand Down Expand Up @@ -1080,151 +968,6 @@ public async Task<T> WriteAsync<T>(Func<T> function, CancellationToken cancellat
return result;
}

/// <summary>
/// Execute a delegate inside a temporary <see cref="Transaction"/> on a worker thread, <b>if</b> called from UI thread. If no exception is thrown,
/// the <see cref="Transaction"/> will be committed.
/// </summary>
/// <remarks>
/// Opens a new instance of this Realm on a worker thread and executes <paramref name="function"/> inside a write <see cref="Transaction"/>.
/// <see cref="Realm"/>s and <see cref="RealmObject"/>s/<see cref="EmbeddedObject"/>s are thread-affine, so capturing any such objects in
/// the <c>action</c> delegate will lead to errors if they're used on the worker thread. Note that it checks the
/// <see cref="SynchronizationContext"/> to determine if <c>Current</c> is null, as a test to see if you are on the UI thread
/// and will otherwise just call Write without starting a new thread. So if you know you are invoking from a worker thread, just call Write instead.
/// </remarks>
/// <example>
/// <code>
/// var dogs = await realm.WriteAsync(tempRealm =&gt;
/// {
/// tempRealm.Add(new Dog
/// {
/// Breed = "Dalmatian",
/// });
///
/// tempRealm.Add(new Dog
/// {
/// Breed = "Poddle",
/// });
///
/// return tempRealm.All&lt;Dog&gt;();
/// });
/// </code>
/// <b>Note</b> that inside the action, we use <c>tempRealm</c>.
/// </example>
/// <param name="function">
/// Delegate with return type <see cref="IQueryable{T}"/> to execute inside a <see cref="Transaction"/>, creating, updating, or removing objects.
/// </param>
/// <typeparam name="T">The type of data in the <see cref="IQueryable{T}"/>.</typeparam>
/// <returns>An awaitable <see cref="Task"/> with return type <see cref="IQueryable{T}"/>.</returns>
[Obsolete("Use Realm.WriteAsync(Func<T> function) instead.")]
public async Task<IQueryable<T>> WriteAsync<T>(Func<Realm, IQueryable<T>> function)
where T : IRealmObjectBase
{
ThrowIfDisposed();

Argument.NotNull(function, nameof(function));

// If running on background thread, execute synchronously.
if (!AsyncHelper.TryGetValidContext(out _))
{
return Write(() => function(this));
}

// If we are on UI thread the SynchronizationContext will be set (often also set on long-lived workers to use Post back to UI thread).
var result = await Task.Run(() =>
{
using var realm = GetInstance(Config);
var writeResult = realm.Write(() => function(realm));
if (writeResult is RealmResults<T> rr && rr.IsValid && rr.IsManaged)
{
return (object)ThreadSafeReference.Create(writeResult);
}

return writeResult;
});

await RefreshAsync();

if (result is ThreadSafeReference.Query<T> tsr)
{
return ResolveReference(tsr);
}

return (IQueryable<T>)result;
}

/// <summary>
/// Execute a delegate inside a temporary <see cref="Transaction"/> on a worker thread, <b>if</b> called from UI thread. If no exception is thrown,
/// the <see cref="Transaction"/> will be committed.
/// </summary>
/// <remarks>
/// Opens a new instance of this Realm on a worker thread and executes <paramref name="function"/> inside a write <see cref="Transaction"/>.
/// <see cref="Realm"/>s and <see cref="RealmObject"/>s/<see cref="EmbeddedObject"/>s are thread-affine, so capturing any such objects in
/// the <c>action</c> delegate will lead to errors if they're used on the worker thread. Note that it checks the
/// <see cref="SynchronizationContext"/> to determine if <c>Current</c> is null, as a test to see if you are on the UI thread
/// and will otherwise just call Write without starting a new thread. So if you know you are invoking from a worker thread, just call Write instead.
/// </remarks>
/// <example>
/// <code>
/// var markDogs = await realm.WriteAsync(tempRealm =&gt;
/// {
/// var mark = tempRealm.All&lt;Person&gt;().Single(d =&gt; d.Name == "Mark");
///
/// mark.Dogs.Add(new Dog
/// {
/// Breed = "Dalmatian",
/// });
///
/// mark.Dogs.Add(new Dog
/// {
/// Breed = "Poodle",
/// });
///
/// return mark.Dogs;
/// });
/// </code>
/// <b>Note</b> that inside the action, we use <c>tempRealm</c>.
/// </example>
/// <param name="function">
/// Delegate with return type <see cref="IList{T}"/> to execute inside a <see cref="Transaction"/>, creating, updating, or removing objects.
/// </param>
/// <typeparam name="T">The type of data in the <see cref="IList{T}"/>.</typeparam>
/// <returns>An awaitable <see cref="Task"/> with return type <see cref="IList{T}"/>.</returns>
[Obsolete("Use Realm.WriteAsync(Func<T> function) instead.")]
public async Task<IList<T>> WriteAsync<T>(Func<Realm, IList<T>> function)
{
ThrowIfDisposed();

Argument.NotNull(function, nameof(function));

// If running on background thread, execute synchronously.
if (!AsyncHelper.TryGetValidContext(out _))
{
return Write(() => function(this));
}

// If we are on UI thread the SynchronizationContext will be set (often also set on long-lived workers to use Post back to UI thread).
var result = await Task.Run(() =>
{
using var realm = GetInstance(Config);
var writeResult = realm.Write(() => function(realm));
if (writeResult is RealmList<T> rl && rl.IsValid && rl.IsManaged)
{
return (object)ThreadSafeReference.Create(writeResult);
}

return writeResult;
});

await RefreshAsync();

if (result is ThreadSafeReference.List<T> tsr)
{
return ResolveReference(tsr);
}

return (IList<T>)result;
}

/// <summary>
/// Update the <see cref="Realm"/> instance and outstanding objects to point to the most recent persisted version.
/// </summary>
Expand Down
81 changes: 0 additions & 81 deletions Tests/Realm.Tests/Database/AsyncTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,87 +30,6 @@ namespace Realms.Tests.Database
[TestFixture, Preserve(AllMembers = true)]
public class AsyncTests : RealmInstanceTest
{
[Test, Obsolete("Tests deprecated WriteAsync API")]
public void AsyncWrite_ShouldExecuteOnWorkerThread()
{
TestHelpers.RunAsyncTest(async () =>
{
var currentThreadId = Environment.CurrentManagedThreadId;
var otherThreadId = currentThreadId;

Assert.That(_realm.All<Person>().Count(), Is.EqualTo(0));
Assert.That(SynchronizationContext.Current != null);
await _realm.WriteAsync(realm =>
{
otherThreadId = Environment.CurrentManagedThreadId;
realm.Add(new Person());
});

Assert.That(_realm.All<Person>().Count(), Is.EqualTo(1));
Assert.That(otherThreadId, Is.Not.EqualTo(currentThreadId));
});
}

[Test, Obsolete("Tests deprecated WriteAsync API")]
public void AsyncWrite_WhenOnBackgroundThread_ShouldExecuteOnSameThread()
{
TestHelpers.RunAsyncTest(async () =>
{
await Task.Run(async () =>
{
var currentThreadId = Environment.CurrentManagedThreadId;
var otherThreadId = -1;

Assert.That(_realm.All<Person>().Count(), Is.EqualTo(0));
Assert.That(SynchronizationContext.Current == null);

await _realm.WriteAsync(realm =>
{
otherThreadId = Environment.CurrentManagedThreadId;
realm.Add(new Person());
});

Assert.That(_realm.All<Person>().Count(), Is.EqualTo(1));
Assert.That(otherThreadId, Is.EqualTo(currentThreadId));

_realm.Dispose();
});
});
}

[Test, Obsolete("Tests deprecated WriteAsync API")]
public void AsyncWrite_UpdateViaPrimaryKey()
{
TestHelpers.RunAsyncTest(async () =>
{
IntPrimaryKeyWithValueObject obj = null;
_realm.Write(() =>
{
obj = _realm.Add(new IntPrimaryKeyWithValueObject { Id = 123 });
});

await _realm.WriteAsync(realm =>
{
var dataObj = realm.Find<IntPrimaryKeyWithValueObject>(123);
dataObj.StringValue = "foobar";
});

// Make sure the changes are immediately visible on the caller thread
Assert.That(obj.StringValue, Is.EqualTo("foobar"));
});
}

[Test, Obsolete("Tests deprecated WriteAsync API")]
public void AsyncWrite_ShouldRethrowExceptions()
{
TestHelpers.RunAsyncTest(async () =>
{
const string message = "this is an exception from user code";
var ex = await TestHelpers.AssertThrows<Exception>(() => _realm.WriteAsync(_ => throw new Exception(message)));
Assert.That(ex.Message, Is.EqualTo(message));
});
}

[Test]
public void RefreshAsync_Tests()
{
Expand Down
Loading