Translate hierarchical result set to custom object using LINQ - c#

I have a hierarchical result set like so:
Then I have a custom object as such:
public class AuthorizedEntity
{
public Departments Department { get; set; }
public string Username { get; set; }
public List<AuthController> Controllers = new List<AuthController>();
}
public class AuthController
{
public string Name { get; set; }
public List<AuthAction> Actions = new List<AuthAction>();
}
public class AuthAction
{
public string Name { get; set; }
public List<string> Methods = new List<string>();
}
Would it be possible to convert the following data into the respective object? In this particular case, the username is justinfarrugia, Controller = StocktakeController, Actions = Permissions with Methods = Edit Permissions and Set Permissions and Action = StockEvaluation with Method = Update Measurements.
I am looking for the most efficient solution.
I have tried this but it doesn't get me to the desired outcome:
ObjectResult<SP_GetPrivilegesByUsername_Result> lstAuthorizedUsersRaw = dbsp.SP_GetPrivilegesByUsername(inUsername.Trim());
lstAuthorizedUsersRaw.GroupBy(p => p.Cntrollers_Name).ToList().ForEach(r =>
{
authEntity.Controllers.Add(new AuthController()
{
Name = r.Key,
Actions = new List<AuthAction>() {
new AuthAction() {
Name = r.ToList().Select(q => q.HMVAct_Name).FirstOrDefault(),
Methods = r.ToList().Select(w => w.HMVMethd_Name).ToList()
}
}
});
});
Thanks,

You missed second grouping - when you select actions from the controller group:
var lstAuthorizedUsersRaw = dbsp.SP_GetPrivilegesByUsername(inUsername.Trim());
authEntity.Controllers = lstAuthorizedUsersRaw
.GroupBy(p => p.Cntrollers_Name)
.Select(controllerGroup => new AuthController {
Name = controllerGroup.Key,
Actions = controllerGroup
.GroupBy(p => p.HMVAct_Name) // here
.Select(actionGroup => new AuthAction {
Name = actionGroup.Key,
Methods = actionGroup.Select(pu => p.HMVMethd_Name).ToList()
}).ToList()
}).ToList();

Related

Map list manually from context

