One to many database to json - c#

I got stuck when I retrieve data from database that is one to many relationship and I get two rows like this
ID NAME CHOICE_ID CHOICE_NAME CHOICE_VALUE
1 1 1 1 true
1 1 1 2 false
How can I make a json like this?
[{
id:1
name:1
choice:[
{
choiceId:1
choiceName:1
choice_value:true
},
{
choiceId:1
choiceName:2
choiceValue:false
}
]
]}
My code is like :
var tbl = from a in db.users
join b in db.choice
on a.choice_id = b.choice_id
select new {
a.id
a.name
a.choice_id
b.choice_name
b.choice_value
}
table.Select(p => new User()
{
id = p.id
name = p.name
choice = new List<Choice>()
{
new Choice()
{
choiceId = p.choiceId
choiceName = p.choiceName
choiceValue = p.choiceValue
}
}
}).toList();
It's will get two with the same id and name I want to get one that contain all choice.

So a pure T-SQL Solution would be something like this:
SELECT Id,
Name,
(
SELECT choice_id, choice_Name, choice_Value
FROM choice c
WHERE c.choice_id = u.choice_id
FOR JSON AUTO
) As choice
FROM users u
FOR JSON AUTO

First is the query. You'll need to work in a "Group By" that groups the entities on the id and name and have the child entities underneath it.
var result = tbl
.GroupBy(p => new { p.id, p.name })
.Select(g => new
{
id = g.Key.id,
name = g.Key.name,
choice = g.Select(p =>
new Choice()
{
choiceId = p.choiceId
choiceName = p.choiceName
choiceValue = p.choiceValue
})
}).toList();
Second, to get it in JSON depends on the libraries you're using. With C# it's likely the .Net Web API or MVC. Either way you can wire it to serialize its responses to JSON which is an entirely different question. If you want to serialize it to a variable directly I'd recommend the Newtonsoft.Json which would be a pretty simple call at this point:
string json = JsonConvert.SerializeObject(result);

Related

LINQ - filtering a result set and joining the result set

