RawQuery cannot compile: "Cannot figure out how to read this field from a cursor." - android-room

I've multiple #RawQuerys in my project with custom data classes as return type and this worked well so far, but now I'm trying to have a data class with a List<OtherResult> property and this fails at compile time with:
error: Cannot figure out how to read this field from a cursor.
So basically:
data class CustomResult(
val count: Int,
val name: String,
val values: List<SubResult>,
)
data class SubResult(
val sub_name: String,
val sub_code: String,
)
------
Dao:
#RawQuery
abstract fun getCustomResultRaw(query: SimpleSQLiteQuery): List<CustomResult>
fun getCustomResult(): List<CustomResult> {
val params = emptyArray<Any>()
val query = "SELECT ..... "
return getCustomResultRaw(SimpleSQLiteQuery(query, params))
}
Is there any way to force-tell room that the List<> property should be parsed as SubResult? What other options do I have?

Is there any way to force-tell room that the List<> property should be parsed as SubResult?
No not like that. Room stores data in SQLite tables, that consist of columns and only a limited set of types those being, from an SQLite perspective:-
NULL
INTEGER (64 bit signed integer e.g. Long, Int, Byte ...)
TEXT (character string e.g. String)
REAL (8-byte IEEE floating point number e.g. Double, Float ...)
BLOB (a stream of Bytes e.g. ByteArray)
NUMERIC (any of the above BUT not supported by ROOM)
A column cannot directly contain multiple values such as a list.
What other options do I have?
Basically two options:-
Store the SubResult values in another table with a relationship to the CustomResult, this would be the way from a relational database perspective.
CustomResult would be the parent, SubResult would be the child (children)
Store the SubResults as one of the allowable types (probably rules out INTEGER or REAL, rules out NULL), so you would need to Convert that data to either a ByteArray or a String. Typically the latter is used and the String is a JSON representation of the data. You then need to define TypeConverters to let ROOM know how to handle the conversion.
Working Example
The following is an example that uses both methods.
NOTE to cater for conversion of a list of SubResults the Google Gson library was added dependency implementation 'com.google.code.gson:gson:2.9.0' (no need for this if using the second table with related SubResults)
Here's the data classes:-
data class CustomResult(
val count: Int,
val name: String,
/*val values: List<SubResult>, uses SubResultList to cater for Type Converters*/
val values: SubResultList /* Not required if using table for SubResults */
)
data class SubResult(
val sub_name: String,
val sub_code: String,
)
/* Holder for List to suit conversion of SubResult List - Not needed if using table for SubResults*/
data class SubResultList(
val srList: List<SubResult>
)
/* Primary Table for the CR's (as well as the converted list of SubResults)*/
#Entity
data class CRTable(
#PrimaryKey
val crId: Long?=null, /* Uniquely Identifies the CR Row - generated if null */
#Embedded
val customResult: CustomResult
)
/* Second table for the SR's - not needed if using JSON representation of SR List*/
#Entity
data class SRTable(
#PrimaryKey
val srId: Long?=null,
#ColumnInfo(index = true)
val parentCrId: Long,
#Embedded
val subResult: SubResult
)
/* For retrieving the CRTables WITH the related SRTable rows - not needed if using JSON representation of SR List*/
data class CRTableWithRelatedSRTables(
#Embedded
val crTable: CRTable,
#Relation(entity = SRTable::class, parentColumn = "crId", entityColumn = "parentCrId")
val srTableList: List<SRTable>
)
The Converters (2) to convert a SubResultList to JSON and back:-
class Converters {
#TypeConverter
fun convertSubResultListToJSONString(subResultList: SubResultList): String = Gson().toJson(subResultList)
#TypeConverter
fun convertJSONStringToSubResultList(jsonString: String): SubResultList = Gson().fromJson(jsonString,SubResultList::class.java)
}
The DAO interface (functions to access the database) :-
#Dao
interface AllDao {
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(crTable: CRTable): Long
/* Not Needed if storing Subresults as an embedded list */
#Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(srTable: SRTable): Long
/* Query for embedded Subresults list */
#Query("SELECT * FROM crtable")
fun getAllCRTableRows(): List<CRTable>
/* Query for both embedded Subresults (as they are combned in this example) and the related SRTable rows */
#Transaction
#Query("SELECT * FROM crtable")
fun getAllCRTablesWithRelatedSRTables(): List<CRTableWithRelatedSRTables>
}
An #Database annotated class with a singleton for the instance. Note for brevity and convenience allows running on the main thread.
#TypeConverters(value = [Converters::class])
#Database(entities = [CRTable::class,SRTable::class], version = 1, exportSchema = false)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDao
companion object {
private var instance: TheDatabase? = null
fun getInstance(context: Context): TheDatabase {
if (instance==null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
.allowMainThreadQueries()
.build()
}
return instance as TheDatabase
}
}
}
if using just the two tables, then #TypConverters would not need to be defined.
if just embedding the SubResultsList then the SRTable class would not be included in the entities parameter
Finally putting it all together in an activity:-
const val TAG = "DBINFO"
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDao
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
/* Preapre some SubResults */
val sr01 = SubResult("SR01","CODEA")
val sr02 = SubResult("SR02","CODEB")
val sr03 = SubResult("SR03","CODEC")
val sr04 = SubResult("SR04","CODED")
val sr05 = SubResult("SR05","CODEE")
/* Prepare some SubResultLists */
val srl1 = SubResultList(listOf(sr01,sr02,sr03))
val srl2 = SubResultList(listOf(sr04,sr05))
val srl3 = SubResultList(listOf(sr01,sr02,sr03,sr04,sr05))
/* Add CustomResults for embedded SubresultList (i.e. converted to JSON)*/
val cr01 = dao.insert(CRTable(customResult = CustomResult(1,"CR01",srl1)))
val cr02 = dao.insert(CRTable(customResult = CustomResult(2,"CR02",srl2)))
val cr03 = dao.insert(CRTable(customResult = CustomResult(3,"CR03",srl3)))
/* Add the related SRTable rows (each block is per parent CustomResult) */
dao.insert(SRTable(null,cr01,sr01))
dao.insert(SRTable(null,cr01,sr02))
dao.insert(SRTable(null,cr01,sr03))
dao.insert(SRTable(null,cr02,sr04))
dao.insert(SRTable(null,cr02,sr05))
dao.insert(SRTable(null,cr03,sr01))
dao.insert(SRTable(null,cr03,sr02))
dao.insert(SRTable(null,cr03,sr03))
dao.insert(SRTable(null,cr03,sr04))
dao.insert(SRTable(null,cr03,sr05))
/* Extract and inspect the data (only need the more complex query as CRTable has the embedded SubResultsList) */
for (crwsr in dao.getAllCRTablesWithRelatedSRTables()) {
/* For each CRTable row */
Log.d(TAG,"CRTable is ${crwsr.crTable.customResult.name} count is ${crwsr.crTable.customResult.count} it has ${crwsr.crTable.customResult.values.srList.size} SR's, it also has ${crwsr.srTableList.size} related SRTable rows.")
Log.d(TAG,"SR's are:-")
/* For each item in the SubResultList (for the current CustomResult) */
for (sr in crwsr.crTable.customResult.values.srList) {
Log.d(TAG,"\tSR is ${sr.sub_name} code is ${sr.sub_code}")
}
Log.d(TAG,"Related SR's are:-")
/* For each related row in the SRTable (for the current CustomResult) */
for (srt in crwsr.srTableList) {
Log.d(TAG,"\tSR is ${srt.subResult.sub_name} code is ${srt.subResult.sub_code} ID is ${srt.srId} related to (child of) CR with an ID of ${srt.parentCrId}")
}
}
}
}
Results
The log includes:-
2022-06-10 05:54:48.982 D/DBINFO: CRTable is CR01 count is 1 it has 3 SR's, it also has 3 related SRTable rows.
2022-06-10 05:54:48.982 D/DBINFO: SR's are:-
2022-06-10 05:54:48.982 D/DBINFO: SR is SR01 code is CODEA
2022-06-10 05:54:48.982 D/DBINFO: SR is SR02 code is CODEB
2022-06-10 05:54:48.982 D/DBINFO: SR is SR03 code is CODEC
2022-06-10 05:54:48.982 D/DBINFO: Related SR's are:-
2022-06-10 05:54:48.982 D/DBINFO: SR is SR01 code is CODEA ID is 1 related to (child of) CR with an ID of 1
2022-06-10 05:54:48.982 D/DBINFO: SR is SR02 code is CODEB ID is 2 related to (child of) CR with an ID of 1
2022-06-10 05:54:48.982 D/DBINFO: SR is SR03 code is CODEC ID is 3 related to (child of) CR with an ID of 1
2022-06-10 05:54:48.983 D/DBINFO: CRTable is CR02 count is 2 it has 2 SR's, it also has 2 related SRTable rows.
2022-06-10 05:54:48.983 D/DBINFO: SR's are:-
2022-06-10 05:54:48.983 D/DBINFO: SR is SR04 code is CODED
2022-06-10 05:54:48.983 D/DBINFO: SR is SR05 code is CODEE
2022-06-10 05:54:48.983 D/DBINFO: Related SR's are:-
2022-06-10 05:54:48.983 D/DBINFO: SR is SR04 code is CODED ID is 4 related to (child of) CR with an ID of 2
2022-06-10 05:54:48.983 D/DBINFO: SR is SR05 code is CODEE ID is 5 related to (child of) CR with an ID of 2
2022-06-10 05:54:48.983 D/DBINFO: CRTable is CR03 count is 3 it has 5 SR's, it also has 5 related SRTable rows.
2022-06-10 05:54:48.983 D/DBINFO: SR's are:-
2022-06-10 05:54:48.983 D/DBINFO: SR is SR01 code is CODEA
2022-06-10 05:54:48.983 D/DBINFO: SR is SR02 code is CODEB
2022-06-10 05:54:48.983 D/DBINFO: SR is SR03 code is CODEC
2022-06-10 05:54:48.984 D/DBINFO: SR is SR04 code is CODED
2022-06-10 05:54:48.984 D/DBINFO: SR is SR05 code is CODEE
2022-06-10 05:54:48.984 D/DBINFO: Related SR's are:-
2022-06-10 05:54:48.984 D/DBINFO: SR is SR01 code is CODEA ID is 6 related to (child of) CR with an ID of 3
2022-06-10 05:54:48.984 D/DBINFO: SR is SR02 code is CODEB ID is 7 related to (child of) CR with an ID of 3
2022-06-10 05:54:48.984 D/DBINFO: SR is SR03 code is CODEC ID is 8 related to (child of) CR with an ID of 3
2022-06-10 05:54:48.984 D/DBINFO: SR is SR04 code is CODED ID is 9 related to (child of) CR with an ID of 3
2022-06-10 05:54:48.984 D/DBINFO: SR is SR05 code is CODEE ID is 10 related to (child of) CR with an ID of 3
i.e. the expected result (3 SRs for the first CR, 2 SRs for the second CR and 5 SRs for the third CR for BOTH methods)
The actual data stored:-
CRTable has 3 rows :-
As can be seen the values has the List of SubResults (as a SubResultsList). The disadvantage would be querying the data, say if you wanted just the CRs that had a specific SR code you can't just say WHERE values = 'CODEE', you might get away with WHERE values LIKE '%CODEE%' (but that's inefficient as it requires a Full Table Scan). Also as can be seen there is a lot of BLOAT stored due to the JSON syntax and naming.
SRTable has a row per related SR so 10 rows:-
far less data is stored
searches would like be via index (SQLite Query Optimizer will do it's business)
the SELECT SQL can be more complicated (especially when Room's conveniences aren't suitable e.g. the query in the example uses subqueries built by Room to get the related SR's rows and that is for ALL related (hence the #Transaction).)

I couldn't solve my problem, but found a workaround using multimap as described in the documentation, ie. in this case returning a Map<CustomResult, List<SubResult>>:
The main class holding the List<> goes like:
data class CustomResult(
val count: Int,
val name: String,
) {
#Ignore
var values: List<SubResult> = emptyList() // note: `var` instead of `val`
}
The Dao functions then go like this:
// now returning a Map<>
#RawQuery
abstract fun getCustomResultRaw(query: SimpleSQLiteQuery): Map<CustomResult, List<SubResult>>
// maps the Map<> to a List<>
fun getCustomResult(): List<CustomResult> {
val params = emptyArray<Any>()
val query = "SELECT ..... "
val resultMap = getCustomResultRaw(SimpleSQLiteQuery(query, params))
return resultMap.map { e ->
CustomResult(
count = e.key.count,
name = e.key.name,
).apply {
values = e.value
}
}
}

Related

How to update room database and How to get the insert status is working or completed?

The first:
I've got dataList from retrofit And insert Room Database.
I want to change dataList(Like insert a element). My Room Database can work because I used OnConflictStrategy.REPLACE. but when I delete dataList some elements, My Room Database can not delete elements.
Dao:
#Insert (onConflict = OnConflictStrategy.REPLACE)
suspend fun insertData(dataList : List<Data>)
Entity:
#Entity
data class Data(
#PrimaryKey val Id : Long,
val Fl : String,
val FlMc : String,
val Dm : String,
val Mc : String,)
ViewModel:
fun insertData(dataList: List<Data>) = viewModelScope.launch {
dataRepository.insertData(dataList)
}
//get data from server
fun getData():LiveData<List<Data>>
Activity:
dataViewModel.getData().observer(this){
dataViewModel.insertData(it)
}
How to resolve this situation except DELETE ALL THEN INSERT
The second:
I want to use a progressbar to indicate that I am inserting dataList
How to get the insert status is working or completed
If I understand correctly, you issue is that you cannot delete because you are building a DataList item but don't know the primary key value as it's generated.
As you haven't shown the DataList entity then assuming it is like:-
#Entity
data class DataList(
#PrimaryKey(autogenerate = true)
val id: Long,
val othercolumns: String
....
)
and if you change from suspend fun insertData(dataList : List<Data>) to suspend fun insertData(dataList : List<Data>): List<Long> (i.e. added the List as the result)
Then you have the values of the id column in the result. In the case above the value is the value of the id column.
If the #PrimaryKey is not an integer type e.g. a String then the long returned WILL NOT be the value of the primary key. It will be a special value known as the rowid.
In short using an integer with primary key makes the column an alias of the rowid. if not an integer primary key then it is not an alias BUT the rowid still exists.
You can still use the rowid to access a specific row as the rowid MUST be a unique value. e.g. (again assuming the above) you could have an #Query such as
#Query("SELECT * FROM the_datalist_table WHERE rowid=:rowid")
suspend fun getDataListById(rowid: Long)
Only of use if you know the rowid though.
You could get rowid's say by using
#Query("SELECT rowid FROM the_datalist_table WHERE othercolumns LIKE :something")
suspend fun getRowidOfSomeDataLists(something: String): List<Long>
still not of great use as the selection criteria would also be able to provide a list of Datalists.
Additional re the comment:-
How to use in viewModel or Activity?
As an example you could do something like :-
fun insertData(dataList: List<Data>) = viewModelScope.launch {
val insertedDataList: ArrayList<Data> = ArrayList()
val insertedIdList = dataRepository.insertData(dataList)
val notInsertedDataList: ArrayList<Data> = ArrayList()
for(i in 0..insertedIdList.size) {
if (insertedIdList[i] > 0) {
insertedDataList.add(
Data(
insertedIdList[i], //<<<<< sets the id as per the returned list of id's
dataList[i].Fl,
dataList[i].FlMc,
dataList[i].Dm,
dataList[i].Mc)
)
} else {
notInsertedDataList.add(
Data(
insertedIdList[i], //<<<<< sets the id as per the returned list of id's WILL BE -1 as not inserted
dataList[i].Fl,
dataList[i].FlMc,
dataList[i].Dm,
dataList[i].Mc
)
)
}
}
val notInsertedCount = notInsertedDataList.size
val insertedCount = insertedDataList.size
}
So you have :-
insertedDataList an ArrayList of the successfully inserted Data's (id was not -1) with the id set accordingly.
notInsertedDataList an ArrayList of the Data's that were not inserted (id was -1) id will be set to -1.
insertedCount an Int with the number inserted successfully.
notInsertedCount and Int with the number not inserted correctly.
DELETE ALL
To delete all rows, unless you extract all rows you can't use the convenience #Delete, as this works on being provided the Object (Data) and selecting the row to delete according to the primary key (id column).
The convenience methods #Delete, #Update, #Insert are written to generate the underlying SQL statement(s) bases upon the object (Entity) passed.
e.g. #Delete(data: Data) would generate the SQL DELETE FROM data WHERE id=?, where ? would be the value of the id field when actually run.
The simpler way to delete all columns is to use the #Query annotation (which handles SQL statements other than SELECT statements). So you could have.
#Query("DELETE FROM data")
fun deleteAllData()
note that this does not the return the number of rows that have been deleted.

Why could the F# LINQ expression not be translated - at least partially?

Issue
I’m using an existing code first approach in order to acquire data from an existing database. This project is encapsulated in a .NET C# project and contains the model as well as the configuration descriptions.
My aim is to use the existing database context implementation for a F# project. I have been testing many times the database access with a XUnit test project which is also written in F#.
My problem is an unexpected behaviour, when I try to select the last name and the person’s id.
In order to explain my implementation and illustrate the issue I implemented four test functions of which only the first two are executed successfully. The last two test functions fail.
The following error appears
System.InvalidOperationException : The LINQ expression 'LastName' could not
be translated. Either rewrite the query in a form that can be translated, or
switch to client evaluation explicitly by inserting a call to 'AsEnumerable',
'AsAsyncEnumerable', 'ToList', or 'ToListAsync'*
I do not understand the error because I am not actually using any client specific implementation for the query evaluation. So normally the LINQ-Provider should convert it into a database appropriate SQL definition. For the tests I am using the Entity Framework memory database instance. On top of that, please note that the error also exists on the real database.
Another issue that I do not understand is why the second test works while the third one fails. I changed only the last name select with the id select.
However, I also added a F# query expression since this is actually recommended from the documentation, but with no success.
Is the main problem the usage of the Entity Framework context? If so, how can I then reuse the implementation of the EF database context?
Test and evaluation with LINQPad 6
I tested the behaviour with LINQPad in order to make the use case more simple. Therefore, I used the DemoDB which should be available for everyone.
Apart from that I’m trying to make it reproduceable for a larger community. Unfortunately, the outcome of my test is the same. So, I created a simple database query and changed the order of the named selections. If I change the alphabetical order of the column names, the error appears. Therefore, why is the alphabetical order important in order to have a valid select statement?
I found another closed issue on stackoverflow which describes the usage of anonymous records but the different order is not treated (F# Query Expression / select operator / changing column headings in result).
// successful query
query {
for c in this.Categories do
select {| A = c.CategoryID; B = c.CategoryName; |}
}
// failed query
query {
for c in this.Categories do
select {| B = c.CategoryID; A = c.CategoryName; |}
}
The argument 'value' was the wrong type. Expected
'System.Func`2[System.Int32,<>f__AnonymousType1383943985`2[System.String,System.Int32]]'.
Actual '<>f__AnonymousType1383943985`2[System.String,System.Int32]'.
Test and evaluation with a F# unit test project
Test result summary
I tested the behaviour with .NET 3.1 and .NET 5.0 (projects as well as LINQPad 6). Furthermore, all dependencies have been adjusted accordingly (e.g. Entity Framework 5.0 or 3.1).
Test
Result
A anonymous record
successful
B anonymous record
successful
C anonymous record
failed
D anonymous record
failed
E partial person type
failed
F partial person type
successful
G partial person type
successful
H partial person type
failed
I partial person type
failed
Test outcome
System.InvalidOperationException : The LINQ expression 'LastName' could not be translated. Either rewrite the query in a form that can be translated, or switch
to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'
Entity Framework Core "5.0.3" initialized
'"TestContext"' using provider '"Microsoft.EntityFrameworkCore.InMemory"'
EF core code first database .NET 5 project
public class Person
{
public int Id { get; set; }
public string LastName { get; set; }
}
...
public void Configure(EntityTypeBuilder<Person> builder)
{
builder.ToTable("PERSON");
builder.HasKey(x => x.Id);
builder.Property(x => x.Id)
.HasColumnName("ID")
.ValueGeneratedNever();
builder.Property(x => x.LastName)
.HasColumnName("LASTNAME")
.HasMaxLength(512)
.IsUnicode(false);
}
...
public class TestContext : DbContext
{
public DbSet<Person> Persons { get; private set; }
public TestContext(DbContextOptions<TestContext> options) : base(options)
{}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new PersonConfig());
}
}
F# xunit test project in order to evaluate the EF core database context access
type PartialPerson = { LastName: string; ID : int; }
type ``success database execution queries`` (output: ITestOutputHelper) =
let sLogger =
LoggerConfiguration()
.MinimumLevel.Verbose()
.MinimumLevel.Override("Microsoft", LogEventLevel.Information)
.MinimumLevel.Override("Microsoft.EntityFrameworkCore", LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.TestOutput(output, Events.LogEventLevel.Verbose)
.WriteTo.Debug()
.CreateLogger()
let loggerFactory =
(new LoggerFactory())
.AddSerilog(sLogger)
let options = DbContextOptionsBuilder<TestContext>()
.EnableSensitiveDataLogging(true)
.UseLoggerFactory(loggerFactory)
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options;
let context = new TestContext(options)
[<Fact>]
let ``success person select lastname Test A`` () =
let rs =
context.Persons.Select(
fun person -> {| Name = person.LastName |} )
rs |> should be Empty // successful
[<Fact>]
let ``success person select id and lastname Test B`` () =
let rs =
context.Persons.Select(
fun person ->
{| ID = person.Id
LastName = person.LastName |})
rs |> should be Empty // successful
[<Fact>]
let ``success person select id and lastname Test C`` () =
let rs =
context.Persons.Select(
fun person ->
{| LastName = person.LastName
ID = person.Id |} )
rs |> should be Empty // failed
[<Fact>]
let ``success person select id and lastname Test D`` () =
let rs =
query {
for person in context.Persons do
select
{| LastName = person.LastName
ID = person.Id |}
}
rs |> should be Empty // failed
// avoid anonymous record and use the partial person type
// type PartialPerson = { LastName: string; ID : int; }
[<Fact>]
let ``success partial person select id and lastname Test E`` () =
let rs =
context.Persons.Select(
fun person ->
{ ID = person.Id
LastName = person.LastName })
rs |> should be Empty // failed
[<Fact>]
let ``success partial person select id and lastname Test F`` () =
let rs =
context.Persons.Select(
fun person ->
{ LastName = person.LastName
ID = person.Id } )
rs |> should be Empty // successful
[<Fact>]
let ``success partial person select id and lastname Test G`` () =
let rs =
query {
for person in context.Persons do
select
{ LastName = person.LastName
ID = person.Id }
}
rs |> should be Empty // successful
[<Fact>]
let ``success partial person select id and lastname Test H`` () =
let rs =
query {
for person in context.Persons do
select
{ ID = person.Id
LastName = person.LastName }
}
rs |> should be Empty // failed
[<Fact>]
let ``success partial person select id and lastname Test I`` () =
let rs =
query {
for person in context.Persons do
select
{ ID = person.Id
LastName = person.LastName }
}
rs.ToList() |> should be Empty // failed
Current findings
It seems that this issue is related to the issues 1226 and 3782. Both issues describe some problems with the order of named selections.
The dapper issue 1226 had a similar problem with the order of anonymous records for the query definition. However, thanks to Isaac Abraham (isaacabraham) who is using the CLIMutable decoration, I thought of turning off the ordering restrictions. So basically the idea was to try it for my tests since the query generation through the LINQ provider could have a positive effect. Unfortunately this was without success, maybe because of the implementation of the LINQ provider because the generation process is implemented with F# and that is the reason why the CLIMutable attribute does not affect it.
After continuing my search, I found another issue 3782 which indicates my problem. The issue has the main focus on the usage of tuples for the data selection but also the issue with records. So, I added another issue description 11131 in order to help with my current findings. Finally, I will keep track the outcome and add it to this issue.
Does this still need answering?
As you already found out, F# anonymous types order fields by name (not by source code order of declaration, as C# anonymous types would do).
When writing {| B = c.CategoryID; A = c.CategoryName; |} in a LINQ query, this will not actually pass an anonymous type, rather the compiler creates an System.Linq.Expressions.Expression that describes how to construct the anonymous type, and later on the underlying framework implementing LINQ (e.g., the Entity Framework) will try to parse that expression (and create e.g. SQL code from it).
Problem here is, c.CategoryID and c.CategoryName may have side effects, hence the compiler will evaluate them in the order specified in the source code (first ID, then Name), but assign them in the order of the anonymous type.
Long story short, the generated System.Linq.Expressions.Expression first will evaluate c.CategoryID, assign the value to a temporary variable, evaluate c.CategoryName, assign that to the anonymous type's first field, and finally assign the temporary variable to the anonymous type's second field. And the EF translator later on does not know how to handle the temporary variable (e.g., how to translate that to SQL.)
(In C#, no field reordering happens, so no temporary variables are required to mask side effects, so the expression parser will not face that problem.) (The F# anonymous type at present is not fully fit for LINQ.)

DTO Projection of Query with #ElementCollection causes 'Unable to locate appropriate constructor on class' error

I'm writing custom query using projection to reduce amount of queries in one session, when couple of field from antity are needed and using Fetch join.
Unfortunately got stuck into a problem when one types in returned dto is a collection.
I have following class with #ElementCollection (siplified version for this purpose):
#Entity
class MyClass(
val someString: String,
#ElementCollection
#Enumerated(EnumType.STRING)
var enums: MutableSet<Enum>,
#ManyToOne
var condition: Condition
//And so on...
)
And DTO used for projection:
data class ProjectionDTO(
val aString: String,
val enumList: List<Enum>
)
But when using query:
fun query(condition: Condition): List<ProjectionDTO> =
entityManager.createQuery(
"""SELECT NEW com.xxx.ProjectionDTO( m.someString, e ) FROM MyClass m
INNER JOIN FETCH m.enums e
WHERE m.condition = :condition""", ProjectionDTO::class.java)
.setParameter("condition", condition)
.resultList
}
I get following exception:
Exception:[org.hibernate.hql.internal.ast.QuerySyntaxException: Unable to locate appropriate constructor on class [com.xxx.ProjectionDTO]. Expected arguments are: com.xxx.String, com.xxx.Enum [SELECT NEW com.xxx.ProjectionDTO( m.someString, e ) FROM MyClass m
INNER JOIN FETCH m.enums e
WHERE m.condition = :condition]]
Already tried different types of collection, additional constructors and calling field of given #ElementCollection like e.enum in query params.
Is it possible to return a list (or other collection) from this kind of query? If so, how do I tackle it?
It is not allowed to use collection path expressions in constructor query. link
Put just root entity into constructor:
SELECT NEW com.xxx.ProjectionDTO(m) WHERE m.condition = :condition
In constructor assign m.enums, m.someString to fields.

linq query with many-to-many relationship

Let’s assume I have three tables in my database:
Authors(PK Id, AuthorName)
Books(PK Id, BookName)
Authors_Books(FK BookId, FK AuthorId)
I need to write a method which passes in a parameter int[] authorsIds and returns all books (Id, BookName, list of authors of this book) if it was written by any author whose Id contains in int[] authorsIds parameter.
I need LINQ query (query method).
I really need a help. Appreciate your help very much.
var int[] authors = { 1, 2, 3 };
var books = from book in books
where book.Authors.Any(x => authors.Contains(x.AuthorId))
select book;
PS: I assume book.Authors is your reference to Authors_Books table (or maybe has a different name).
this answer is based on the exact structure without considering that A obj has B in its structure
var book = from b in books
where book.Id in ( from ab in AuthersBooks
where selectedAuthers.contains(ab.AutherId)
select ab.bookId ) //now we have books that are authered by one of the selected authers
select new { BookId = b.Id, // now we create an anonymos object to contain any info we need
BookName = b.Name,
BookAuthers = (from a in authers
join ab in AuthersBooks
on a.Id == ab.AuthersId
where ab.bookid == b.Id
select a)
this will propably have syntax errors and after correcting you may get an error about multiple threads which already has an answer here

How would I count these records using Linq to NHibernate (3.1)

I have an entity defined like so:
public class TestEntity : EntityBase
{
public string Source {get;set;}
public bool Suppressed {get;set;}
/* other stuff */
}
I want to show an HTML table that looks like:
Source Suppressed Not Suppressed
-------------------------------------------------
Source1 30 1225
Soure 7 573
My first attempt to query this was:
from e in _session.Query<TestEntity>()
group e by e.Source into g1
select new
{
Source = g1.Key,
Suppressed = g1.Sum(x=>x.Suppressed ? 1 : 0),
NotSuppressed = g1.Sum(x=>x.Suppressed ? 0 : 1),
}
But of course, Linq choked on the ternary expression when converting it to SQL. Any alternative ways to do this?
Edit: I tried Dmitry's suggestion, and it returns the same counts for both. The SQL generated by his suggestion is:
select
customer0_.SourceA as col_0_0_,
cast(count(*) as INT) as col_1_0_,
cast(count(*) as INT) as col_2_0_
from
dbo.Customers customer0_
group by
customer0_.SourceA
Which obviously isn't what I want...
What about doing something like g1.Count(x => x.Suppressed == true)?

Resources