Initially I was using automapper for this but its seems way harder for me to implement it.
Basically, I just want to return an empty list instead of null values. I can do this on projects level but not on teammates level. The API must not return a null because the UI that consumes it will have an error.
Sample of my implementation below:
Projects = !Util.IsNullOrEmpty(x.Projects) ? x.Projects : new List<ProjectsDto>(),
Ill highly appreciate if someone can guide me on how to manually map this with null/empty checking.
If you can also provide and example using automapper that too will be very helpful.
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public List<ProjectsDto> Projects { get; set; }
}
public class ProjectsDto
{
public string Status { get; set; }
public List<TeammatesDto> Teammates { get; set; }
}
public class TeammatesDto
{
public string TeammateName { get; set; }
public string PreviousProject { get; set; }
}
//Get by Id
var employee = await _context.Employees
.Where(x => x.id.Equals(request.Id)
.FirstOrDefaultAsync(cancellationToken);
//Map employee
EmployeeDto ret = new EmployeeDto()
{
Id = employee.id,
Name = employee.Name,
Projects = null //TODO: map manually
}
//Get all employees
var employees = await _context.Employees.AsNoTracking()
.ToListAsync(cancellationToken);
//Map here
IList<EmployeeDto> list = new List<EmployeeDto>();
foreach (var x in employees)
{
EmployeeDto dto = new EmployeeDto()
{
Id = x.id,
Name = x.Name,
Projects = null //TODO: map manually
};
list.Add(dto);
}
return list;
Instead of materializing full entities, do the following:
var query = _context.Employees
.Select(e = new EmployeeDto
{
Id = e.id,
Name = e.Name,
Projects = e.Projects.Select(p => new ProjectDto
{
Status = p.Status,
Templates = p.Templates.Select(t => new TemplateDto
{
TeammateName = t.TeammateName,
PreviousProject = t.PreviousProject
}).ToList()
}).ToList()
}
);
var result = await query.ToListAsync();

Get and Add/Update multilevel embedded/nested MongoDB document using C#

How can i make filter and add/update third, fourth level child of a mongodb document using C#.
I can add/Update till second level but not further. Please provide me a solution or any kind reference from where can get help. Is there any other way to do it except builders..Elemmatch.
Here is my classes and code:
namespace CrudWithMultilvelNestedDoc
{
public class Channel
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
public string Name { get; set; }
public Episode[] Episodes { get; set; }
}
public class Episode
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
public string Name { get; set; }
public Track[] Tracks { get; set; }
}
public class Track
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
public string Name { get; set; }
public Like[] Likes { get; set; }
}
public class Like
{
[BsonId]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
public string Name { get; set; }
}
}
//First Method
//var filter = Builders<Channel>.Filter.And(Builders<Channel>
// .Filter.Where(x => x.Id == "5e4606e6ae7b090688671416"), // OR &
// Builders<Channel>.Filter.ElemMatch(e => e.Episodes, Builders<Episode>
// .Filter.Eq(e => e.Id, "5e460851d29c1b3df4d27b7d")));
//Second Method
//var filter = Builders<Channel>.Filter.Eq(e => e.Id, "5e4606e6ae7b090688671416")
// & Builders<Channel>.Filter.ElemMatch(e => e.Episodes, Builders<Episode>.Filter.Eq(e => e.Id, "5e46071d385a672b0cea0f86"));
//Third Method
var filter = channelFilter.ElemMatch(e => e.Episodes, episodeFilter.ElemMatch(e=> e.Tracks, trackFilter.Eq(e => e.Id, "5e460dbe2bc5e70c9cfeac21")));
var data = collection.Find(filter);
//Update Filter
var update = Builders<Channel>.Update.Push("Episodes[-1].Tracks[-1].Likes", like);
var result = collection.UpdateOne(filter, update);
//Data
{"_id":"5e4606e6ae7b090688671416","Name":"Channel 1","Episodes":[{"_id":"5e46071d385a672b0cea0f86","Name":"Episode 1","Tracks":[{"_id":"5e460dbe2bc5e70c9cfeac21","Name":"Trak 1","Likes":[]},{"_id":"5e4612d60747a2121870c815","Name":"Trak 2","Likes":[]}]},{"_id":"5e460851d29c1b3df4d27b7d","Name":"Episode 2","Tracks":[{"_id":"5e460e307ca6843758ce814e","Name":"Trak 1","Likes":[]}]}]}
As per documentation:
The positional $ operator cannot be used for queries which traverse more than one array
So using -1 is not a way forward here. The approach you should take is the $ positional filtered operator.
There's no strongly-typed representation in C# so your code can look like below:
var filter = Builders<Channel>.Filter.Eq(x => x.Id, "5e4606e6ae7b090688671416");
var like = new Like() {Name = "new like", Id = "1"};
var episodeId = "5e46071d385a672b0cea0f86";
var trackId = "5e460dbe2bc5e70c9cfeac21";
var update = Builders<Channel>.Update.Push("Episodes.$[e].Tracks.$[t].Likes", like);
var arrayFilters = new List<ArrayFilterDefinition>();
ArrayFilterDefinition<BsonDocument> episodesFilter = new BsonDocument("e._id", new BsonDocument("$eq", episodeId));
ArrayFilterDefinition<BsonDocument> tracksFilter = new BsonDocument("t._id", new BsonDocument("$eq", trackId));
arrayFilters.Add(episodesFilter);
arrayFilters.Add(tracksFilter);
var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };
var result = mongoDBCollection.UpdateOne(filter, update, updateOptions);

Get all values for A variable in a class

I have two classes one of them is Destinations and the other one is DestinationDetails
public class Destinations
{
public Destinations() { }
public string CarrierName { get; set; }
public List<DestinationDetails> Details { get; set; }
}
public class DestinationDetails
{
public DestinationDetails() { }
public string Destination { get; set; }
public string Type { get; set; }
}
I want to get all string "Destination" in the second class from List of objects from the first class
I have List<Destinations> and I don't want to use for loop or foreach statments
var dest = new Destinations();
//Initialize the details
var destNames = dest.Details.Select(d => d.Destination).ToList();
Are you looking for something like this?
var det = new Destinations();
det.Details = new List<DestinationDetails>();
det.Details.Add(new DestinationDetails() { Destination = "CA" });
det.Details.Add(new DestinationDetails() { Destination = "NJ" });
...
...
var details = new DestinationDetails();
details.Destination = string.Join(",",det.Details.Select(x => x.Destination).ToArray() );
Update:-
provided list of Destinations "allDet", you can get the list of strings as below:-
alldet.Where(x => x.Details != null).SelectMany(x => x.Details.Select(y => y.Destination)).ToList() //With out ToList() it will give you IEnumerable<String>
List<Destinations> AirportDestinations ; // this list has Destinations objects which have Details which have Destination
So by using SelectMany
List<string> cities.AddRange(AirportDestinations.Where(x => x.Details != null).SelectMany(d => d.Details.Select(s => s.Destination)));
Now you have all Destination in all objects in the list

