LINQ with dynamic group by - c#

I have model:
public class Student
{
public string Name{ get; set; }
public DateTime BirthDate{ get; set; }
public string UniversityName{ get; set; }
public decimal Balance{ get; set; }
}
I have three bool variables:
IsName
IsBirthDate
IsUniversityName
And based on them, I need to create GroupBy. If IsBirthDate=true then
DbContext.Students.GroupBy(x => new { x.BirthDate }).Select(s => new
{
s.Key.BirthDate,
Balance = s.Sum(x => x.Balance).ToString(),
});
If IsBirthdate=true, IsUniversityName=true then
DbContext.Students.GroupBy(x => new { x.BirthDate, x.UniversityName }).Select(s => new
{
s.Key.BirthDate,
s.Key.UniversityName,
Balance = s.Sum(x => x.Balance).ToString(),
});
And other options with bool parameters.
How to generate the query dynamically with .GroupBy and .Select?

Maybe this helps you: Create a class that represents the key for your GroupBy:
public class StudentGroupingKey
{
public string Name { get; set; }
public DateTime? BirthDate{ get; set; }
public string UniversityName { get; set; }
}
If IsName is true, the Name property of the grouping key will be the value, otherwise it should have always the same value (e.g. null). In your Select method, you have to have always every property, but they will be null if the corresponding properties will be false. If it is necessary for you that these properties do not exist, you can't work with anoymous types. Maybe dynamic might be an option in that case.
DbContext.Students.GroupBy(x => new StudentGroupingKey
{
Name = IsName ? x.Name : null,
BirthDate = IsBirthDate ? x.Birthdate : null,
UniversityName = IsUniversityName ? x.UniversityName : null
}).Select(s => new
{
s.Key.BirthDate,
s.Key.Name,
s.Key.UniversityName,
Balance = s.Sum(x => x.Balance).ToString()
});

Related

Error when using Select() instead of Include() in a query

I have the following query:
var catInclude = _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Include(x => x.CatItems)
.SingleOrDefault(p => p.Id == request.ProvId
cancellationToken: cancellationToken);
As I don't want to get all properties from CatItems with Include(), I have created the following query:
var catSelect = _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Select(p ==> new
{ Provider = p,
Items = p.CatItems.Select(x => new List<CatItems> { new CatItems
{ Id = x.Id, Name = x.Name, Price = x.Price } }
})})
SingleOrDefault(cancellationToken: cancellationToken);
But something is wrong in the 2nd query because here return _mapper.ProjectTo<CatDto>(cat) I get the following error:
Argument 1: cannot convert from '<anonymous type: Db.Entities.Cat Prov, System.Colletions.Generic.IEnumerable<System.Colletions.Generic.List<Models.CatItems> > Items>' to 'System.Linq.IQueryable'
Here is my CatDto:
public class CatDto
{
public int ProvId { get; set; }
public List<CatItems> CatItems { get; set; }
}
Here are my entities:
public class Prov
{
public int Id { get; set; }
public Cat Cat { get; set; }
}
public class Cat
{
public int Id { get; set; }
public int ProvId { get; set; }
public List<CatItems> CatItems { get; set; }
}
public class CatItems
{
public int Id { get; set; }
public int CatId { get; set; }
public DateTime CreatedOn { get; set; }
}
Is there a way to recreate the 2nd query and use it?
Main difference that instead of returning List of CatItems, your code returns IEnumerable<List<CatItems>> for property Items.
So, just correct your query to project to List:
var catSelect = await _db.Cat
.Where(x => x.ProvId == request.ProvId)
.Select(p => new CatDto
{
ProvId = p.ProvId,
Items = p.CatItems.Select(x => new CatItems
{
Id = x.Id,
Name = x.Name,
Price = x.Price
})
.ToList()
})
.SingleOrDefaultAsync(cancellationToken: cancellationToken);
I mean, even the exception is pretty self-explanatory. Nevertheless:
You are performing a .Select(...). It returns an Anonymous type. So, your catSelect is an anonymous type, thus the AutoMapper fails.
The quickest fix is to just cast (Cat)catSelect before mapping.
Or, you can dig deeper into how does AutoMapper play with anonymous types.
I feel like you can make most of the classes inherent Id and why is public cat CAT {get; set;} i thought you were supposed to initialize some kind of value

C# and MongoDriver - How to get values from foreign collection (aggregate + lookup)?

