Dart allows for chaining futures to invoke more than one async method in sequence without nesting callbacks, which is awesome.
Let's say we want to first connect to a data store like Redis, and then run a bunch of sequential reads:
Future<String> FirstValue(String indexKey)
{
return RedisClient.connect(Config.connectionStringRedis)
.then((RedisClient redisClient) => redisClient.exists(indexKey))
.then((bool exists) => !exists ? null : redisClient.smembers(indexKey))
.then((Set<String> keys) => redisClient.get(keys.first))
.then((String value) => "result: $value");
}
Four async methods and yet the code is fairly easy to read and understand. It almost looks like the steps are executed synchronously and in sequence. Beautiful! (Imagine having to write the same code using nested JavaScript callbacks...)
Unfortunately, this won't quite work: the RedisClient we get from the .connect method is only assigned to a local variable which is not in scope for the subsequent .thens. So, redisClient.smembers and redisClient.get will actually throw a null pointer exception.
The obvious fix is to save the return value in another variable with function scope:
Future<String> FirstValue(String indexKey)
{
RedisClient redisClient = null;
return RedisClient.connect(Config.connectionStringRedis)
.then((RedisClient theRedisClient)
{
redisClient = theRedisClient;
return redisClient.exists(indexKey);
})
.then((bool exists) => !exists ? null : redisClient.smembers(indexKey))
.then((Set<String> keys) => redisClient.get(keys.first))
.then((String value) => "result: $value");
}
Unfortunately, this makes the code more verbose and less beautiful: there's now an additional helper variable (theRedisClient), and we had to replace one of the Lambda expressions with an anonymous function, adding a pair of curly braces and a return statement and another semicolon.
Since this appears to be a common pattern, is there a more elegant way of doing this? Any way to access those earlier intermediate further down the chain?
You can use a nested assignment to avoid curly braces and return :
.then((RedisClient rc) => (redisClient = rc).exists(indexKey))
You can do scopes with futures too, by not putting all the 'then' calls at the same level.
I'd do something like:
Future<String> FirstValue(String indexKey) =>
RedisClient.connect(Config.connectionStringRedis)
.then((RedisClient redisClient) =>
redisClient.exists(indexKey)
.then((bool exists) => !exists ? null : redisClient.smembers(indexKey))
.then((Set<String> keys) => redisClient.get(keys.first))
.then((String value) => "result: $value");
);
Indentation is always difficult with code like this. This example follows the Dart style guide, but I think it could be more readable with less indentation of the then calls:
Future<String> FirstValue(String indexKey) =>
RedisClient.connect(Config.connectionStringRedis)
.then((RedisClient redisClient) =>
redisClient.exists(indexKey)
.then((bool exists) => !exists ? null : redisClient.smembers(indexKey))
.then((Set<String> keys) => redisClient.get(keys.first))
.then((String value) => "result: $value");
);
Related
What is Dart equivalent to get an element of a sequence and transform it with a map() fluently through a pipeline.
Something like doing in Java streams:
var result = ....findFirst().map(elem -> transform(elem)).get();
It does not really make much sense to have a "pipeline" for a single event (if that was what you mean by the findFirst() call). But if you want to, you can combine something with where() and take() so you keep the Iterable and therefore be able to use map on it:
void main() {
print(
[1, 2, 3]
.where((element) => element == 2)
.take(1) // Could also be skipped because of .first later
.map((e) => e.toRadixString(2))
.map((e) => e.padLeft(8, '0'))
.map((e) => 'Number in binary is: $e')
.first,
); // Number in binary is: 00000010
}
just wondering how I can get rid of this warning:
Microsoft.EntityFrameworkCore.Model.Validation[10620]
The property 'FI' on entity type 'Routes' is a collection or enumeration type with a value converter but with no
value comparer. Set a value comparer to ensure the
collection/enumeration elements are compared correctly.
Code:
modelBuilder.Entity<Routes>().Property(l => l.FI).HasConversion(
FI=> String.Join(",", FI),
dbVal => dbVal.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList()
);
where FI is defined in the model as:
public List<string> FI { get; set; }
I understand I have to do a SetValueComparer call somehow but I am not sure how. The examples on line are a bit different and don't have the String.Join and dbVal lines.
Thank you
From the Microsoft docs - this is a example of how to set a value comparer (inside of HasConversion()):
modelBuilder
.Entity<EntityType>()
.Property(e => e.MyListProperty)
.HasConversion(
v => JsonSerializer.Serialize(v, (JsonSerializerOptions)null),
v => JsonSerializer.Deserialize<List<int>>(v, (JsonSerializerOptions)null),
new ValueComparer<List<int>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToList()
)
);
You need to choose the appropriate value comparison, of course (List<string> instead of List<int>, for example).
UPDATE: adapted to your code snippet, it should look like this (don't know, if it works, though):
modelBuilder
.Entity<Routes>()
.Property(l => l.FI)
.HasConversion(
fi => String.Join(",", fi),
dbVal => dbVal.Split(',', StringSplitOptions.RemoveEmptyEntries).ToList(),
new ValueComparer<List<string>>(
(c1, c2) => c1.SequenceEqual(c2),
c => c.Aggregate(0, (a, v) => HashCode.Combine(a, v.GetHashCode())),
c => c.ToList()
)
);
Once in a while we all need to quickly return multiple values from a function, and look for a way to create a new type on the fly.
In Python I can return a tuple
def get_trio1():
return (True, 23, "no")
(_, first1, second1) = get_trio1()
print(first1)
print(second1)
ignore one of the values, and retrieve both of the other two on the fly in one assignment.
I can likewise return an array.
def get_trio2():
return [True, 23, "no"]
[_, first2, second2] = get_trio2()
print(first2)
print(second2)
But both of these are brittle. If I edit the code to add a value, particularly if it's within the three already defined, the revised code could fail silently.
Which is why the nicest solution is to create a dict on the fly.
def get_trio3():
return {"b": True, "i": 23, "s": "no"}
r = get_trio3()
print(r["i"])
print(r["s"])
The use of named members means that maintaining the code is considerably safer.
What is the closest I can do to get the same safety in Dart? Is defining a class for the return type necessary?
In case it matters, the context is avoiding List<List<dynamic>> when returning a future.
Future<List<dynamic>> loadAsset() async =>
return await Future.wait([
rootBundle.loadString('assets/file1.txt'),
rootBundle.loadString('assets/file2.txt'),
]);
Update
Using Stephen's answer for a future introduces a problem. Future.wait is hardwired to use an array Iterable.
Future<Map<String, dynamic>> loadAsset() async =>
return await Future.wait({
"first": rootBundle.loadString('assets/file1.txt'),
"second": rootBundle.loadString('assets/file2.txt'),
});
Your loadAsset function returns a Future<List<dynamic>> because that's how you declared it. You could have declared it to return a Future<List<String>> instead.
Future.wait is hardwired to use an array.
Especially since Dart is a statically-typed language, you can't really expect it to take both a List and some Map with your custom semantics. You could write your own version:
Future<Map<String, T>> myFutureWait<T>(Map<String, Future<T>> futuresMap) async {
var keys = futuresMap.keys.toList();
var values = futuresMap.values.toList();
var results = await Future.wait<T>(values);
return Map.fromIterables(keys, results);
}
Use a map.
Map<String, dynamic> foo() => {'A': 'a', 'B': 'b', 'C': false }
var result = foo();
print result['B']; // prints b;
How do I get the count of items from a lambda expression in a veiwbag ?
I dont want to use model.count() as I have a model directive for other models and this is for something different
here is my code
var Count = _context.Users_Accounts_Address
.Where(c => c.Email == user)
.Select(c => c.Post_Code + " " +c.AddressType );
ViewBag.ReturnCount = Count.CountAsync();
and my view I put
#ViewBag.ReturnCount
At runtime However I get back
System.Threading.Tasks.Task`1[System.Int32]
When you call .CountAsync() you get back an asynchronous Task<T> object (in this case T is an int as that's the return type of the non-async .Count() method.
You should use either:
ViewBag.ReturnCount = Count.Count();
or
ViewBag.ReturnCount = await Count.CountAsync();
(if your controller is async)
ZF2 docs show the following example in terms of using Db\RecordExists validator with multiple columns.
$email = 'user#example.com';
$clause = $dbAdapter->quoteIdentifier('email') . ' = ' . $dbAdapter->quoteValue($email);
$validator = new Zend\Validator\Db\RecordExists(
array(
'table' => 'users',
'field' => 'username',
'adapter' => $dbAdapter,
'exclude' => $clause
)
);
if ($validator->isValid($username)) {
// username appears to be valid
} else {
// username is invalid; print the reason
$messages = $validator->getMessages();
foreach ($messages as $message) {
echo "$message\n";
}
}
I’ve tried this using my own Select object containing a more complex where condition. However, isValid() must be called with a value parameter.
In the example above $username is passed to isValid(). But there seems to be no according field definition.
I tried calling isValid() with an empty string, but this does not produce the desired result, since Zend\Validator\Db\AbstractDb::query() always adds the value to the statement:
$parameters = $statement->getParameterContainer();
$parameters['where1'] = $value;
If I remove the seconds line above, my validator produces the expected results.
Can someone elaborate on how to use RecordExists with the where conditions in my custom Select object? And only those?
The best way to do this is probably by making your own validator that extends one of Zend Framework's, because it doesn't seem like the (No)RecordExists classes were meant to handle multiple fields (I'd be happy to be proven wrong, because it'd be easier if they did).
Since, as you discovered, $parameters['where1'] is overridden with $value, you can deal with this by making sure $value represents what the value of the first where should be. In the case of using a custom $select, $value will replace the value in the first where clause.
Here's a hacky example of using RecordExists with a custom select and multiple where conditions:
$select = new Select();
$select->from('some_table')
->where->equalTo('first_field', 'value1') // this gets overridden
->and->equalTo('second_field', 'value2')
;
$validator = new RecordExists($select);
$validator->setAdapter($someAdapter);
// this overrides value1, but since isValid requires a string,
// the redundantly supplied value allows it to work as expected
$validator->isValid('value1');
The above produces the following query:
SELECT `some_table`.* FROM `some_table` WHERE `first_field` = 'value1' AND `second_field` = 'value2'
...which results in isValid returning true if there was a result.