How To Convert IEnumerable To one object - c#

In my Application Users can Have Multiple Roles , I Want To Convert List of Roles To One Common Role Object For Apply Permission
How i can handle convert List to Common role ?
public class Role
{
public boolData ListBools { get; set; }
public ListId ListIds { get; set; }
}
public class boolData
{
public bool CanSale { get; set; }
public bool CanBuy { get; set; }
}
public class ListId
{
public IEnumerable<Int64> Product { get; set; }
public IEnumerable<Int64> Formula { get; set; }
}
For Example this User have two Roles I Want to exucute common to one role object
IEnumerable<Role> UserRoles = new List<Role>()
{
new Role(){ListBools = new boolData()
{
CanBuy = false,
CanSale = true
},
ListIds = new ListId()
{
Product = new List<long>(){1,4,58},
Formula = new List<long>(){2,7,}
}
},
new Role(){ListBools = new boolData()
{
CanBuy = true,
CanSale = true
},
ListIds = new ListId()
{
Product = new List<long>(){1,6,70},
Formula = new List<long>(){2,9,}
}
},
};
must convert To
Role Result = new Role()
{
ListBools = new boolData()
{
CanBuy = true,
CanSale = true
},
ListIds = new ListId()
{
Product = new List<long>() { 1, 4, 6, 58, 70 },
Formula = new List<long>() { 2, 7, 9, }
}
};

You can use combination of Any and SelectMany() from Linq,
Like,
var Result = new Role()
{
ListBools = new boolData()
{
CanBuy = UserRoles.Any(x => x.ListBools.CanBuy),
CanSale = UserRoles.Any(x => x.ListBools.CanSale)
},
ListIds = new ListId()
{
Product = UserRoles.SelectMany(x => x.ListIds.Product).Distinct(),
Formula = UserRoles.SelectMany(x => x.ListIds.Formula).Distinct()
}
};

For list of Formulas and Products you can use SelectMany + Distinct:
Role sum = new Role
{
ListBools = new boolData()
{
CanBuy = true,
CanSale = true
},
ListIds = new ListId
{
Formula = UserRoles.SelectMany(x => x.ListIds.Formula).Distinct().ToList(),
Product = UserRoles.SelectMany(x => x.ListIds.Product).Distinct().ToList()
}
};
I'm not suer how ListBools should be produced .... Maybe you should group all rules and join Formula/Product per boolData

You could use this code:
SelectMany merges many Product/Formula lists into one
Distinct removes duplicates
OrderBy ensures a ascending order
Any checks if at least one CanBuy/CanSale property is true
Btw - I agree with the comments that say the Class/Property names could be be
var products = UserRoles.SelectMany(m => m.ListIds.Product).Distinct().OrderBy(m => m).ToList();
var formulas = UserRoles.SelectMany(m => m.ListIds.Formula).Distinct().OrderBy(m => m).ToList();
var anyCanBuy = UserRoles.Any(m => m.ListBools.CanBuy);
var anyCanSale = UserRoles.Any(m => m.ListBools.CanSale);
Role Result = new Role()
{
ListBools = new boolData()
{
CanBuy = anyCanBuy,
CanSale = anyCanSale,
},
ListIds = new ListId()
{
Product = products,
Formula = formulas,
}
};

Related

How to prepare data for all apps at once without loop for each app?