I have User Table and each user can have multiple Computers... Lets say I am Ram, I can have only one DomainId, multiple UserId and multiple Computers..
I would like to get the DeviceId of all my computers and those should be only Laptops not Desktops
I am getting all the computers details of a given user
var res = (from b in DB.Users
WHERE b.DomainId == 'MS\\aram'
select new {b.UserId, b.DeviceId}).ToList();
From the above deivces I want to get only those devices which are laptops... The device Id should be present in laptops table
My tblLaptops table is having a column called DeviceId.. I tried in the below query but it is throwing error cannot convert int into anonymous type
var devices = (from a in LBDB.tblLaptops where res.contains(a.DeviceId)
select new {a.UserId, a.DeviceId }).ToList();
once the above is returned I also want to get Display Name of users which is in Extended Users table
Regarding the DisplayName, the column name is DisplayName and the table Name is ExtendedUser .. The ExtenderUserDevice table has DeviceId column which can be used to compare and the DisplayName of the device...
You can try a join:
var result = (from laptop in DB.tblLaptops
join user in DB.Users
on user.DeviceId equals laptop.DeviceId
where user.DomainId =='MS\\aram'
select new { user.UserId, laptop.DeviceId }).ToList();
The above query would return the laptop devices and user ids for a specific domain. Regarding the DisplayName, we need some more info, in order to plug it also this in the above query, in order to fetch also this information.
Update
Since the above it is not going to work, since you access these tables through different contexts, here is my thought.
Provided that laptops is not a rather big table, you could fetch it in memory and make the join in memory. Apparently, this is not an optimal solution, but rather a workaround, that it wouldn't hurt you, if the laptops table is not big.
In terms of code:
// fetch the user devices in the specific domain:
var usersDevices = (from user in DB.Users
where user.DomainId == 'MS\\aram'
select new
{
user.UserId,
user.DeviceId
}).ToList();
// fetch **ALL** the laptops:
var laptops = DB.tblLaptops.ToList();
// perform the join:
var userLaptops = (from laptop in laptops
join userDevice in usersDevices
on userDevice.DeviceId equals laptop.DeviceId
select new
{
user.UserId,
laptop.DeviceId
}).ToList();
The correct approach it would be to think about, why these related info are behind different DbContext classes. That essentially means that these data are in different databases. If so, are these databases in the same machine ? If so and you don't plan these databases to be in different machines in the short future, you could quite probably makes all these queries in the database and fetch from the server that your application leaves only the needed data and not all the data and then filter/join them. IMHO just my 2 cents, based on many assumptions :)
given you tag your question with EF, you could potentially opt for .Include() on navigation properties but if we assume you just want the LINQ, here's a method chain version for your consideration:
var result = users
.Join(extendedUsers, u => u.UserId, eu => eu.UserId, (user, extUser) => new { user, extUser }) // you mentioned you want to join to ExtendedUser, so we might as well do it first. doesn't really make a difference
.Join(tblLaptops, u => u.user.DeviceId, l => l.DeviceId, (user, laptop) => new {user, laptop}) // then we get both results from previous join and add your laptop table onto it
.Where(u => u.user.user.DomainId == "MS\\aram") // filter it all down
.Select(x => new {x.laptop.UserId, x.laptop.DeviceId, x.user.extUser.DisplayName}); // get the desired output
I didn't have your databases to verify the code so I've created something quick:
enum SystemType
{
Desktop = 0,
Laptop,
}
struct User
{
public int Id;
public string Name;
}
struct SystemStruct
{
public int Id;
public SystemType Type;
}
private List<User> users = new List<User>()
{
new User() { Id = 0, Name = "John" },
new User() { Id = 1, Name = "Alice" },
new User() { Id = 2, Name = "Bob" }
};
private List<SystemStruct> systems = new List<SystemStruct>()
{
new SystemStruct() { Id = 0, Type = SystemType.Desktop },
new SystemStruct() { Id = 1, Type = SystemType.Laptop },
new SystemStruct() { Id = 2, Type = SystemType.Desktop }
};
It basically creates an enum and structs to map your database results.
And to join your users only where the system type is a laptop you use:
var laptopUsers = users.Join(systems.Where(s => s.Type == SystemType.Laptop), u => u.Id, s => s.Id, (user, system) => user);

Combining 2 lists of diffrent types into 1 based on an ID using LINQ

Hello so I'm trying to combine these 2 lists into 1.
List<Entry> testing = new List<Entry>();
List<Tool> yes = new List<Tool>();
foreach (var p in _context.Tool
.Join(_context.Entry,
o => o.ToolId,
od => od.FktoolId,
(o, od) => new { o.Name, od.Measure, od.RunDate, od.User, od.V }))
{
testing.Add(new LogEntries {
Measure = p.Measure,
V = p.V,
RunDate = p.RunDate,
User = p.User});
yes.Add(new Tool {
Name = p.Name });
}
So I'm taking these 2 lists because I want the Name from one table to join with the other table. I'm then taking the list and turning it into JSON
ViewBag.Testing = JsonConvert.SerializeObject(testing);
I'm only getting the data from testing list but I want the Name from list "yes" into list "testing" based on the tool ids.
So can I just push the name into the list or is there a way to merge them. Since they are diffrent types I can't concat or intersect.
So the Data I'm pulling now is
[{ Measure : "1",
V : 3,
RunDate: "08/12/19",
User: "Test User"}]
And I'm wanting it to look like this
[{ Measure : "1",
V : 3,
RunDate: "08/12/19",
User: "Test User",
Name: "Dragon"}]
In your Join statement you are already creating combined objects. Why don't you just put those combined objects in a list and serialize that?
var list = _context.Tool
.Join(_context.Entry,
o => o.ToolId,
od => od.FktoolId,
(o, od) => new { od.Measure, od.V, od.RunDate, od.User, o.Name })
.ToList();
ViewBag.Testing = JsonConvert.SerializeObject(list);

