Group by clause not allowing grouping on class level - c#

I have the following class but I want to use it within a LINQ group expression but I am hitting an error.
namespace importService.Model
{
[DelimitedRecord(",")]
[IgnoreEmptyLines()]
public class Import
{
public int ID { get; set; }
public string ProductType { get; set; }
public Single? Weight { get; set; }
public string ProductName { get; set; }
public string NominalCode { get; set; }
public decimal Costings { get; set; }
public DateTime DateTimeCreated { get; set; }
public string ImportedBy { get; set; }
public string OrderUser { get; set; }
public bool isActive { get; set; }
public int Status { get; set; }
public long OrderNo { get; set; }
}
}
I am trying to use it as following to group them nicely in an email
List<Import> orders = new List<Import>
orders=Filled in from select statment from dapper
orders = orders.GroupBy(g => g.OrderNo).ToList();
However, I am getting the following error
Severity Code Description Project File Line Suppression State
Error CS0029 Cannot implicitly convert type >
System.Collections.Generic.List<System.Linq.IGrouping<long,
ImportService.Model.Import>>' to
'System.Collections.Generic.List<ImportService.Model.Import>'
How do I properly construct the group by query in this case?, I am using dapper to fill my orders element so it's getting filled ok it's just not wanting to allow the group.
Aim
My Aim is so that I can send an email shot based of the order numbers in the class and show a total row at the bottom of the email for each different order number.
It should be one email one email, where each total come after the rows for each set of OrderNo
foreach (var item in orders)
{
using (var tr = table.AddRow(classAttributes: "someattributes"))
{
tr.AddCell(item.OrderNo.ToString(), "style:font-bold;");
tr.AddCell(item.ProductName.ToString(), "style:font-bold;");
if (item.Status == (int)ImportStatus.NominalInvlaid)
{
tr.AddCell("Nominal Code Invlaid");
}
tr.AddCell(item.Costings.ToString(), "style:font-bold;");
tr.AddCell("DB", "style:font-bold;");
}
This should display at the bottom of the lines above which should be group by the order no
using (var tr = table.AddRow(classAttributes: "someattributes"))
{//this should be the total of item.costings a the bottom of the above
tr.AddCell(TotalOrdervalue.ToString(), "style:font-bold;");
}
}

That's because the result of the group by isn't a list of imports, instead it's a collection of lists.
try using the code below and see if it's working
var groupedOrders = orders.GroupBy(g => g.OrderNo).ToList();
an example of for loop would be
foreach (var og in groupedOrders)
{
foreach (var item in og)
{
...your code
}
}

Related

Reading, modifying and writing a CSV with CSVHelper (variable scope with 'using' )