I have one master list of 2 filters and this linked with one app,
var filterData = new List<FilterData>
{
new FilterData { Filter1 = "test1", Filter2 = "X", GoesToApp = "App1"},
new FilterData { Filter1 = "test2", Filter2 = "X", GoesToApp = "App2"},
new FilterData { Filter1 = "test2", Filter2 = "Y", GoesToApp = "App1"},
new FilterData { Filter1 = "test3", Filter2 = "Y", GoesToApp = "App3"},
new FilterData { Filter1 = "test4", Filter2 = "Y", GoesToApp = "App3"},
new FilterData { Filter1 = "test1", Filter2 = "Z", GoesToApp = "App1"},
};
Now I have one Data which contains above filters data as well,
var data = new Data
{
Id = 1,
Filter2 = new List<string> { "X", "Y" },
FilterValues = new List<FilterValue>
{
new FilterValue {Filter1 = "test1", Value = "1"},
new FilterValue {Filter1 = "test2", Value = "2"},
new FilterValue {Filter1 = "test3", Value = "3"}
}
};
Here I need to filter Data with master list (above) on Filter1 and Filter2 and need to prepare data for each app (App1, App2 and so on).
With the code below I am able to prepare data for only one app at a time (example, App1) and this gives me the desired data what I want for this app based on comparing filters,
var destData = new Data()
{
Id = 1,
FilterValues = new List<FilterValue>()
};
var filter1DataForApp1 = filterData.Where(item =>
item.GoesToApp == "App1" && data.Filter2.Contains(item.Filter2))
.Select(item => item.Filter1);
foreach (var f1 in filter1DataForApp1)
{
destData.FilterValues.AddRange(data.FilterValues.Where(a => a.Filter1 == f1));
}
For a single item in Data if I have 50 apps , then above code I need to run 50 times for each app, which I want to avoid. How to do this? Is this possible to generates all apps data in one query?
Here are different class structures,
public class FilterData
{
public string Filter1 { get; set; }
public string Filter2 { get; set; }
public string GoesToApp { get; set; }
}
public class Data
{
public int Id { get; set; }
public List<string> Filter2 { get; set; }
public List<FilterValue> FilterValues { get; set; }
}
public class FilterValue
{
public string Filter1 { get; set; }
public string Value { get; set; }
}
Expected output data for,
App1 (only test1 and test2)
var app1Data = new Data
{
Id = 1,
Filter2 = null,
FilterValues = new List<FilterValue>
{
new FilterValue {Filter1 = "test1", Value = "1"},
new FilterValue {Filter1 = "test2", Value = "2"}
}
};
App2 (only test2)
var app1Data = new Data
{
Id = 1,
Filter2 = null,
FilterValues = new List<FilterValue>
{
new FilterValue {Filter1 = "test2", Value = "2"}
}
};
and so on...
I need one result something like something like List<GoesToApp, List<datafor that app>>
I think what you're looking for is something like this:
var result = filterData.GroupBy(x => x.GoesToApp).ToDictionary(grp => grp.Key, grp => {
return new Data
{
Id = 1,
FilterValues = grp.Where(item => data.Filter2.Contains(item.Filter2)).SelectMany(item => data.FilterValues.Where(f => item.Filter1 == f.Filter1)).ToList()
};
});
foreach(var item in result)
{
Console.WriteLine(item.Key);
foreach(var fv in item.Value.FilterValues)
Console.WriteLine($"\t{fv.Filter1}");
}
Live example: https://dotnetfiddle.net/xsomse

Can I use LINQ GroupBy to do this more cleanly?

