I'm trying to connect to MongoDB from my code using mongo_dart package.
So my approach is below,
import 'package:mongo_dart/mongo_dart.dart';
class MongoDB {
late Db db;
MongoDB(
{hosts = const ['myserver1', 'myserver2', 'myserver3'],
port = '27017',
username = 'admin',
password = 'mypassword',
dbname = 'mydb',
authSource = 'admin'}) {
db = Db.pool(
hosts.map((elem) => "mongodb://$username:$password#$elem:$port/$dbname?authSource=$authSource").toList());
}
When executing this code an exception occurs which has the message Expected a value of type 'List<String>', but got one of type 'List<dynamic>' on .toList().
All the parameters are string types, but why does it happen?
Please be aware that i am not an expert on Dart's type inference, but this is probably what is happening:
The host argument for your MongoDB constructor omits specific type information, which does not give the analyzer enough information to infer that you always want a List<String>. So it assumes it to be dynamic
There are multiple way to fix this:
Specify the type in the arguments of the constructor: MongoDB({List<String> hosts = const ['myserver1', 'myserver2', 'myserver3'], ...})
Specify that your map will always return a String and thus toList() will produce a List<String>: hosts.map<String>((elem) => "mongodb://$username:$password#$elem:$port/$dbname?authSource=$authSource").toList()
Related
I have a map consisting of different types and strings:
const Map<Type, String> hiveTableNames = {
BreakTimeDto: "breaktime",
WorkTimeDto: "worktime"
};
And I want to loop through it because I want to call a function for each type which takes a type parameter:
Future<void> sendAll<T>(List item) async {
...
}
My attempt was to use the forEach-loop:
hiveTableNames.forEach((key, value) async {
final box = await Hive.openBox(value);
_helper.sendAll<key>(box.values.cast<key>().toList());
});
But the App throws an error: Error: 'key' isn*t a type.
Why is that? I declared the map to store types and from my understanding i pass these types in the function.
Dart separates actual types and objects of type Type. The latter are not types, and cannot be used as types, they're more like mirrors of types. A Type object can only really be used for two things: as tokens to use with dart:mirrors and comparing for equality (which isn't particularly useful except for very simple types).
The only things that can be used as type arguments to generic functions or classes are actual literal types or other type variables.
In your case, you have a Type object and wants to use the corresponding type as a type argument. That won't work, there is no way to go from a Type object to a real type.
That's a deliberate choice, it means that the compiler can see that if a type is never used as a type argument in the source code, then it will never be the type bound to a type parameter, so if you have foo<T>(T value) => ... then you know that T will never be Bar if Bar doesn't occur as a type argument, something<Bar>(), anywhere in the program.
In your case, what you can do is to keep the type around as a type by using a more complicated key object.
Perhaps:
class MyType<T> {
const MyType();
R use<R>(R Function<X>() action) => action<T>();
int get hashCode => T.hashCode;
bool operator==(Object other) => other is MyType && other.use(<S>() => T == S);
}
This allows you to store the type as a type:
final Map<MyType, String> hiveTableNames = {
const MyType<BreakTimeDto>(): "breaktime",
const MyType<WorkTimeDto>(): "worktime"
};
(I'm not making the map const because const maps must not have keys which override operator==).
Then you can use it as:
hiveTableNames.forEach((key, value) async {
final box = await Hive.openBox(value);
key.use(<K>() =>
_helper.sendAll<K>([for (var v in box.values) v as K]);
}
(If all you are using your map for is iterating the key/value pairs, then it's really just a list of pairs, not a map, so I assume you are using it for lookups, which is why MyType override operator==).
In general, you should avoid using Type objects for anything, they're very rarely the right tool for any job.
In this article it shows how to use the SqlCommandProvider type. The sample code has this:
use cmd = new SqlCommandProvider<"
SELECT TOP(#topN) FirstName, LastName, SalesYTD
FROM Sales.vSalesPerson
WHERE CountryRegionName = #regionName AND SalesYTD > #salesMoreThan
ORDER BY SalesYTD
" , connectionString>(connectionString)
what does the <... ,...> before the type constructor name mean and why the the
first parameter have to be a string literal? It looks like a generic but it's taking variables not types. The constructor seems to be taking in a connection string already in the <> section.
The angle brackets are the configuration for a type.
In your example, you are defining a type and creating an instance at the same type. It's clearer when the steps are separated.
Define a type.
type SalesPersonQuery = SqlCommandProvider<query, connectionString>
But to actually have an instance of the type you have to create it:
let command = new SalesPersonQuery()
Now you can use the command.Execute() rather then SalesPersonQuery.Execute().
The reason there is a constructor is because later on (at run-time) you can change the connection string to a different then the one provided in the definition, so for instance:
let command = new SalesPersonQuery(differentConnectionString)
You can find that in the documentation in configuration section:
Connection string can be overridden at run-time via constructor optional parameter
First parameter can be a path to a SQL script or a SQL query. I suppose that's the reason it's a string: how else would you like to define a SQL query?
Again, from the documentation:
Command text (sql script) can be either literal or path to *.sql file
The normal way of using a SqlDataConnection type provider is as follows:
type dbSchema = SqlDataConnection<"Data Source=MYSERVER\INSTANCE;InitialCatalog=MyDatabase;Integrated Security=SSPI;">
let db = dbSchema.GetDataContext()
However we have a problem which is we want to use this type provider in an f# script where the connection string for the database is passed as a parameter. So what I would like to do is something like this:
let connectionString= Array.get args 1
type dbSchema = SqlDataConnection<connectionString>
However it gives the error "This is not a constant expression or valid custom attribute value"
Is there any way to do this?
Unfortunately there's no way to do this, the type provider requires a compile time literal string. This is so that when you're compiling the application, the type provider's able to connect and retrieve the metadata about the database and generate the types for the compiler. You can choose to extract out the connection string into a string literal by writing it in the form
[<Literal>] let connString = "Data Source=..."
type dbSchema = SqlDataConnection<connString>
Assuming your 2 databases have the same schema, it's then possible to supply your runtime connection string as a parameter to the GetDataContext method like
let connectionString = args.[1]
let dbContext = dbSchema.GetDataContext(connectionString)
The way i've been doing it is i have a hardcoded literal string (using the "Literal" attribute) for design time use and use a local string from the configuration when getting the data context. I use a local db schema (also hardcoded) to speed up intelli-sense during development.
type private settings = AppSettings<"app.config">
let connString = settings.ConnectionStrings.MyConnectionString
type dbSchema = Microsoft.FSharp.Data.TypeProviders.SqlDataConnection<initialConnectionString, Pluralize = true, LocalSchemaFile = localDbSchema , ForceUpdate = false, Timeout=timeout>
let indexDb = dbSchema.GetDataContext(connString);
Sample code that explains problem.
import "dart:mirrors";
void main() {
var type = getTypeFromDeclaration();
var typeArguments = getAnotherTypeArguments();
var myType = reflectType(type, typeArguments);
}
How to getting in Dart the type mirror with the specified type arguments through reflection?
P.S.
I think that I don't need to explain "Why this need?" because we all know that this functionality required for the data hydration.
Also this is very useful in data codecs that used reflection for the better data consistency.
As you noticed, I will not explain why.
First, I agree that it would be best if the mirror system allowed creating a type mirror of Map from the type mirrors of Foo and Bar and the class mirror of Map. That is currently not the case.
Without that, I don't think you can solve the problem as written.
There is no way to create a parameterized type with a type argument that is not known as a type at compile-time.
For completeness, I'll include a way to reflect a parameterized type if you know the type at compile time. If the type arguments can be represented as something else than a Type object or a TypeMirror object, you can build your own representation that allows operations like this.
If you can't use reflectType(Map<Foo,Baz>) because Map<Foo,Baz> is not a valid type literal, there is a small workaround to get a Type for any type: Have a class with a type parameter and a way to get the Type value of that parameter.
import "dart:mirrors";
class Typer<T> { Type get type => T; }
main() {
var mapStringInt = reflectType(new Typer<Map<String,int>>().type);
print(mapStringInt); // ClassMirror on 'Map'
print(mapStringInt.typeArguments); // [ClassMirror on 'String', ClassMirror on 'int']
// That is: it's a TypeMirror on Map<String,int>.
}
I am using a TypeProvider to access some data like this:
type internal SqlConnection =
SqlEntityConnection<ConnectionString =
#"XXXXXXXXXX">
type FooRepository () =
member x.GetFoo (Id) =
let context = SqlConnection.GetDataContext()
query {for foo in context.Foos do
where (foo.Id = Id)
select foo}
The problem is I am getting this:
The type 'FooRepository' is less accessible than the value, member or type 'member System.Linq.IQueryable' it is used in
I see the same problem asked on SO here.
Since I want to expose this type and I don't like the solutions on SO, I tried doing this:
SqlDataConnection<ConnectionString =
#"XXXXXXX">
The problem is that the database is on Azure Sql Server so I am getting this:
The type provider 'Microsoft.FSharp.Data.TypeProviders.DesignTime.DataProviders' reported an error: Error reading schema. Warning : SQM1012: Unable to extract table 'dbo.Foos from SqlServer.
Does anyone have an idea about how to get around this? One of the best things about using TPs is that I don't need to explicitly defines the types - but it looks like I am going to have to? Ugh
Are you sure the error you are getting is the one you posted here? My impression is that
the type which is less accessible should be the one from the generated database schema
(because you mark that as internal).
I tried reproducing your error by writing a simple library that uses the Northwind database:
open Microsoft.FSharp.Data.TypeProviders
type internal Nwind = SqlDataConnection<"Data Source=.\sqlexpress;Initial Catalog=Northwind;Integrated Security=True">
type NwindLibrary() =
let nw = Nwind.GetDataContext()
member this.Products = nw.Products
And I get the following message:
The type 'Products' is less accessible than the value, member or type 'member Class1.Products : System.Data.Linq.Table' it is used in C:\temp\Library1\Library1\Library1.fs 9 17 Library1
This is what I was expecting - because the Nwind type (generated by a provider) is marked as internal. However, the NwindLibrary type is public and the property Products (that I defined) returns a value of type IQueryable<Table<Nwind.ServiceTypes.Product>>. The compiler error is not particularly useful here (because it says that the type returns just Table), but the problem is that Nwind.ServiceTypes.Product is internal and so you cannot return it in a public property of a public type.
If you make the Nwind type public, then it should work fine. If you do not want to make the generated types public, then you'll have to wrap the returned values in your custom types. For example, even if you leave the type internal, the following works fine (returning a tuple instead of generated type):
member this.Products =
query { for p in mys.Products do
select (p.ProductID, p.ProductName) }