What is the best way to get the percentage for an object in linq list and map it to JSON?

As a result of a join in linq I am getting a list of RoleViewModel objects. What I want to do after is to get the percentage for each WorkRole in the list and map the work role's percentage and it's name to a json.
So, if I have two objects total in var list - one has RoleName "Role1" and the other has RoleName "Role2", what's the best way to get a JSON like :
myObj = {
"rolename":"Role1",
"perc":50
},
{
"rolename":"Role2",
"perc":50
},
Here is the query for my list :
var list= list1.
Join(db.WorkRolesUsersDetails,
o => o.WorkRoleId, od => od.WorkRoleId,
(o, od) => new
{
WorkRoleId = o.WorkRoleId,
RoleName = o.RoleName,
RoleDescription = o.RoleDescription,
CompanyId = o.CompanyId,
WRUDId = od.WRUDId,
UserDetailsId = od.UserDetailsId,
FocusStart = od.FocusStart,
FocusEnd = od.FocusEnd
}).ToList()
.Select(item => new RoleViewModel(
item.WorkRoleId,
item.RoleName,
item.RoleDescription,
item.CompanyId,
item.WRUDId,
item.UserDetailsId,
item.FocusStart,
item.FocusEnd)).ToList();
So, any tips on how can I do what I want in the best and easiest way? I am new to c#.
It should work like that:
var perclist = list.GroupBy(i=>i.RoleName)
.Select(i=>
new {
rolename=i.Key,
perc = ((double)(i.Count()) / (double)(list.Count()) )*100
});
var json = JsonConvert.SerializeObject(perclist);
I user Json.NET for serialization

Linq Nested Projections in Entity Framework

In a Linq projection for an EF entity, I am able to select only properties which are required.
In the code below ex questions. Now question has navigation property as options.
I want to select only option id and option title as nested property
If I write
options.ToList()
it will work with all properties.
I want only Options.ID and Options.Title to be included
var query = from c in context.Sec_Questions.AsNoTracking()
where c.IsActive == true
select new
{ c.ID, c.QuestionType,
c.Title, c.ControlName,
c.IsNumberOnly,
c.Maxlenghth,
options = c.Options.ToList(),
c.IsMultiple,
c.ControlID,
c.HelpText,
c.IsRequired };
var questions = query.ToList();
But this code doesn't work
var query = from c in context.Sec_Questions.AsNoTracking()
where c.IsActive == true
select new
{ c.ID, c.QuestionType,
c.Title, c.ControlName,
c.IsNumberOnly,
c.Maxlenghth,
options = new { c.Options.ID, c.options.Title },
c.IsMultiple,
c.ControlID,
c.HelpText,
c.IsRequired };
var questions = query.ToList();
From this c.Options.ToList() I understand that Options is a collection. So what you should do is use .Select to project a new object containing just those two properties:
var query = from c in context.Sec_Questions
where c.IsActive == true
select new {
c.ID,
c.QuestionType,
c.Title,
c.ControlName,
c.IsNumberOnly,
c.Maxlenghth,
Options = c.Options.Select(o => new { o.ID, o.Title }),
c.IsMultiple,
c.ControlID,
c.HelpText,
c.IsRequired };

Join table with object list