I've got a problem with getting values from foreign collection in C#.
In this case I can easily get values from list:
var gamesList = gamesCollection.Find(_ => true).ToList();
foreach (var item in gamesList)
{
Console.WriteLine($"{item.Title}");
}
But when I'm using aggregate with lookup function, I can not access to values from foreign collection.
Here are my two collections which I try to join:
public class GameModel
{
[BsonId]
public ObjectId Id { get; set; }
public string Title { get; set; }
public List<String> Type { get; set; }
public string GameMode { get; set; }
public List<String> Platform { get; set; }
public string Production { get; set; }
}
public class FavouriteGameModel
{
[BsonId]
public ObjectId Id { get; set; }
public ObjectId UserID { get; set; }
public ObjectId GameID { get; set; }
}
And here's the part of problematic code:
var joinedFavGamesList = favouriteGamesCollection.Aggregate().Match(x => x.UserID == loggedUser[0].Id).//ToList();
Lookup("Games", "GameID", "_id", #as: ("myAlias")).
Project(
new BsonDocument { { "_id", 0 }, { "myAlias.Title", 1 } }
).ToList();
Is there any way to invoke to myAlias.Title? I want only this value to display, but i get:
{ "myAlias" : [{ "Title" : "Some Game" }] }
I will be greatful if someone could look at this and tell me what I'm doing wrong. Thanks
my choice would be to join/lookup using the AsQueryable interface like so:
var favGames = favCollection.AsQueryable()
.Where(fg=> fg.UserID== "xxxxxxxxxxx")
.Join(gameCollection.AsQueryable(), //foreign collection
fg => fg.GameID, //local field
gm => gm.ID, //foreign field
(fg, gm) => new { gm.Title }) //projection
.ToList();
with aggregate interface:
public class JoinedGameModel
{
public GameModel[] Results { get; set; }
}
var favGames = favGameCollection.Aggregate()
.Match(fg => fg.UserID == "xxxxxxxxxxxx")
.Lookup<FavouriteGameModel, GameModel, JoinedGameModel>(
gameCollection,
fg => fg.GameID,
gm => gm.ID,
jgm => jgm.Results)
.ReplaceRoot(jgm => jgm.Results[0])
.Project(gm => new { gm.Title })
.ToList();

How do I negotiate joins and groupings based on nested properties in LINQ?

So I've got a nested data structure like this:
public class ContractTerm
{
public int ContractId { get; set; }
public string SectionId { get; set; }
public string SubsectionId { get; set; }
public string TermId { get; set; }
public int TermOrder { get; set; }
public TermItem TermNavigation { get; set; }
}
public class TermItem
{
public string SectionId { get; set; }
public string SubsectionId { get; set; }
public string TermId { get; set; }
public string Text { get; set; }
public ICollection<ContractTerm> ContractNavigation { get; set; }
}
I've also got a class to map the section/subsection pairings in a more EF-friendly way (IRL this is an enum with attribute values and a helper, but this class abstracts away some work not necessary to reproduce the issue):
public class Section
{
public string Name { get; set; }
public string SectionId { get; set; }
public string SubsectionId { get; set; }
}
Both ContractTerm and TermItem have their own collections in a DbContext, and I'm trying to get a collection of all text entries assigned to specific Sections for a given ContractId. I have the following class to contain it:
public class TextsBySection
{
public string SectionName { get; set; }
public IEnumerable<string> Texts { get; set; }
}
I want to select a collection of TextsBySection, and have something like this:
public class ContractManager
{
//insert constructor initializing MyContext here
private MyContext Context { get; }
public IEnumerable<MyOutputClass> GetTerms(int contractId, IEnumerable<Section> sections)
{
Func<string, string, IEnumerable<string>> getBySection =
(section, subsection) => context.ContractTerms.Include(x => x.TermNavigation)
.Where(x => x.ContractId == contractId
&& x.SectionId == section
&& x.SubsectionId == subsection)
.Select(x => x.TermNavigation.Text);
var result = sections.Select(x => new MyOutputClass
{
SectionName = x.Name,
Texts = getBySection(x.SectionId, x.SubsectionId)
}).ToList();
return result;
}
}
This works fine and dandy, but it hits the database for every Section. I feel like there's got to be a way to use Join and/or GroupBy to make it only query once, but I can't quite see it. Something like this, perhaps:
var result = context.ContractTerms.Include(x => x.TermNavigation)
.Where(x => x.ContractId == contractId)
.Join(sections,
term => //something
section => //something
(term, section) => /*something*/)
If all this were in SQL, selecting the necessary data would be easy:
SELECT sections.name,
term_items.text
FROM contract_terms
JOIN term_items
ON term_items.section_id = contract_terms.section_id
AND term_items.subsection_id = contract_terms.subsection_id
AND term_items.term_id = contract_terms.term_id
JOIN sections --not a real table; just corresponds to sections argument in method
ON sections.section_id = contract_terms.section_id
AND sections.subsection_id = contract_terms.subsection_id
...and then I could group the results in .NET. But I don't understand how to make a single LINQ query that would do the same thing.
I changed my answer, well I would do something like this... maybe this may help you.
public static void Main(string[] args)
{
List<Section> sections = new List<Section>();
List<ContractTerm> contractTerms = new List<ContractTerm>();
List<TermItem> termItens = new List<TermItem>();
//considering lists have records
List<TextsBySection> result = (from contractTerm in contractTerms
join termItem in termItens
on new
{
contractTerm.SectionId,
contractTerm.SubsectionId,
contractTerm.TermId
}
equals new
{
termItem.SectionId,
termItem.SubsectionId,
termItem.TermId
}
join section in sections
on new
{
contractTerm.SectionId,
contractTerm.SubsectionId
} equals new
{
section.SectionId,
section.SubsectionId
}
select
new
{
sectionName = section.Name,
termItemText = termItem.Text
}).GroupBy(x => x.sectionName).Select(x => new TextsBySection()
{
SectionName = x.Key,
Texts = x.Select(i=> i.termItemText)
}).ToList();
}

LINQ - append MemberBinding expression into exist MemberInit expression

Basic idea is similar to Merging Expression Trees to Reuse in Linq Queries.
In my situation, I have two models and DTOs:
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public Extra Extra { get; set; }
}
public class Extra
{
public int Id { get; set; }
public string Text { get; set; }
}
public class UserDto
{
public int Id { get; set; }
public string Name { get; set; }
public ExtraDto Extra { get; set; }
}
public class ExtraDto
{
public int Id { get; set; }
public string Text { get; set; }
}
and expressions:
Expression<Func<Extra, ExtraDto>> extraSelector = o => new ExtraDto
{
Id = o.Id,
Text = o.Text
};
Expression<Func<User, UserDto>> userSelector = o => new UserDto
{
Id = o.Id,
Name = o.Name
};
Now, I'd like to 'append' extraSelector into userSelector. The pseudo code is like:
var selectorExpression = userSelector.Append(user => user.Extra, extraSelector);
Context.Users.Select(selectorExpression).ToList();
The final expression would be like this:
Expression<Func<User, UserDto>> userSelector = o => new UserDto
{
Id = o.Id,
Name = o.Name,
Extra = new ExtraDto
{
Id = o.Extra.Id,
Text = o.Extra.Text
}
};
I've tried using ExpressionVisitor, but no luck.
Apart from the "merge" of the two selectors, you have to insert the "path" o => o.Extra into the extraSelector and create a new "bind expression" for the property Extra of UserDto.
In fact, i'm playing around with such scenarios within this project, where i've tried to abstract this kind of expression plumbing. Your "merge" would then look like that:
userSelector = extraSelector.Translate()
.Cross<User>(o => o.Extra)
.Apply(o => o.Extra, userSelector);
The Translate extension method is just a little helper to make use of type inference, Cross inserts o => o.Extra into the extraSelector, Apply creates the "bind expression" for the property Extra of UserDto and finally merges the result with userSelector.