In this contrived example, which closely resembles my real-world problem, I have a data set coming from an external source. Each record from the external source takes the following form:
[Classification] NVARCHAR(32),
[Rank] INT,
[Data] NVARCHAR(1024)
I am looking to build an object where the Rank and Data are patched into a single instance of a response object that contains list properties for the three hard-coded Classification values, ordered by Rank.
I have something that works, but I can't help but think that it could be done better. This is what I have:
public static void Main()
{
IEnumerable<GroupingTestRecord> records = new List<GroupingTestRecord>
{
new GroupingTestRecord { Classification = "A", Rank = 1, Data = "A1" },
new GroupingTestRecord { Classification = "A", Rank = 2, Data = "A2" },
new GroupingTestRecord { Classification = "A", Rank = 3, Data = "A3" },
new GroupingTestRecord { Classification = "B", Rank = 1, Data = "B1" },
new GroupingTestRecord { Classification = "B", Rank = 2, Data = "B2" },
new GroupingTestRecord { Classification = "B", Rank = 3, Data = "B3" },
new GroupingTestRecord { Classification = "C", Rank = 1, Data = "C1" },
new GroupingTestRecord { Classification = "C", Rank = 2, Data = "C2" },
new GroupingTestRecord { Classification = "C", Rank = 3, Data = "C3" },
};
GroupTestResult r = new GroupTestResult
{
A = records.Where(i => i.Classification == "A").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
B = records.Where(i => i.Classification == "B").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
C = records.Where(i => i.Classification == "C").Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank),
};
The source record DTO:
public class GroupingTestRecord
{
public string Classification { get; set; }
public int? Rank { get; set; }
public string Data { get; set; }
}
The destination single class:
public class GroupTestResult
{
public IEnumerable<GroupTestResultItem> A { get; set; }
public IEnumerable<GroupTestResultItem> B { get; set; }
public IEnumerable<GroupTestResultItem> C { get; set; }
}
The distination child class:
public class GroupTestResultItem
{
public int? Rank { get; set; }
public string Data { get; set; }
}
Ouput
{
"A":[
{
"Rank":1,
"Data":"A1"
},
{
"Rank":2,
"Data":"A2"
},
{
"Rank":3,
"Data":"A3"
}
],
"B":[
{
"Rank":1,
"Data":"B1"
},
{
"Rank":2,
"Data":"B2"
},
{
"Rank":3,
"Data":"B3"
}
],
"C":[
{
"Rank":1,
"Data":"C1"
},
{
"Rank":2,
"Data":"C2"
},
{
"Rank":3,
"Data":"C3"
}
]
}
Fiddle
Is there a better way to achieve my goal here?
The same JSON output was achieved using GroupBy first on the Classification and applying ToDictionary on the resulting IGrouping<string, GroupingTestRecord>.Key
var r = records
.GroupBy(_ => _.Classification)
.ToDictionary(
k => k.Key,
v => v.Select(j => new GroupTestResultItem { Rank = j.Rank, Data = j.Data, }).OrderBy(k => k.Rank).ToArray()
);
var json = JsonConvert.SerializeObject(r);
Console.WriteLine(json);
which should easily deserialize to the destination single class (for example on a client)
var result = JsonConvert.DeserializeObject<GroupTestResult>(json);
is it possible to get the top level result into a GroupTestResult object?
Build the result from the dictionary
var result = new GroupTestResult {
A = r.ContainsKey("A") ? r["A"] : Enumerable.Empty<GroupTestResultItem>();,
B = r.ContainsKey("B") ? r["B"] : Enumerable.Empty<GroupTestResultItem>();,
C = r.ContainsKey("C") ? r["C"] : Enumerable.Empty<GroupTestResultItem>();,
};
Or this
var result = records.GroupBy(x => x.Classification)
.ToDictionary(x => x.Key, x => x.Select(y => new {y.Rank, y.Data})
.OrderBy(y => y.Rank));
Console.WriteLine(JsonConvert.SerializeObject(result));
Full Demo Here

c# Group linq results into a new list of a particular class

