I am trying to create a type safe data access layer in F# using FSharp.Data.SqlClient type providers to be callable from C#. I have highly complicated SQL queries that are performance critical but there are also several variations.
Given the following (obviously simplified schema):
CREATE TABLE [dbo].[company] (
[id] INT IDENTITY (1, 1) NOT NULL,
[name] VARCHAR (50) NOT NULL)
I have the following F# code:
module CompanyDAL =
open FSharp.Data // requires FSharp.Data.SqlClient package
[<Literal>]
let conn = "Data Source=(localdb)\ProjectsV13;Initial Catalog=TESTDB;Integrated Security=True;Pooling=False;Connect Timeout=30"
[<Literal>]
let baseQuery = "select id, name from company where 1 = 1 "
[<Literal>]
let filterById = "and id = #id"
[<Literal>]
let filterByName = "and name = #name"
[<Literal>]
let queryFilteredById = baseQuery + filterById
type GetCompanyById = SqlCommandProvider<queryFilteredById, conn>
[<Literal>]
let queryFilterbyName = baseQuery + filterByName
type GetCompanyByName = SqlCommandProvider<queryFilterbyName, conn>
type GetAllCompanies = SqlCommandProvider<baseQuery, conn>
type Company = {
Name : string
id : int
}
let getCompanyById (runtimeConn:string) =
async {
use query = new GetCompanyById(runtimeConn)
let! result = query.AsyncExecute(id = 10)
return result
|> Seq.map (fun x ->
{ Name = x.name
id = x.id })
|> Seq.toArray
} |> Async.StartAsTask
let getCompanyByName (runtimeConn:string) =
async {
use query = new GetCompanyByName(runtimeConn)
let! result = query.AsyncExecute(name = "abc" )
return result
|> Seq.map (fun x ->
{ Name = x.name
id = x.id })
|> Seq.toArray
} |> Async.StartAsTask
let getAllCompanies (runtimeConn:string) =
async {
use query = new GetAllCompanies(runtimeConn)
let! result = query.AsyncExecute()
return result
|> Seq.map (fun x ->
{ Name = x.name
id = x.id })
|> Seq.toArray
} |> Async.StartAsTask
Is there any way I can reduce the number of duplication while maintaining the raw performance of type safe raw queries?
I've tried using
type GetCompany = SqlEnumProvider<baseQuery, conn, provider>
Then I can write this code
GetCompany.Items |> Seq.where(fun (id, name) -> name = "xxx") |> printfn "%A"
So I can avoid duplication and pass a generic predicate fun (id, name) -> to the only GetCompany type.
Similarly,
GetCompany.Items |> Seq.where(fun (id, name) -> id = "1")
would work as well.
Note
Anyway, notice that the above will filter in memory. In fact Dynamic SQL is not permitted with this library, because it relies only on static analysis at compile time. If you need to construct queries dynamically, you can revert to raw ADO.NET or other light ORM.
Related
I am using the below code to insert to a Postgres DB using tokio-postgres, is there any better option :
let members = &[obj] //obj is a struct
let mut params = Vec::<&(dyn ToSql + Sync)>::new();
let mut i = 1;
let mut qry:String = "insert into tablename(id,userid,usertype) values".to_string();
for column in members{
if(i ==1){
qry = format!("{} (${},${},${})",qry,i,i+1,i+2);
}else{
qry = format!("{}, (${},${},${})",qry,i,i+1,i+2);
}
params.push(&column.id);
params.push(&column.userid);
params.push(&column.usertype);
i = i+3;
}
println!("qry : {}",qry);
let result = p.execute(&qry, ¶ms[..]).await; //p is the pool manager
No:
Inserting multiple values at the same time
Ability to insert multiple rows by specifying multiple rows in VALUES?
You can marginally improve it by using iterators:
use itertools::Itertools; // For tuples() and format_with()
let params: Vec<_> = members
.iter()
.flat_map(|row| [&row.id as &(dyn ToSql + Sync), &row.userid, &row.usertype])
.collect();
let query = format!(
"insert into tablename(id, userid, usertype) values {}",
(0..params.len())
.tuples()
.format_with(", ", |(i, j, k), f| {
f(&format_args!("(${i}, ${j}, ${k})"))
}),
);
However I don't really think that's better.
I have an SQL statement where I would like to pass a list of tuples customerIdSubCustomerPairs so that I can fetch all the rows matching the where clause from the table customers.
This is the SQL for it:
#Language("PostgreSQL")
private const val getExistingRowsSql = """
select customerId, subcustomer
from customer
where (uuid IN (:uuids) AND is_deleted = false)
union
select customerId, subcustomer
from customer
where (customerId, subcustomer) IN (:customerIdSubCustomerPairs) AND is_deleted = false
"""
I have a list of customers from which I am building a list of lists for customerIdSubCustomerPairs.
It looks like this:
val existingCustomers = fetchRows(
ctx, getExistingRowsSql, mapOf(
"uuids" to customers.map { it["uuid"] },
"customerIdSubCustomerPairs" to customers.map { listOf(it["customerId"] as String, it["subCustomer"] as String) }
))
And I have a function where I convert values to Oid types:
fun jdbcConvertValue(session: Session, v: Any?): Any? =
when (v) {
is Collection<*> -> {
when (val type = v.firstOrNull()) {
null -> null
is String -> session.connection.underlying.createArrayOf("text", v.toTypedArray())
is Long -> session.connection.underlying.createArrayOf("bigint", v.toTypedArray())
is Int -> session.connection.underlying.createArrayOf("bigint", v.toTypedArray())
is UUID -> session.connection.underlying.createArrayOf("uuid", v.toTypedArray())
is List<*> -> v.map { jdbcConvertValue(session, it) }.toTypedArray()
else -> throw Exception("You need to map your array type $type ${type.javaClass}")
}
}
else -> v
}
However, when I try to fetch rows like that, I get an error:
org.postgresql.util.PSQLException: Cannot cast an instance of [Ljava.lang.Object; to type Types.ARRAY
I am not sure how to pass values as tuples into a SQL statement?
The fields customerId and subcustomer are both text columns in the DB table.
The SQLite.swift documentation says about executing arbitrary SQL:
let stmt = try db.prepare("SELECT id, email FROM users")
for row in stmt {
for (index, name) in stmt.columnNames.enumerate() {
print ("\(name)=\(row[index]!)")
// id: Optional(1), email: Optional("alice#mac.com")
}
}
I wanted to get the values directly like this
let stmt = try db.prepare("SELECT id, email FROM users")
for row in stmt {
let myInt: Int64 = row[0] // error: Cannot convert value of type 'Binding?' to specified type 'Int64'
let myString: String = row[1] // error: Cannot convert value of type 'Binding?' to specified type 'String'
}
but the row index is of type Binding? and I can't figure out how to convert that to the type I need. I see there is a Statement.bind method in the source code but I am still not discovering how to apply it.
You can retrieve correctly typed selected columns from a table like this:
// The database.
let db = try Connection(...)
// The table.
let users = Table("users")
// Typed column expressions.
let id = Expression<Int64>("id")
let email = Expression<String>("email")
// The query: "SELECT id, email FROM users"
for user in try db.prepare(users.select(id, email)) {
let id = user[id] // Int64
let mail = user[email] // String
print(id, mail)
}
An alternative is to (optionally) cast the Binding values
to the correct type:
let stmt = try db.prepare("SELECT id, email FROM users")
for row in stmt {
if let id = row[0] as? Int64,
let mail = row[1] as? String {
print(id, mail)
}
}
I am using Entity Framework 3.5. My model has a Mediator table and a MediatorAvailabilities table. Most Mediators do not have an entry in MediatorAvailabilities (Availability = Null) but I still need to bring back the mediator whether or not there is a related MediatorAvailabilities.
My query below is only bring back a mediator if there is a related Availability. Again how do I get mediators even if Availability = null?:
Dim mediators = (From m In entity.Mediators.Include("MediatorAvailabilities") _
Where(m.MediatorAvailabilities.Any(Function(a) a.Availability = String.Empty Or a.Availability.Contains("Weekends") = True))
Where (m.isActive = True) _
Order By m.Sequence _
Select New RankingCriteria() With { _
.FirstName = m.FirstName, _
.LastName = m.LastName, _
.CompanyName = m.CompanyName, _
.PhoneHome = m.PhoneHome, _
.PhoneWork = m.PhoneWork, _
.PhoneMobile = m.PhoneMobile, _
.Email = m.Email _
}).ToList()
What's the correct way to do this?
I think you need to add a.Availability is Nothing in your Any method call like so:
Where(m.MediatorAvailabilities.Any(Function(a) a.Availability is Nothing Or a.Availability = String.Empty Or a.Availability.Contains("Weekends") = True))
I have
List<Attributes> la = new List<Attributes>();
la = (from t in result
let t1 = t.AttributeCollection
from t2 in t1
where t2.AttributeCode.Equals(attributeType)
let t3 = t2.TimeSeriesData
from k in t3.ToList()
where k.Key.Equals(startDate) && k.Key.Equals(endDate)
select new
{
AttributeCode = attributeType,
TimeSeriesData = fn(k.Key, k.Value.ToString())
}).ToList<Attributes>();
I am getting the error:
'System.Collections.Generic.IEnumerable<AnonymousType#1>' does not contain a definition for 'ToList' and the best extension method overload 'System.Linq.Enumerable.ToList<TSource>(System.Collections.Generic.IEnumerable<TSource>)' has some invalid arguments
I understood tye error meaning but how to type cast it. I have used var and then iterating over it got the result. But without that any other way by which I can do it?
Using C# 3.0
Thanks
may be:
List<Attributes> la = new List<Attributes>();
la = (from t in result
let t1 = t.AttributeCollection
from t2 in t1
where t2.AttributeCode.Equals(attributeType)
let t3 = t2.TimeSeriesData
from k in t3.ToList()
where k.Key.Equals(startDate) && k.Key.Equals(endDate)
select new Attributes()
{
AttributeCode = attributeType,
TimeSeriesData = fn(k.Key, k.Value.ToString())
}).ToList<Attributes>();
you are constructing anonymous type with same fields as Attributes. But they are different types and can't be cast to each other.
Basically save it to an anonymous list type, and then call a conversion function.
public void TestMethod(){
List<Attributes> la = new List<Attributes>();
var annonyType = (from t in result
let t1 = t.AttributeCollection
from t2 in t1
where t2.AttributeCode.Equals(attributeType)
let t3 = t2.TimeSeriesData
from k in t3.ToList()
where k.Key.Equals(startDate) && k.Key.Equals(endDate)
select new
{
AttributeCode = attributeType,
TimeSeriesData = fn(k.Key, k.Value.ToString())
});
la = annonyType.ConvertAll(x=>ConvertToAttribute(x.AttributeCode , x.TimeSeriesData ));
....
//End test method
}
//... (where TimeSeriesDataType = type returned by fn(
public Attributes ConvertToAttribute(string AttributeType, TimeSeriesDataType d){
return new Attribute()....;
}