How can I compare two classes base their methods?

I want to find out the missing properties in one class by comparing the other class
public class User
{
public int UserID { get; set; }
public string UserName { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class UserDTO
{
public string UserName { get; set; }
public string FirstName { get; set; }
}
Above I should get the output like "UserID, "LastName" properties are missing in UserDTO.
var list = typeof(User).GetProperties().Select(x => x.Name)
.Except(typeof(UserDTO).GetProperties().Select(y => y.Name))
.ToList();
EDIT
Including suggestions in comments and public Fields
public static IEnumerable<string> Diff(Type t1, Type t2)
{
return t1.GetProperties().Select(p1 => new { Name = p1.Name, Type = p1.PropertyType })
.Concat(t1.GetFields().Select(f1 => new { Name = f1.Name, Type = f1.FieldType }))
.Except(t2.GetProperties().Select(p2 => new { Name = p2.Name, Type = p2.PropertyType })
.Concat(t2.GetFields().Select(f2 => new { Name = f2.Name, Type = f2.FieldType })))
.Select(a => a.Name);
}
Use reflection to get the properties, see Type.GetProperties. Then compare both property lists to find the missing ones.
var UserProperties = typeof(User).GetProperties().Select(p => p.Name);
var UserDTOProperties = typeof(UserDTO).GetProperties().Select(p => p.Name);
var missingProperties = UserProperties.Except(UserDTOProperties);
Take into account that all inherited properties will also be present in these lists, unless yous specify BindingFlags.DeclaredOnly to the GetProperties() method, see BindingFlags.

Categories