I have a table, lets say tblCar with all the related columns like Id, Make, Model, Color etc.
I have a search model for car containing two params Id and Model.
public class CarSearch
{
public int Id { get; set; }
public string Model { get; set; }
}
var carSearchObjets = new List<CarSearch>();
With list of primitive data (like Id list), to get cars with those Ids I could have done:
var idList = new List<int> { 1, 2, 3 };
var carsFromQuery = context.Cars.Where(x => idList.Contains(x.Id);
But if I have to fetch all the cars with Id and model from the list, how do I do it? Simple join cannot be done between in memory objects and tables.
I need something like,
from m in context.Cars
join n in carSearchObjets
on new { Id = n.Id, Model = n.Model } equals new { Id = m.Id, Model = m.Model }
select m;
This obviously won't work.
Please ignore any typos.And if you need more info or the question is not clear, let me know.
One (ugly-but-working) way to manage that is to use concatenation with a "never used" concat char.
I mean a char that should never appear in the datas. This is always dangerous, as... never is never sure, but you've got the idea.
For example, we'll say that our "never used" concat char will be ~
This is not good for perf, but at least working :
var carSearchObjectsConcatenated = carSearchObjets.Select(m => new { m.Id + "~" + m.Model});
then you can use Contains again (concatenating on the db too) : you'll need to use SqlFunctions.StringConvert if you wanna concatenate string and numbers on the db side.
var result = context.Cars.Where(m =>
carSearchObjectsConcatenated.Contains(SqlFunctions.StringConvert((double)m.Id) + "~" + m.Model);
EDIT
Another solution would be to use PredicateBuilder, as mentionned by Sorax, or to build your own Filter method if you don't want a third party lib (but PredicateBuilder is really fine).
Something like that in a static class :
public static IQueryable<Car> FilterCars(this IQueryable<Car> cars, IEnumerable<SearchCar> searchCars)
{
var parameter = Expression.Parameter(typeof (Car), "m");
var idExpression = Expression.Property(parameter, "Id");
var modelExpression = Expression.Property(parameter, "Model");
Expression body = null;
foreach (var search in searchCars)
{
var idConstant = Expression.Constant(search.Id);
var modelConstant = Expression.Constant(search.Model);
Expression innerExpression = Expression.AndAlso(Expression.Equal(idExpression, idConstant), Expression.Equal(modelExpression, modelConstant));
body = body == null
? innerExpression
: Expression.OrElse(body, innerExpression);
}
var lambda = Expression.Lambda<Func<Car, bool>>(body, new[] {parameter});
return cars.Where(lambda);
}
usage
var result = context.Cars.FilterCars(carSearchObjets);
this will generate an sql looking like
select ...
from Car
where
(Id = 1 And Model = "ax") or
(Id = 2 And Model = "az") or
(Id = 3 And Model = "ft")
'PredicateBuilder' might be helpful.
var predicate = PredicateBuilder.False<Car>();
carSearchObjects
.ForEach(a => predicate = predicate.Or(p => p.Id == a.Id && p.Model == a.Model));
var carsFromQuery = context.Cars.AsExpandable().Where(predicate);
Note the text in the link regarding EF:
If you're using Entity Framework, you'll need the complete LINQKit -
for the AsExpandable functionality. You can either reference
LINQKit.dll or copy LINQKit's source code into your application.
Old school solution..
//in case you have a
List<CarSearch> search_list; //already filled
List<Cars> cars_found = new List<Cars>();
foreach(CarSearch carSearch in search_list)
{
List<Cars> carsFromQuery = context.Cars.Where(x => x.Id == carSearch.Id && x.Model == carSearch.Model).ToList();
cars_found.AddRange(carsFromQuery);
}
Abd don't worry about the for loops.
I landed up passing in an xml list as a parameter to the sql query and joined to that:
var xml = new XElement("Cars", yourlist.Select(i => new XElement("Car", new XElement("Id", i.Id), new XElement("Model", i.Model))));
var results = Cars
.FromSql("SELECT cars.*"
+ "FROM #xml.nodes('/Cars/Car') Nodes(Node)"
+ "JOIN Cars cars on cars.Id = Nodes.Node.value('Id[1]', 'int') and cars.Model = Nodes.Node.value('Model[1]', 'varchar(100)')",
new SqlParameter("#xml", new SqlXml(xml.CreateReader())));
For entity-framework-core users I created a nuget package extension:
EntityFrameworkCore.SqlServer.Extensions.Contains

Categories

Resources