How do I need to write to iterate through the results of each of those three initial lists in order to return a single List. Title is the grouping value.
var invoiced = new List<Anonim>
{
new Anonim {Category = 1, Title = "Legal", Amount = 20},
new Anonim {Category = 2, Title = "Accounting", Amount = 10}
}
var settled = new List<Anonim> {
new Anonim {Category = 1, Title = "Legal", Amount = 10}
}
var credit = new List<Anonim> {
new Anonim {Category = 1, Title = "Legal", Amount = 30},
new Anonim {Category = 2, Title = "Accounting", Amount = 20}
}
var result = new List<Result> {
new Result {Title = credit.Title, Invoiced = invoiced.Amount, Settled = settled.Amount, SumAmount = credit.Amount + settled.Amount + invoiced.Amount },
new Result {Title = credit.Title, Invoiced = invoiced.Amount, Settled = settled.Amount, SumAmount = credit.Amount + settled.Amount + invoiced.Amount }
}
public class Result
{
public string Title { get; set; }
public decimal Credit { get; set; }
public decimal Invoiced { get; set; }
public decimal Settled { get; set; }
public decimal SumAmount { get; set; }
}
public class Anonim {
public int Category { get; set; }
public string Title { get; set; }
public decimal Amount { get; set; }
}
SumAmount is the sum of Invoiced, settled, credit of each item
It's a little unclear what you want to happen, but assuming you want to group by the Title property, here's one method. First you project each list into the Result class, making sure to set the relevant properties for each one, union them together into a big list and then group them to get the totals:
var groupedResults = invoiced.Select(i => new Result
{
Title = i.Title,
Invoiced = i.Amount
}).Union(settled.Select(i => new Result
{
Title = i.Title,
Settled = i.Amount
})).Union(credit.Select(i => new Result
{
Title = i.Title,
Credit = i.Amount
}));
var result = groupedResults
.GroupBy(r => r.Title)
.Select(g => new Result
{
Title = g.Key,
Invoiced = g.Sum(r => r.Invoiced),
Settled = g.Sum(r => r.Settled),
Credit = g.Sum(r => r.Credit),
SumAmount = g.Sum(r => r.Invoiced+r.Settled+r.Credit)
});
If I understood you correctly, this is something you want, however this is inefficient due to numerous collection traversals. Maybe #DavidG solution will be more performant.
var groupedSum = invoiced.Union(settled).Union(credit).GroupBy(g => g.Title).Select(g => new Result
{
Title = g.Key,
Credit = credit.Where(c => c.Title == g.Key).Sum(c => c.Amount),
Settled = settled.Where(c => c.Title == g.Key).Sum(c => c.Amount),
Invoiced = invoiced.Where(c => c.Title == g.Key).Sum(c => c.Amount),
SumAmount = g.Sum(i => i.Amount)
});
This is simplest I can find :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace ConsoleApplication73
{
class Program
{
static void Main(string[] args)
{
List<Anonim> invoiced = new List<Anonim>
{
new Anonim {Category = 1, Title = "Legal", Amount = 20},
new Anonim {Category = 2, Title = "Accounting", Amount = 10}
};
List<Anonim> settled = new List<Anonim> {
new Anonim {Category = 1, Title = "Legal", Amount = 10}
};
List<Anonim> credit = new List<Anonim> {
new Anonim {Category = 1, Title = "Legal", Amount = 30},
new Anonim {Category = 2, Title = "Accounting", Amount = 20}
};
List<Result> results = new List<Result>();
List<string> titles = invoiced.Select(x => x.Title).ToList();
titles.AddRange(settled.Select(x => x.Title).ToList());
titles.AddRange(credit.Select(x => x.Title).ToList();
titles = titles.Distinct().ToList();
foreach(string title in titles)
{
Result newResult = new Result();
results.Add(new Result);
newResult.Title = title;
newResult.Credit = credit.Where(x => x.Title == title).Sum(x => x.Amount);
newResult.Invoiced = invoiced.Where(x => x.Title == title).Sum(x => x.Amount);
newResult.Settled = settled.Where(x => x.Title == title).Sum(x => x.Amount);
newResult.SumAmount = newResult.Credit + newResult.Invoiced + newResult.Settled;
}
}
}
public class Result
{
public string Title { get; set; }
public decimal Credit { get; set; }
public decimal Invoiced { get; set; }
public decimal Settled { get; set; }
public decimal SumAmount { get; set; }
}
public class Anonim
{
public int Category { get; set; }
public string Title { get; set; }
public decimal Amount { get; set; }
}
}
var invoiced = new List<Anonim>
{
new Anonim {Category = 1, Title = "Legal", Amount = 20},
new Anonim {Category = 2, Title = "Accounting", Amount = 10}
};
var settled = new List<Anonim> {
new Anonim {Category = 1, Title = "Legal", Amount = 10}
};
List<Anonim> credit = new List<Anonim> {
new Anonim {Category = 1, Title = "Legal", Amount = 30},
new Anonim {Category = 2, Title = "Accounting", Amount = 20}
};
List<Result> x = new List<Result>();
x.AddRange(invoiced.Select(y => new Result { Title = y.Title, Invoiced = y.Amount }));
x.AddRange(settled.Select(y => new Result { Title = y.Title, Invoiced = y.Amount }));
x.AddRange(credit.Select(y => new Result { Title = y.Title, Invoiced = y.Amount }));
var FinalList = x.GroupBy(a => a.Title).Select(Fn => new Result {
Title =Fn.Key,
Invoiced = Fn.Sum(a => a.Invoiced),``
Settled = Fn.Sum(a => a.Settled),
Credit = Fn.Sum(a => a.Credit),
SumAmount = Fn.Sum(a => a.Invoiced + a.Settled + a.Credit)
});

c# and mongodb: Aggregates with filter

I am using latest version of mongodb with MongoDB driver for C#. I created a little example, which should explain my problem. My goal ist to make some conditional Count(), conditional First() or an Average() with with filter. None of it works.
What would be the best solution to this problem. Thanks for any hint
class MealDocument
{
public string name { get; set; }
public DateTime time { get; set; }
public Type type { get; set; }
public double calorie { get; set; }
public enum Type { breakfast, launch, dinner }
}
class MealAnalysis
{
public string name { get; set; }
public int numberOfBreakfast { get; set; }
public DateTime firstLaunch { get; set; }
public double averageDinnerCalorie { get; set; }
}
public void Test()
{
var collection = Database.GetCollection<MealDocument>("meal_test");
collection.InsertMany(new MealDocument[] {
new MealDocument { name = "Thomas", type = MealDocument.Type.breakfast, calorie = 100, time = new DateTime(2017,8,1) },
new MealDocument { name = "Thomas", type = MealDocument.Type.breakfast, calorie = 100, time = new DateTime(2017,8,2) },
new MealDocument { name = "Thomas", type = MealDocument.Type.launch, calorie = 800, time = new DateTime(2017,8,3) },
new MealDocument { name = "Thomas", type = MealDocument.Type.dinner, calorie = 2000, time = new DateTime(2017,8,4) },
new MealDocument { name = "Peter", type = MealDocument.Type.breakfast, calorie = 100, time = new DateTime(2017,8,5) },
new MealDocument { name = "Peter", type = MealDocument.Type.launch, calorie = 500, time = new DateTime(2017,8,6) },
new MealDocument { name = "Peter", type = MealDocument.Type.dinner, calorie = 800, time = new DateTime(2017,8,7) },
new MealDocument { name = "Paul", type = MealDocument.Type.breakfast, calorie = 200, time = new DateTime(2017,8,8) },
new MealDocument { name = "Paul", type = MealDocument.Type.launch, calorie = 600, time = new DateTime(2017,8,9) },
new MealDocument { name = "Paul", type = MealDocument.Type.launch, calorie = 700, time = new DateTime(2017,8,10) },
new MealDocument { name = "Paul", type = MealDocument.Type.dinner, calorie = 1200, time = new DateTime(2017,8,11) }
});
var analysis = collection.Aggregate()
.Group(
doc => doc.name,
group => new MealAnalysis
{
name = group.Key,
// !!!! The condition in the Count() gets ignored
numberOfBreakfast = group.Count(m => m.type == MealDocument.Type.breakfast),
// !!!! Exception --> Not supported
averageDinnerCalorie = group.Where(m => m.type == MealDocument.Type.dinner).Average(m => m.calorie),
// !!!! Exception --> Not supported
firstLaunch = group.First(m => m.type == MealDocument.Type.launch).time
}
);
var query = analysis.ToString();
var result = analysis.ToList();
}
I spent a few hours and I can't get driver to work as you need it to. Although I didn't come up with correct solution this is closest I got. I am not a big fan of this solution because it calculates a lot you don't need but it might work good enough if Db is not that large and until you find solution to problem.
var groupRes = collection.Aggregate()
.Group
(
x => new { x.name, x.type },
g => new
{
key = g.Key,
count = g.Count(),
averageCalorie = g.Average(y => y.calorie),
first = g.First().time
}
)
.ToList();
var paulStat = new MealAnalysis()
{
name = "Paul",
averageDinnerCalorie = groupRes.FirstOrDefault(x => (x.key.name == "Paul" && x.key.type == Type.dinner)).averageCalorie,
numberOfBreakfast = groupRes.FirstOrDefault(x => (x.key.name == "Paul" && x.key.type == Type.breakfast)).count,
firstLaunch = groupRes.FirstOrDefault(x => (x.key.name == "Paul" && x.key.type == Type.launch)).first
};
as you can see I calculate stuff you need for each enum and then construct for each person his MealAnalysis. Maybe someone finds solution you need.
Good Luck!

Linq to entities - filter on a child table via include

I have a one to many r/ship between two tables. I am using Entity Frameworks 6. Table 1 is called "Vote" and table 2 is "VoteInfo". The Vote table has an attribute called "VoteYear" and the VoteInfo table has an attribute called "VoterId". There could be many vote records in a given year but a voter can only vote once per a vote record. My goal is to return a list of all votes
in a year that a voter has voted on. I want to include the information from the VoteInfo table as part of this list. This is how I want to return my result:
<Vote>
<VoteDate>5/21/2015</VoteDate>
<VoteYear>2015</VoteYear>
<VoteInfo>
<VoterId>1</VoterId>
<VoteValue>Yes</VoteValue>
</VoteInfo>
</Vote>
<Vote>
<VoteDate>4/21/2015</VoteDate>
<VoteYear>2015</VoteYear>
<VoteInfo>
<VoterId>1</VoterId>
<VoteValue>Yes</VoteValue>
</VoteInfo>
</Vote>
<Vote>
<VoteDate>3/21/2015</VoteDate>
<VoteYear>2015</VoteYear>
<VoteInfo>
<VoterId>1</VoterId>
<VoteValue>No</VoteValue>
</VoteInfo>
</Vote>
I tried this but not working:
public IQueryable<Vote> GetVoterRecord(string Year, string VoterId)
{
return _dbCtx.Vote.Include("VoteInfo")
.Where(v => v.VoteYear == Year && v.VoteInfo.Any(i => i.VoterId == VoterId));
}
The sample code satisfying OP's conditions is the following.
public class VotesContext : DbContext {
public DbSet<Vote> Votes { get; set; }
public DbSet<VoteInfo> VoteInfos { get; set; }
}
public class Vote {
public int VoteId { get; set; }
public string VoteYear { get; set; }
public virtual ICollection<VoteInfo> VoteInfo { get; set; }
}
public class VoteInfo {
public int VoteInfoId { get; set; }
public string VoterId { get; set; }
}
Then the code
var v1 = db.Votes.Add(new Vote { VoteYear = "2001", });
var v2 = db.Votes.Add(new Vote { VoteYear = "2001", });
var v3 = db.Votes.Add(new Vote { VoteYear = "2002", });
var v4 = db.Votes.Add(new Vote { VoteYear = "2003", });
v1.VoteInfo = new List<VoteInfo> { new VoteInfo { VoterId = "1", }, new VoteInfo { VoterId = "3", }, };
v2.VoteInfo = new List<VoteInfo> { new VoteInfo { VoterId = "1", }, new VoteInfo { VoterId = "4", }, };
v3.VoteInfo = new List<VoteInfo> { new VoteInfo { VoterId = "2", }, };
//v1.VoteInfo = new List<VoteInfo> { new VoteInfo { VoterId = "3", }, };
//v2.VoteInfo = new List<VoteInfo> { new VoteInfo { VoterId = "4", }, };
db.SaveChanges();
// Each record corresponds to a vote in 2001 where voter #1 has voted.
// VoteInfo for each voter in this vote is included.
var votesIn2001With1 = db.Votes
.Where(x1 =>
x1.VoteYear == "2001" &&
x1.VoteInfo.Any(x2 => x2.VoterId == "1"))
.Select(x => new {
vote = x,
voteInfo = x.VoteInfo.ToList(),
})
.ToList();
// Each record corresponds to a vote in 2001 where voter #1 has voted.
// VoteInfo only for voter #1 is included.
var votesOf1In2001 = db.Votes
.Where(x1 =>
x1.VoteYear == "2001" &&
x1.VoteInfo.Any(x2 => x2.VoterId == "1"))
.Select(x1 => new {
vote = x1,
voteInfo = x1.VoteInfo
.Where(x2 => x2.VoterId == "1")
.ToList(),
})
.ToList();
does exactly the necessary things.

Categories