Add a list of DTOs to the master DTO

I have two DTOs:
public class MasterDTO
{
public int Id { get; set; }
public string Name { get; set; }
public List<DetailDTO> Details { get; set; }
}
public class DetailDTO
{
public int Id { get; set; }
public string DetailName { get; set; }
}
Also, I have a function:
using (var context = new Context())
{
var r = context.MasterData
.Select(d => new MasterDTO
{
Id = d.Id,
Name = d.Name,
}
}
I need to fill the list of DetailDTOs too and do it in a single request.
At this moment, I have to get list of DetailsData data and add it through foreach to the MasterDTO, which, of course causes a lot of requests to the database server.
Is there a better solution?
In your data call, do an eager load on your DetailData.
Example:
var r = context.MasterData.Include("DetailData")
DetailData should be the name of your navigation property attached to your MasterData entity.
This will cause detail data to be pulled along with your call for MasterData.
The full call may look something like this:
using (var context = new Context())
{
context.LazyLoadingEnabled = false;
var r = context.MasterData.Include("DetailData")
.Select(d => new MasterDTO()
{
Id = d.Id,
Name = d.Name,
Details = d.Details.Select(dt => new DetailDTO()
{
Id = dt.Id,
DetailName = dt.DetailName
})
});
}

Automapper overwrites missing source property on list with child objects

I have a problem with Automapper. I set up a test windows form application and below is the code. Also look at the comments after each MessageBox:
public class FirstClass
{
public string FirstProp { get; set; }
public IList<FirstClassChild> Children { get; set; }
}
public class FirstClassChild
{
public string FirstChildProp { get; set; }
}
public class SecondClass
{
public string FirstProp { get; set; }
public string SecondProp { get; set; }
public IList<SecondClassChild> Children { get; set; }
}
public class SecondClassChild
{
public string FirstChildProp { get; set; }
public string SecondChildProp { get; set; }
}
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
AutoMapper.Mapper.CreateMap<FirstClass, SecondClass>();
AutoMapper.Mapper.CreateMap<FirstClassChild, SecondClassChild>();
var f = new FirstClass { FirstProp = "FirstClass" };
f.Children = new List<FirstClassChild> { new FirstClassChild { FirstChildProp = "FirstClass" } };
var s = new SecondClass { FirstProp = "SecondClass", SecondProp = "SecondClass" };
s.Children = new List<SecondClassChild> { new SecondClassChild { FirstChildProp = "SecondClass", SecondChildProp = "SecondClass" } };
AutoMapper.Mapper.Map(f, s);
var fc = new FirstClassChild { FirstChildProp = "FirstClass" };
var sc = new SecondClassChild { FirstChildProp = "SecondClass", SecondChildProp = "SecondClass" };
AutoMapper.Mapper.Map(fc, sc);
MessageBox.Show(sc.FirstChildProp);//FirstClass as expected
MessageBox.Show(sc.SecondChildProp);//SecondClass as expected
MessageBox.Show(s.FirstProp);//FirstClass as expected
MessageBox.Show(s.SecondProp);//SecondClass as expected
MessageBox.Show(s.Children.First().FirstChildProp);//FirstClass as expected
MessageBox.Show(s.Children.First().SecondChildProp);//Empty not expected!!
}
}
What can I do to avoid this? Is this behavior expected?
Anyway can anyone guide me how make SecondClass childs SecondChildProp to remain "SecondClass" as it is before the mapping occurs.
I asked a similar question here and found another similar one here.
I think #PatrickSteele makes a very good point: how is AutoMapper supposed to map a source list to a dest list of existing objects, when the dest list may not necessarily bear any resemblance to the source list? i.e. "But what if one list has 3 and the other list has 5?"
If you are sure that FirstClass and SecondClass have the same number of Children, and if the FirstClass's Nth Child always corresponds to SecondClass's Nth child, you could try something like this:
Mapper.CreateMap<FirstClass, SecondClass>()
.ForMember(m => m.Children, o => o.Ignore())
.AfterMap((src, dest) =>
{
for (var i = 0; i < dest.Children.Count; i++)
Mapper.Map(src.Children[i], dest.Children[i]);
});
or if FirstChildProp is some kind of unique key:
Mapper.CreateMap<FirstClass, SecondClass>()
.ForMember(m => m.Children, o => o.Ignore())
.AfterMap((src, dest) =>
{
foreach (var dChild in dest.Children)
{
var sChild = src.Children.Single(c => c.FirstChildProp == dChild.FirstChildProp);
Mapper.Map(sChild, dChild);
}
});

Categories

Resources