Database schema:
Staff has multiple Asset. 1 Asset has 1 AssetModel
I write up a script by Linq that return IQueryable
var q = from staff in this.unitOfWork.StaffRepository.Data
from asset in this.unitOfWork.AssetRepository.Data
.Where(asset => staff.Id == asset.StaffId).DefaultIfEmpty()
from assetModel in this.unitOfWork.AssetModelRepository.Data
.Where(assetModel => asset.AssetModelId == assetModel.Id).DefaultIfEmpty()
group new { staff, assetModel }
by new
{
staff.Id
,staff.Name
,AssetModelType = assetModel.Type
} into g
select new StaffQuery
{
Id = g.Key.Id,
Name = g.Key.Name
AssetStatistic = (g.Key.AssetModelType == null ? AssetType.None.ToString()
: g.Key.AssetModelType.ToString()) + g.Count()
};
I'm using OData to easy filter, order, select, paging Data Result.
The below is what I got from OData
{
"#odata.context":"http://localhost:52052/odata/$metadata#staffs",
"value":[
{
"Id":1,
"Name":"John",
"AssetStatistic":"CaseOrLap2"
},
{
"Id":1,
"Name":"John"
"AssetStatistic":"Monitor1"
}
]
}
Is it possible to group concat by AssetModel.Type like the below result
{
"#odata.context":"http://localhost:52052/odata/$metadata#staffs",
"value":[
{
"Id":1,
"Name":"John",
"AssetStatistic":"CaseOrLap2, Monitor1"
}
]
}
It must be return IQueryable and string.Join in this context is invalid because Linq to Entities cannot resolved it.
Related
I have a dynamic query that brings me dynamic columns (System.Linq.Dynamic.Core):
var data = data.Select("new (col1, col2, col3)", "T", StringComparison.OrdinalIgnoreCase);
[
{ col1: "This", col2: 1.56, col3: "Something else" }
]
Now I need to bring the type of each column. ex:
[
{
col1: { value: "This", type: "string" },
col2: { value: 1.56, type: "decimal" }
}
]
To get that, I was thinking of something like:
var rows = data.Select(x => new {
Type = MyHelper.GetType(x),
Value = x
});
But that it's not working.
An unhandled error occurred The best overloaded method match for
'MyHelper.GetType(System.Reflection.PropertyInfo)' has some invalid
arguments
I do know how to get the type
public static string GetType(PropertyInfo type)
{
return type.PropertyType.IsGenericType && type.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>) ? type.PropertyType.GetGenericArguments()[0].ToString().Replace("System.", "") : type.PropertyType.Name;
}
But I don't know how to build the query.
[UPDATE]
This is a try, but not what I need
var rows = data.Select(x => new {
Type = MyHelper.GetType(x.GetType()),
Value = x
});
[
{
"type": "<>f__AnonymousType0`3",
"value": {
"col1": "2019-10-15T00:00:00",
"col2": 0.00,
"col3": "abc"
}
},
]
Here is a working demo you could refer to:
public List<object> Get(int id)
{
var data = new List<Object>
{
new{
col1="aaa",
col2= 1.25,
col3 = 1
},
new{
col2="asd",
col3= 1.2,
col4 =2
}
};
var aaa = new List<object>() { };
foreach (var item in data)
{
var ds = item.GetType().GetProperties();
foreach(var i in ds)
{
var name = i.Name;
var type = i.PropertyType.Name;
var value = item.GetType().GetProperty(name).GetValue(item);
aaa.Add(new { name=name, type = type, value = value });
}
}
return aaa;
}
I have the following api controller that returns a json representation of the query you see here:
public async Task<ActionResult<IEnumerable<CarDto>>> GetCarData(Guid carID)
{
var carData = await (from cl in _context.CarList
join tl in _context.transmissionList
on cl.CId equals tl.CId
join to in _context.transmissionOptions
on tl.TId equals to.TId
where cl.CId == carID
select new CarDto
{
CarId = cl.CarId,
TransmissionId = tl.TId,
OptionId = to.OptionId,
GearId = to.GearId
})
.ToListAsync();
return carData;
}
The returned json data looks like this:
[
{
"carId": "351a",
"transmissionId": "ec7",
"optionId": "a1",
"gearId": "674532a"
},
{
"carId": "351a",
"transmissionId": "ec7",
"optionId": "b7",
"gearId": "5f9173f"
},
{
"carId": "351a",
"transmissionId": "ec7",
"optionId": "c5",
"gearId": "cf807"
}
]
However, I'd like for it to be formatted such that there is a property called transmissionChoices that contains an array of the possible options.
Like this:
{
"carId": "351a",
"transmissionId": "ec7",
"transmissionChoices": [
{
"optionId": "a1",
"gearId": "674532a"
},
{
"optionId": "b7",
"gearId": "5f9173f"
},
{
"optionId": "c5",
"gearId": "cf807"
}
]
}
Is there a way to get the controller to format it like that?
You can use the LINQ GroupBy method and then project the grouped results into the shape you want.
public async Task<ActionResult<IEnumerable<object>>> GetCarData(Guid carID)
{
var carData = await (from cl in _context.CarList
join tl in _context.transmissionList
on cl.CId equals tl.CId
join to in _context.transmissionOptions
on tl.TId equals to.TId
where cl.CId == carID
select new
{
CarId = cl.CarId,
TransmissionId = tl.TId,
OptionId = to.OptionId,
GearId = to.GearId
})
.GroupBy(x => x.CarId)
.Select(g => new
{
CarId = g.First().CarId,
TransmissionId = g.First().TransmissionId,
TransmissionChoices = g.Select(x => new
{
OptionId = x.OptionId,
GearId = x.GearId
})
})
.ToListAsync();
return carData;
}
Note that this is projecting the results into an anonymous type. Feel free to create a model that matches the schema you need and then use that model in the Select(...) projection.
Consider the following linq query
var result = from a in
from b in filledTable
join c in distinctList on b[0].SerialNumber equals c.Field("SERIAL NUMBER")
select new { b, c }
group a by new { a.b[0].SerialNumber } into d
select new
{
Id = d.Select(x => x.b[0].Id),
SerialNumber = d.Select(x => x.b[0].SerialNumber),
// This part is not producing the correct output.
ImportTable = d.Select(w => w.c.Table
.AsEnumerable()
.GroupBy(y => y.Field("SERIAL NUMBER"))
.Select(z => z.First())
.CopyToDataTable())
};
filledTable in my linq query is a List<dynamic> which is populated by what the values are returned from a sproc and distinctList is a List<DataRow> which I distinct the values coming from the DataTable as follows:
List<DataRow> distinctList = dt.AsEnumerable().Distinct(DataRowComparer.Default).ToList();
My Linq query produces the following JSON
[
{
"FilledTableList":[
[
{
"Id":[
2
],
"SerialNumber":[
"1073410"
],
"ImportTable":[
[
{
"SERIAL NUMBER":"1073410",
"PRODUCT TYPE":"Product A"
},
{
"SERIAL NUMBER":"1073411",
"PRODUCT TYPE":"Product B"
}
]
]
},
{
"Id":[
-1
],
"SerialNumber":[
"1073411"
],
"ImportTable":[
[
{
"SERIAL NUMBER":"1073410",
"PRODUCT TYPE":"Proeduct A"
},
{
"SERIAL NUMBER":"1073411",
"PRODUCT TYPE":"Product B"
}
]
]
}
]
]
}]
But I would like the following JSON output
[
{
"FilledTableList":[
[
{
"Id":[
2
],
"SerialNumber":[
"1073410"
],
"ImportTable":[
[
{
"SERIAL NUMBER":"1073410",
"PRODUCT TYPE":"Product A"
}
]
]
},
{
"Id":[
-1
],
"SerialNumber":[
"1073411"
],
"ImporTable":[
[
{
"SERIAL NUMBER":"1073411",
"PRODUCT TYPE":"Product B"
}
]
]
}
]
]
}]
So the ImportTable node only contains the information matching to the serial number in the above FilleTabledList node. Everything else seems to work as expected by the Linq query apart from this. Can someone tell me where I'm going wrong please
Update:
My filledTable contains two items as follows:
{ Id = 2, SerialNumber = "1073410"}
{ Id = -1, SerialNumber = "1073411"}
Eventually I will have more items in the list but just to figure out why more linq query isn't working I have narrowed it down to just to items
I created a fiddle, which makes it easier to communicate the available data and the expected results.
When I understood it correctly you like to get a list of all products, listed in the filledTable and then find all elements with the same serial number from the dataTable.
If this is correct, than the LINQ query has to be:
var result = filledTable.GroupJoin(distinctList, product => product.SerialNumber, row => row.Field<string>("SERIAL NUMBER"), (Product, Rows) => new { Product, Rows })
.Select(group => new
{
Id = group.Product.Id,
SerialNumber = group.Product.SerialNumber,
ImportTable = group.Rows.CopyToDataTable()
});
and the result will be
[
{
"Id": 2,
"SerialNumber": "1073410",
"ImportTable": [
{
"SERIAL NUMBER": "1073410",
"PRODUCT TYPE": "Product A"
}
]
},
{
"Id": -1,
"SerialNumber": "1073411",
"ImportTable": [
{
"SERIAL NUMBER": "1073411",
"PRODUCT TYPE": "Product B"
}
]
}
]
I am not really sure, but would something like this work?
var result = (from a in (from b in filledTable join c in distinctList on b[0].SerialNumber equals c.Field<string>("SERIAL NUMBER") select new { b, c })
group a by new { a.b[0].SerialNumber } into d
select new
{
Id = d.Select(x => x.b[0].Id),
SerialNumber = d.Select(x => x.b[0].SerialNumber),
ImportTable = d.Select(w => w.c.Table.AsEnumerable()
.Where(y=>y.Field<string>("SERIAL NUMBER") == d.Key.ToString())
.GroupBy(y => y.Field<string>("SERIAL NUMBER")).Select(z => z.First()).CopyToDataTable())
});
Here is a simplified query that can be used:
var result =
from entry in filledTable
join row in distinctList on entry[0].SerialNumber equals row.Field<string>("SERIAL NUMBER")
group new { entry, row } by entry[0].SerialNumber into items
select new
{
Id = items.Select(x => x.entry[0].Id),
SerialNumber = new[] { items.Key }.AsEnumerable(),
ImportTable = items.Select(x => x.row).CopyToDataTable()
};
It should be equivalent to the desired output and deal with most strange data combinations that are handled by the original query.
I'm trying to combine multiple queries into a single boolean query but the query generated is empty for the queries that are raw.
This is my method call
ConvertQueryToESD("{\"term\":\"fieldname\":\"value\"}"
,null
,a range filter)
.From(0)
.Take(50)
.SortMulti(a sort)
This is the method
internal SearchDescriptor<T> ConvertQueryToESD<T>(string queryString, string filterId, List<QueryContainer> filters) where T : class
{
var filterQueryString = GetFilter(filterId);
if (!string.IsNullOrWhiteSpace(filterQueryString))
{
var qd = new QueryDescriptor<T>();
filters.Add(qd.Raw(filterQueryString));
}
if (!string.IsNullOrWhiteSpace(queryString))
{
var qd = new QueryDescriptor<T>();
filters.Add(qd.Raw(queryString));
}
var sd = new SearchDescriptor<T>();
sd.Query(q => q.Bool(b => b.Must(filters.ToArray())));
return sd;
}
My resulting query is
{
"from": 0,
"size": 50,
"sort": [
{
"startTimeLocal": {
"order": "desc"
}
}
],
"query": {
"bool": {
"must": [
{
"range": {
"startTimeLocal": {
"gte": "0001-01-01T00:00:00",
"lte": "2015-12-22T16:28:22"
}
}
},
{}
]
}
}
}
The {} is where I would expect to see the raw term query. Why is the raw query not being serialized in to the SearchDescriptor? I have inspected the SearchDescriptor and the field is populated before the search is ran. I have also tested only adding the raw string term query without the range filter and the search descriptor serializes correctly.
Addendum
private string GetFilter(string filterId)
{
if (string.IsNullOrWhiteSpace(filterId))
{
return null;
}
var response = _elasticClient.Get<FilterMapping>(filterId);
ThrowIfNotSuccessfull(response.ConnectionStatus);
return response.Source?.QueryString;
}
I am trying to add a new record to the var "issue". I get a list of XXX from a SQL server DB and return in as follows for a jTable grid:
public dynamic XXXList(int CCC)
{
try
{
var issue = db.XXX.ToList().
Select(c => new { DisplayText = c.AAA, Value = c.BBB, c.CCC}).
Where(h => h.HHH == JJJ);
return (new { Result = "OK", Options = issue });
}
catch (Exception ex)
{
return (new { Result = "ERROR", Message = ex.Message });
}
}
The function returns:
{
"$id": "1",
"Result": "OK",
"Options": [
{
"$id": "2",
"DisplayText": "Food and Beverages",
"Value": 4,
"CCC": 4
},
{
"$id": "3",
"DisplayText": "Wrong software versions",
"Value": 5,
"CCC": 4
}
]
}
How can I add another record to the issue var before returning it?
Example:
{
"DisplayText": "new display text",
"Value": 5,
"CCC": 4
}
EDIT:
Here is my function after applying the answers:
public dynamic XXXList(int CCC)
{
try
{
var newRecord = new[] { new { DisplayText = "None", Value = -1, CCC = -1} };
var issue = db.ProjectXXXs.Where(h => h.CCC == JJJ).Select(c => new { DisplayText = c.AAA, Value = c.BBB, c.CCC }).ToList().Concat(newRecord);
return (new { Result = "OK", Options = issue });
}
catch (Exception ex)
{
return (new { Result = "ERROR", Message = ex.Message });
}
}
Thank you very much for all the help.
Concatenate any additional items:
var additional = new[] {new { DisplayText = ..., Value = ..., CCC = ... },
new { DisplayText = ..., Value = ..., CCC = ... }};
var issue = db.XXX.
Where(h => h.HHH == JJJ).
Select(c => new { DisplayText = c.AAA, Value = c.BBB, c.CCC}).
ToList().
Concat(additional);
(EDIT: Credit for the reordering of Where, Select and ToList goes to James Curran.)
Well first thing you want to do is move the ToList() to the end. With the ToList where it was, you'd read every column from every record from the Database, build a list from that, and then search it. With the ToList at the end, you send a query for just those columns of that record to the database, and then build a list from what comes back. You'll also need to move the Where before the Select, so it applies to the XXX records and not the output from the select.
var issue = db.XXX
.Where(h => h.HHH == JJJ)
.Select(c => new { DisplayText = c.AAA, Value = c.BBB, c.CCC})
.ToList();
From here the Add or Concat options suggested by other should work, however, you may have to use a named class instead of an anonomous one.
class OptionValues
{
public string DisplayText {get; set;}
public int Value {get; set;}
public int CCC {get; set;}
}
// :
// :
.Select(c => new OptionValues {DisplayText = c.AAA, Value = c.BBB, CCC= c.CCC})
I'm not sure, but can't you just write a line under var issue:
issue.Add(new { });
with the values that you want of course.