This is a general coding question of how I can share the same list of data between the stages of reading, modifying and writing.
I'm a novice and new to C# too, so I'm struggling. I was expecting to be able to: read the CSV into the variable records, modify one or more of the records and then write out the modified data as a new CSV, but there is a scope issue with the using function.
The code below won't compile because records is out of scope in both the foreach loop that aims to modify some data and again when I'm trying to write the modified file.
I have tried various things to make records a more global variable but they have all failed and I am out of my depth.
I'm not even sure that this is the best way to approach the problem, so any advice would be appreciated.
private void Btn_Merge_Click(object sender, EventArgs e)
{
// Read the CSV into 'records'
StreamReader reader = new StreamReader(textBox_Shopify.Text);
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
var records = csv.GetRecords<Contact>();
}
// We now need to find the record for a specific person and change it
foreach (Contact customer in records)
{
if (customer.Email == "john.dow#business.com") // Hard-coded while testing
{
string Tags = customer.Tags; // Get the current tags
// If the Lead Marking tag is not already there, add it
if (!Tags.Contains("Send me Lead Marketing"))
{
// If there are tags already there, append a semi-colon separator
if (customer.Tags.Length > 0)
{
customer.Tags += ";";
}
customer.Tags += "Send me Lead Marketing";
MessageBox.Show(customer.Email + " Tags: " + customer.Tags); //Just while I'm testing
}
}
// If the customer is not in the list, add them as a new record
// To do...
}
// We can now write out the modified file
using (var writer = new StreamWriter(#"C:\temp\Output.csv"))
using (var outputCSV = new CsvWriter(writer, CultureInfo.InvariantCulture))
{
outputCSV.WriteRecords(records);
}
}
public class Contact
{
[Name("First Name")] // This 'attribute' allows the class property First_Name to be matched to the header "First Name"
public string First_Name { get; set; }
[Name("Last Name")]
public string Last_Name { get; set; }
public string Email { get; set; }
[Name("Accepts Email Marketing")]
public string Accepts_Email_Marketing { get; set; }
public string Company { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string Province { get; set; }
[Name("Province Code")]
public string Province_Code { get; set; }
public string Country { get; set; }
[Name("Country Code")]
public string Country_Code { get; set; }
public string Zip { get; set; }
public string Phone { get; set; }
[Name("Accepts SMS Marketing")]
public string Accepts_SMS_Marketing { get; set; }
[Name("Total Spent")]
public string Total_Spent { get; set; }
[Name("Total Orders")]
public string Total_Orders { get; set; }
public string Tags { get; set; }
public string Note { get; set; }
[Name("Tax Exempt")]
public string Tax_Exempt { get; set; }
}
#Mureinik is halfway there. Since CsvHelper will only yield records as you iterate them, you will also need to call ToList() or in some other way iterate the records within the using statement.
IEnumerable<Contact> records;
// Read the CSV into 'records'
StreamReader reader = new StreamReader(textBox_Shopify.Text);
using (var csv = new CsvReader(reader, CultureInfo.InvariantCulture))
{
records = csv.GetRecords<Contact>().ToList();
}
You could define records in the scope of the entire method:
private void Btn_Merge_Click(object sender, EventArgs e)
{
IEnumerable<Contact> records;
// result of the code...

Get subcollection of MongoDB collection with C# driver based on search

I have this project with https://github.com/Mech0z/Foosball/blob/master/Models/Old/PlayerRankHistory.cs
I have the following classes where PlayerRankHistory is saved in MongoDB, this contains a list of PlayerRankHistorySeasonEntry which each contains PlayerRankHistoryPlot.
I would then like to provide an email of a player and a seasonname and then only get the list PlayerRankHistoryPlots out as a list, but the code I have written is very slow and not faster than just providing only an email and getting much more data out
And as a side note, not sure how to write it to make it async
The query I have now is
public async Task<List<PlayerRankHistoryPlot>> GetPlayerRankEntries(string email, string seasonName)
{
var query = Collection.AsQueryable().SingleOrDefault(x => x.Email == email)
.PlayerRankHistorySeasonEntries.SingleOrDefault(x => x.SeasonName == seasonName).HistoryPlots;
List<PlayerRankHistoryPlot> result = query.ToList();
return result;
}
public class PlayerRankHistory
{
public PlayerRankHistory(string email)
{
Email = email;
PlayerRankHistorySeasonEntries = new List<PlayerRankHistorySeasonEntry>();
}
public Guid Id { get; set; }
public string Email { get; set; }
public List<PlayerRankHistorySeasonEntry> PlayerRankHistorySeasonEntries { get; set; }
}
public class PlayerRankHistorySeasonEntry
{
public PlayerRankHistorySeasonEntry(string seasonName)
{
SeasonName = seasonName;
HistoryPlots = new List<PlayerRankHistoryPlot>();
}
public string SeasonName { get; set; }
public List<PlayerRankHistoryPlot> HistoryPlots { get; set; }
}
public class PlayerRankHistoryPlot
{
public PlayerRankHistoryPlot(DateTime date, int rank, int eloRating)
{
Date = date;
Rank = rank;
EloRating = eloRating;
}
public DateTime Date { get; set; }
public int Rank { get; set; }
public int EloRating { get; set; }
}
An example of a document
{"_id":"AYU3e3Qgw0Gut1fngze80g==","Email":"someemail#gmail.com","PlayerRankHistorySeasonEntries":[{"SeasonName":"Season 1","HistoryPlots":[{"Date":"2020-01-10T12:24:12.511Z","Rank":11,"EloRating":1488},{"Date":"2020-01-13T12:51:41.597Z","Rank":12,"EloRating":1488},{"Date":"2020-01-15T11:11:43.223Z","Rank":10,"EloRating":1510},{"Date":"2020-01-15T11:11:45.049Z","Rank":8,"EloRating":1530},{"Date":"2020-01-15T12:14:58.042Z","Rank":9,"EloRating":1530},{"Date":"2020-01-15T12:14:59.886Z","Rank":8,"EloRating":1530}]}]}
I believe when you define Collection.AsQueryable().FirstOrDefault(), you are pulling all records in that collection and then filtering through them. You should use the Find() method that is provided by MongoDB C# driver to filter the records which is much faster as well.
Get the PlayerRankHistory objects based on the email address
From on the filtered records, only return the records that have the required season
Get the HostoryPlots for only the first match as list
Collection.Find(Builders<PlayerRankHistory>.Filter.Eq(x => x.Email, email))
.Select(y => y.PlayerRankHistorySeasonEntries.Where(z => z.SeasonName.Equals(seasonName)))
.FirstOrDefault()?.HistoryPlots
.ToList();

Iteration error in Entity Framework

I'm creating a simple Messaging application with C# Winforms. I'm connecting to a SQLEXPRESS server running on my computer and storing everything there.
Here is my schema:
public class UserContext : DbContext {
public UserContext() : base("name=BuddyDatabase") {
}
public DbSet<User> Users { get; set; }
public DbSet<Message> Messages { get; set; }
}
public class User {
[Key]
public string username { get; set; }
public string password { get; set; }
public static implicit operator User(bool v) {
throw new NotImplementedException();
}
public virtual List<User> friends { get; set; }
}
public class Message {
[Key]
public int ID { get; set; }
public virtual User sender { get; set; }
public virtual User recipient { get; set; }
public string content { get; set; }
public virtual List<User> group { get; set; }
}
Pretty simple
Sending messages to a single recipient works but group messaging doesn't, whenever I open the "Messages Screen" I get this error:
Unable to create a constant value of type 'WindowsFormsApp1.User'. Only primitive types or enumeration types are supported in this context.
And this is the method I have run when the Message Screen loads up:
var x = db.Messages.Where(b => b.recipient.username == currentuser.username);
foreach (var y in x) {
MainMessagesBox.Text += y.content;
}
x = null;
var z = db.Messages.Where(b => b.group.Contains(currentuser));
foreach (var y in z) {
MainMessagesBox.Text += y.content;
}
(Visual Studio highlights the 'in' in this line as the cause of the error.)
foreach (var y in z) {
Thought this might be a problem with me using the Where method and involving non-primitives (as the error message suggests) so I tried changing my schema so that group is a list of strings that contain the usernames of the intended recipients and modified the methods accordingly but that didn't work either. Will provide code and errors for that trial on request.
Here is the actual "Send Message" code:
if (!textBox1.Text.Contains(',')) {
db.Messages.Add(new Message { sender = currentuser, recipient = db.Users.Find(textBox1.Text), content = currentuser.username + ": " + ContentBox.Text + "\n" });
db.SaveChanges();
} else {
List<User> recips = new List<User>();
string[] poop = textBox1.Text.Split(',');
foreach (var x in poop) {
recips.Add(db.Users.Find(x));
}
db.Messages.Add(new Message { sender = currentuser, group = recips, content = ContentBox.Text });
db.SaveChanges();
}
poop is an array of the intended recipients separated by comma from a textbox, this is temporary.
Sorry if anything is misformatted or I'm unclear, this is my first question.
Thank you in advance.
The problem is the Select query on message table where you check for the current user to be in the group list. This won't work, because the query has to be translated to SQL to be send to the database.
SQL does not understand what a ´User´ type is and can't compare references. And actually, neither does C# compare this properly. Two objects of type ´User´ with the same username would not be equal in your case. You need to compare the username of those objects.
Regardless, as Bitmask explained in a comment, you have to properly define the foreign key for the messages.
It's quite easy for the sender of the messages. You have a public virtual ICollection<Message> Messages { get; set; } in your User class.
But for the recipients, it's a many-to-many relation. So both, the User class and the Message class have a collection to Message and User respectively.
Something like this should work:
public class User {
[Key]
public string username { get; set; }
public string password { get; set; }
public static implicit operator User(bool v) {
throw new NotImplementedException();
}
public virtual ICollection<Message> SentMessages { get; set; }
public virtual ICollection<Message> ReceivedMessages { get; set; }
public virtual List<User> friends { get; set; }
}
public class Message {
[Key]
public int ID { get; set; }
public virtual User Sender { get; set; }
public virtual ICollection<User> Recipients { get; set; }
public string content { get; set; }
}
Check this link for an example on how to define a many-to-many relation either using data annotations or fluent API. This assumes you're using EF6 though. If you're using an older version of EF, you might have to define the joining table yourself to get the many-to-many relation.
And as for the query. You can use the following in your message screen OnLoad method:
MainMessagesBox.Text = string.Join(System.Environment.NewLine, currentuser.ReceivedMessages.Select(m => m.content))
This concatenates all messages, separated with a new line.

Error inserting record with entity framework

I am sorry if it has already been answered but I can't find any solution. Here is my (little) problem. Also all my apologies if the terms I use are approximate, I am far from being a skilled C# developer
Note that I think my problem is similar to this one Entity Framework validation error for missing field, but it's not missing?
I have a table "Tweets" with a tweet_id field (bigint) which is my primary key.
I use the following class to load the table :
class TwitterDbContext : DbContext
{
public TwitterDbContext() : base("Twitter")
{
}
public DbSet<Stream> Streams { get; set; }
public DbSet<StreamParameter> StreamParameters { get; set; }
public DbSet<Tweet> Tweets { get; set; }
}
public class Tweet
{
public Tweet()
{
}
[Key]
public long tweet_id { get; set; }
public string tweet { get; set; }
public long creator { get; set; }
public double latitude { get; set; }
public double longitude { get; set; }
public string language { get; set; }
public DateTime created_at { get; set; }
public DateTime registered_at { get; set; }
public long? in_reply_to { get; set; }
public bool retweeted { get; set; }
}
I have an other class to store within the code execution all the fields used by the Tweet table. For the need here, let's imagine I manually create it that way
private void Test_Button_Click(object sender, RoutedEventArgs e)
{
Twts twtReceived = new Twts();
twtReceived.tweet_id = 1;
twtReceived.tweet = "test";
twtReceived.creator = 1;
twtReceived.latitude = -1;
twtReceived.longitude = -1;
twtReceived.language = "a";
twtReceived.created_at = DateTime.Now;
twtReceived.registered_at = DateTime.Now;
twtReceived.in_reply_to = 1;
twtReceived.retweeted = true;
AddTweet(twtReceived);
}
Now here is the AddTweet method
static public void AddTweet(Twts twtReceived)
{
try
{
// update the tweet data in the database
using (var TwitterDb = new TwitterDbContext())
{
Tweet twt = new Tweet()
{
tweet_id = twtReceived.tweet_id,
tweet = twtReceived.tweet,
creator = twtReceived.creator,
longitude = twtReceived.longitude,
latitude = twtReceived.latitude,
language = twtReceived.language,
created_at = twtReceived.created_at,
registered_at = twtReceived.registered_at,
in_reply_to = twtReceived.in_reply_to,
retweeted = twtReceived.retweeted
};
TwitterDb.Tweets.Add(twt);
TwitterDb.SaveChanges();
}
}
catch(Exception ex)
{
MessageBox.Show(ex.InnerException.ToString());
}
}
I constantly have the same error message:
Cannot insert the value NULL into column 'tweet_id', table
'Twitter.dbo.Tweets'; column does not allow nulls. INSERT fails.
The thing is that when I spy on "TwitterDb.Tweets.Local" after TwitterDb.Tweets.Add(twt); I correctly have tweet_id set to 1.
Any idea where is the issue?
Try marking your tweet_id field with following (instead of just [Key]), if this is a primary key column where you want to provide values yourself
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
If it is an auto-increment, then remove explicit assignments to this field and mark it as 'Identity' instead:
[Required, Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]

List by GroupBy from IEnumerable

I am trying to create a list of Queues that are displayed by Queue Category. Each Queue Category is assigned an Enum value as such.
public enum QueueCategory
{
None=0,
Critical=1,
High=2,
Orphaned=3,
Missing=4
}
And for each Category, I want to then display these fields.
public class QueueInformation
{
public string Name { get; set; }
public Decimal PercentOfThreshold { get; set; }
public string Host { get; set; }
public DateTime OldestArrival { get; set; }
public QueueCategory Category { get; set; }
}
}
How would I go about linking these two pages so that QueueInformation is displayed by QueueCategory?
IEnumerable<QueueInformation> infos = ...;
foreach (var categoryGroup in infos.GroupBy(i => i.Category))
{
Console.WriteLine("Current category: {0}", categoryGroup.Key);
foreach (var queueInfo in categoryGroup)
{
Console.WriteLine(queueInfo.Name /*...*/);
}
Console.WriteLine("==========================");
}
I assume you want a source ordered by the QueueCategory:
IEnumerable<QueueInformation> list = new BindingList<QueueInformation>();
var orderedList = from l in list orderby l.Category select l;
Hope this helps

Categories

Resources