Grouping, adding up then sorting with linq - c#

I have 3 tables Drivers, Races and DriverRaces.
public partial class Driver
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Driver()
{
this.DriverRaces = new HashSet<DriverRace>();
}
public int DriverID { get; set; }
[DisplayName("Name")]
public string DriverName { get; set; }
[DisplayName("Number")]
public string DriverNumber { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<DriverRace> DriverRaces { get; set; }
}
public partial class DriverRace
{
public int DriverRaceID { get; set; }
public int Points { get; set; }
[DisplayName("Driver")]
public Nullable<int> Driver_DriverID { get; set; }
[DisplayName("Race")]
public Nullable<int> Race_RaceID { get; set; }
public virtual Driver Driver { get; set; }
public virtual Race Race { get; set; }
}
public partial class Race
{
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
public Race()
{
this.DriverRaces = new HashSet<DriverRace>();
}
public int RaceID { get; set; }
public System.DateTime RaceDate { get; set; }
[DisplayName("Name")]
public string RaceName { get; set; }
[DisplayName("Class")]
public string RaceClass { get; set; }
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly")]
public virtual ICollection<DriverRace> DriverRaces { get; set; }
}
There are 5 classes of races, a driver can be in more than one class, and can compete in multiple races. I am trying to write a linq statement to get the top 5 drivers of each class, and the sum of their total points from each race in that class.
I know how to select all rookie drivers and get their names and points
var drivers = DriverRaces.Where(d=>d.Points > 0 && d.Race.RaceClass == "Rookie");
foreach (var driver in drivers){
Console.WriteLine("Driver Name is {0} points is {1}", driver.Driver.DriverName, driver.Points);
}
Which could return something like
Driver Name is Alexander points is 100
Driver Name is Mady points is 99
Driver Name is Blake points is 98
Driver Name is Alexander points is 100
Driver Name is Mady points is 99
Driver Name is Tom points is 98
But I want it to show
Driver Name is Alexander points is 200
Driver Name is Mady points is 198
Driver Name is Blake points is 98
Driver Name is Tom points is 98
I need help adding up each drivers totals and then sorting them.

To achieve the goal you will need to aggregate these results. There are two approaches one uses Aggregate other Select. Aggregate is more performant while Select is cleaner.
I've added a small example to indicate behavior:
class Program
{
static void Main(string[] args)
{
List<Driver> driversFirstTable = new List<Driver>()
{
new Driver
{
DriverName = "John", DriverRaces = new Collection<DriverRace>
{
new DriverRace { Points = 99, Category = "Rookie" },
new DriverRace { Points = 100, Category = "Rookie" },
new DriverRace { Points = 10, Category = "Mid" },
new DriverRace { Points = 99, Category = "Pro" },
}
},
new Driver
{
DriverName = "Jack", DriverRaces = new Collection<DriverRace>
{
new DriverRace { Points = 100, Category = "Rookie" },
new DriverRace { Points = 98, Category = "Rookie" },
new DriverRace { Points = 66, Category = "Mid" },
new DriverRace { Points = 100, Category = "Pro" },
}
},
new Driver
{
DriverName = "Richard", DriverRaces = new Collection<DriverRace>
{
new DriverRace { Points = 98, Category = "Rookie" },
new DriverRace { Points = 99, Category = "Rookie" },
new DriverRace { Points = 62, Category = "Mid" },
new DriverRace { Points = 98, Category = "Pro" },
}
},
new Driver
{
DriverName = "Will", DriverRaces = new Collection<DriverRace>
{
new DriverRace { Points = 97, Category = "Rookie" },
new DriverRace { Points = 97, Category = "Rookie" },
new DriverRace { Points = 61, Category = "Mid" },
new DriverRace { Points = 97, Category = "Pro" },
}
}
};
var rookieTop = driversFirstTable
.Select(x => new DriverAggregated { Category = "Rookie", DriverName = x.DriverName, TotalPoints = x.DriverRaces.Where(y => y.Category == "Rookie").Sum(y => y.Points) })
.OrderByDescending(x => x.TotalPoints)
.Take(3);
// Will is not in the list
foreach (var driver in rookieTop)
{
Console.WriteLine($"Driver - {driver.DriverName} gathered {driver.TotalPoints} points.");
}
Console.Read();
}
}
class DriverAggregated
{
public string DriverName { get; set; }
public int TotalPoints { get; set; }
public string Category { get; set; }
}
public class Driver
{
public string DriverName { get; set; }
public virtual ICollection<DriverRace> DriverRaces { get; set; }
}
public class DriverRace
{
public int Points { get; set; }
public string Category { get; set; }
}
EDIT:
Another approach could be to use Aggregate function not as pretty but more performant:
var topRookie = driversFirstTable.Select(x => x.DriverRaces.Aggregate(new DriverAggregated() { Category = "Rookie", DriverName = x.DriverName }, (seed, race) =>
{
if (race.Category == seed.Category)
seed.TotalPoints += race.Points;
return seed;
}))
.OrderByDescending(x => x.TotalPoints)
.Take(3);

Related

Strange Entity Framework behavior

I have 2 models:
public class Office
{
[Key] public int OfficeId { get; set; }
public string Brand { get; set; }
public string Type { get; set; }
[NotMapped] public IRepository<CarItem> CarsDataBase { get; set; }
public virtual ICollection<CarItem> Cars { get; set; }
public Office(string brand, string type)
{
Type = type;
Cars = new List<CarItem>();
Brand = brand;
}
}
and
public class CarItem
{
public CarItem() { } //for serialization
public CarItem(string brand, string model, uint price, uint stockBalance)
{
Brand = brand;
Model = model;
Price = price;
StockBalance = stockBalance;
}
[Key] public int ItemId { get; set; }
public string Brand { get; set; }
public string Model { get; set; }
public uint Price { get; set; }
public uint StockBalance { get; set; }
public int? OfficeId { get; set; }
public Office Office { get; set; }
}
and DataBase Context
public class EFDataBaseContext : DbContext
{
public DbSet<Office> Offices => Set<Office>();
public DbSet<CarItem> CarItems => Set<CarItem>();
public EFDataBaseContext()
{
Database.EnsureCreated();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=carstoredb;Trusted_Connection=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Office>().HasData
(
new Office("Audi", "SQL")
{
OfficeId = 1,
},
new Office("Scoda", "SQL")
{
OfficeId = 2,
}
);
modelBuilder.Entity<CarItem>(entity =>
{
entity.HasOne(item => item.Office).WithMany(office => office.Cars).HasForeignKey(item => item.OfficeId).OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<SparePart>(entity =>
{
entity.HasOne(item => item.Office).WithMany(office => office.Parts).HasForeignKey(item => item.OfficeId).OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity<CarItem>().HasData
(
new CarItem { OfficeId = 1, ItemId = 1, Brand = "Audi", Model = "A228", Price = 4, StockBalance = 14 },
new CarItem { OfficeId = 1, ItemId = 2, Brand = "Audi", Model = "Super", Price = 44, StockBalance = 5 },
new CarItem { OfficeId = 2, ItemId = 3, Brand = "Scoda", Model = "Logan", Price = 47, StockBalance = 9 },
new CarItem { OfficeId = 2, ItemId = 4, Brand = "Scoda", Model = "Spider", Price = 78, StockBalance = 3 }
);
}
Manually I added an office called BSU.
And in main function I write this:
using (EFDataBaseContext db = new EFDataBaseContext())
{
Office office = mainOffice.Dealerships.FirstOrDefault(of => of.Brand == "BSU");
CarItem carItem = new CarItem(office.Brand, "BSss", 2222, 4);
office.CarsDataBase ??= new EFDataBase(office);
office.CarsDataBase.Create(carItem);
}
Adding a new CarItem to the BSU somehow magically creates a new office named BSU in my database every time a new CarItem is added to the BSU.
using (EFDataBaseContext db = new EFDataBaseContext())
{
Office office = mainOffice.Dealerships.FirstOrDefault(of => of.Brand == "Audi");
CarItem carItem = new CarItem(office.Brand, "AuuuU", 2222, 4);
office.CarsDataBase ??= new EFDataBase(office);
office.CarsDataBase.Create(carItem);
}
Adding a new CarItem to an Audi, on the other hand, does absolutely nothing. No new cars with the Audi brand appear in the database, and nothing at all.
Seems like you're over complicating things
Adding a new Car to an existing Office should perhaps looks like:
var ctx = new EFDatabaseContext();
var off = ctx.Offices.FirstOrDefault(o => o.Type == "Main" and o.Brand == "Audi");
off.Cars.Add(new CarItem(off.Brand, "AuuuU", 2222, 4));
ctx.SaveChanges();
Find the office, add a car, save the changes

Use LINQ to join 2 objects for each of its properties

I have created 2 models to store the results of an sql query. Now I would like to join them for each of the week's... (week1 = Record_id, week2 = Record_id)
to get a new Object in which I would have all the data from the 1st model, as well as map data from the "Category" Model to it.
I created a new Model for it, but I am not sure how to write a linq query
First Model:
public class CustomData
{
public string full_name { get; set; }
public string location { get; set; }
public int week1 { get; set; }
public int week2 { get; set; }
public int week3 { get; set; }
}
Second Model:
public class Category
{
public int Record_ID { get; set; }
public int Color{ get; set; }
public string Name { get; set; }
}
New Model for end result:
public class WeekView
{
public string full_name { get; set; }
public string location { get; set; }
public Category week1 { get; set; }
public Category week2 { get; set; }
public Category week3 { get; set; }
}
This should work:
List<CustomData> list = new List<CustomData>();
list.Add(new CustomData() { full_name = "test", location = "test", week1 = 0, week2 = 1, week3 = 2 });
list.Add(new CustomData() { full_name = "test2", location = "test2", week1 = 0, week2 = 12, week3 = 22 });
List<Category> categories = new List<Category>();
categories.Add(new Category { Color = 0, Name = "testName", Record_ID = 0 });
categories.Add(new Category { Color = 1, Name = "testName1", Record_ID = 1 });
categories.Add(new Category { Color = 2, Name = "testName2", Record_ID = 2 });
categories.Add(new Category { Color = 3, Name = "testName3", Record_ID = 12 });
categories.Add(new Category { Color = 4, Name = "testName4", Record_ID = 22 });
List<WeekView> results = new List<WeekView>();
results.AddRange(list.Select(x=>
new WeekView() { full_name = x.full_name,
location = x.location,
week1 = categories.FirstOrDefault(c => c.Record_ID == x.week1),
week2 = categories.FirstOrDefault(c => c.Record_ID == x.week2),
week3 = categories.FirstOrDefault(c => c.Record_ID == x.week3)
}));
Try out the following:
var result = (from cd in CustomDatas
join ca1 in Categories on cd.week1 equals ca.Record_ID into ca1r
from ca1 in ca1r.DefaultIfEmpty()
join ca2 in Categories on cd.week2 equals ca.Record_ID into ca2r
from ca2 in ca2r.DefaultIfEmpty()
join ca3 in Categories on cd.week3 equals ca.Record_ID into ca3r
from ca3 in ca3r.DefaultIfEmpty()
select new {
full_name = cd.full_name,
location = cd.location,
week1 = ca1,
week2 = ca2,
week3 = ca3
}

Unable to deserialize JSON array

Below is my class :
public class Employee : Base
{
public int Id { get; set; }
public string Fname { get; set; }
public DepartmentModel Department { get; set; }
}
public class DepartmentModel : Base
{
public int Id { get; set; }
public string DepartmentName { get; set; }
public List<Location> Locations { get; set; }
}
public class Locations
{
public string Area { get; set; }
public string StreetNo { get; set; }
public string Nearby { get; set; }
}
Response return from service:
var response = new
{
id = 100,
department = new
{
id = 200,
departmentName = "Abc",
locations = new[]
{
Employee.Department.Locations
.Select
(
lo => new
{
area = lo.Area,
streetNo = lo.streetNo,
nearby = lo.Nearby
}
).ToList()
}
}
};
return JsonConvert.SerializeObject(response);
Now when I try to deserialize this above JSON into my class Employee like below:
var deserialize = JsonConvert.DeserializeObject<Employee>(response.ToString());
Error:
How can I deserialize this above JSON?
The problem lies here:
locations = new[]
{
Employee.Department.Locations
.Select
(
lo => new
{
area = lo.Area,
streetNo = lo.streetNo,
nearby = lo.Nearby
}
).ToList()
}
The LINQ expression ends with .ToList() and thus is already returning a list of items. You are then wrapping that with new[] in an array. So, instead of being an array of Locations, the JSON is an array of an array of Locations.
Try removing the new[]. You don't want locations to be an array of lists
locations = Employee.Department.Locations
.Select(lo => new
{
area = lo.Area,
streetNo = lo.streetNo,
nearby = lo.Nearby
}
).ToList()
You need to instantiate a new Employee() and use the same casing as the classes:
var response = new Employee() // Instantiates Employee to ensure correct properties used.
{
Id = 100, // Has PascalCase.
Department = new DepartmentModel()
{
Id = 200,
DepartmentName = "Abc",
Locations = Employee.Department.Locations
.Select(lo => new Location
{
Area = lo.Area,
StreetNo = lo.StreetNo,
Nearby = lo.Nearby
}
).ToList()
}
};

Entity Framework Seed method not running

I'm having some trouble getting the Seed method of EF to run. I've run update-database in the PMC - but no effect on the DB. Here's the method:
public class PhilosopherInitialiser : System.Data.Entity.DropCreateDatabaseIfModelChanges<PhilosopherContext>
{
protected override void Seed(PhilosopherContext context)
{
var philosophers = new List<Philosopher>{
new Philosopher {
FirstName = "Bertrand",
LastName = "Russell",
DateOfBirth = DateTime.Parse("1872-05-18"),
DateOfDeath = DateTime.Parse("1970-02-02"),
IsAlive = false,
NationalityID = 1,
AreaID = 7,
Description = "Here's some text about Bertrand Russell"
},
new Philosopher {
FirstName = "Immanuel",
LastName = "Kant",
DateOfBirth = DateTime.Parse("1724-04-22"),
DateOfDeath = DateTime.Parse("1804-02-12"),
IsAlive = false,
NationalityID = 3,
AreaID = 1,
Description = "Here's some text about Immanuel Kant"
},
new Philosopher {
FirstName = "John",
LastName = "Rawls",
DateOfBirth = DateTime.Parse("1921-02-21"),
DateOfDeath = DateTime.Parse("2002-11-24"),
IsAlive = false,
NationalityID = 9,
AreaID = 3,
Description = "Here's some text about John Rawls"
}
};
philosophers.ForEach(p => context.Philosophers.Add(p));
context.SaveChanges();
var nationalities = new List<Nationality>
{
new Nationality { Name = "English" },
new Nationality { Name = "Scotish" },
new Nationality { Name = "German" },
new Nationality { Name = "French" },
new Nationality { Name = "Greek" },
new Nationality { Name = "Italian" },
new Nationality { Name = "Spanish" },
new Nationality { Name = "Russian" },
new Nationality { Name = "American" }
};
nationalities.ForEach(n => context.Nationalities.Add(n));
context.SaveChanges();
var areas = new List<Area>{
new Area { Name = "Metaphysics" },
new Area { Name = "Existentialism" },
new Area { Name = "Political philosophy" },
new Area { Name = "Philosophy of the mind" },
new Area { Name = "Aesthetics" },
new Area { Name = "Social philosophy" },
new Area { Name = "Logic" },
new Area { Name = "Moral philosophy" },
new Area { Name = "Epistemology" }
};
areas.ForEach(a => context.Areas.Add(a));
context.SaveChanges();
var books = new List<Book>
{
new Book {
Title = "The impact of science on society",
PhilosopherID = 1,
AreaID = 6
},
new Book {
Title = "The analysis of mind",
PhilosopherID = 1,
AreaID = 4
},
new Book {
Title = "Marriage and morals",
PhilosopherID = 1,
AreaID = 8
},
new Book{
Title = "Critique of pure reason",
PhilosopherID = 2,
AreaID = 9
},
new Book{
Title = "The metaphysics of morals",
PhilosopherID = 2,
AreaID = 8
},
new Book{
Title = "A theory of justice",
PhilosopherID = 3,
AreaID = 3
}
};
books.ForEach(b => context.Books.Add(b));
context.SaveChanges();
}
}
}
And here's my PhilosopherContext class:
public class PhilosopherContext : DbContext
{
public PhilosopherContext() : base("PhilosopherContext")
{
}
public DbSet<Philosopher> Philosophers { get; set; }
public DbSet<Area> Areas { get; set; }
public DbSet<Nationality> Nationalities { get; set; }
public DbSet<Book> Books { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Book>()
.HasRequired(p => p.Philosopher)
.WithMany()
.HasForeignKey(p => p.PhilosopherID)
.WillCascadeOnDelete(false);
modelBuilder.Entity<Philosopher>()
.Property(p => p.DateOfBirth)
.HasColumnType("datetime2");
modelBuilder.Entity<Philosopher>()
.Property(p => p.DateOfDeath)
.HasColumnType("datetime2");
}
}
}
Inside the Web.Config file I'm using initialising the DB here:
<contexts>
<context type="PhilosophersLibrary.DAL.PhilosopherContext, PhilosophersLibrary">
<databaseInitializer type="PhilosophersLibrary.DAL.PhilosopherInitialiser, PhilosophersLibrary" />
</context>
</contexts>
Does anyone have any suggestions? I feel that the method might not be called.
UPDATE
I seem to be making progress. The Areas and Nationalities tables are being seeded with the data. But I have to comment out the Philosophers data and the Books data. Is there something wrong with my data model?
public class Book
{
public int BookID { get; set; }
public string Title { get; set; }
[Display(Name = "Philosopher")]
public int PhilosopherID { get; set; }
[Display(Name = "Area")]
public int AreaID { get; set; }
public virtual Philosopher Philosopher { get; set; }
public virtual Area Area { get; set; }
}
public class Philosopher
{
// <className>ID pattern causes property to be primary key
public int PhilosopherID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
[Display(Name = "Date of birth")]
public DateTime DateOfBirth { get; set; }
[Display(Name = "Date of death")]
public DateTime DateOfDeath { get; set; }
public Boolean IsAlive { get; set; }
public string Description { get; set; }
// Foreign keys have corresponding navigation properties
// <NavigationProperty>ID naming convention cause EF to identify foreign keys
public int NationalityID { get; set; }
public int AreaID { get; set; }
// Navigation properties - defined as virtual to use LazyLoading
// Nationality and Area have a 1 to 1 relationship with philosopher
// Books has a 1 to many relationship with philosopher
public virtual Nationality Nationality { get; set; }
public virtual Area Area { get; set; }
public virtual ICollection<Book> Books { get; set; }
}
Try to use this:
CreateDatabaseIfNotExists<PhilosopherContext>
instead of this:
DropCreateDatabaseIfModelChanges<PhilosopherContext>
Also I would add:
public PhilosopherContext() : base("PhilosopherContext")
{
Database.SetInitializer<PhilosopherContext>(new CreateDatabaseIfNotExists<PhilosopherContext>());
}

How to Convert List<T> to BsonArray to save a MongoDB Document

I'm having an Model Class, I need to Save it in a MongoDB Collection.
My Model Class:
public Class Employee
{
public string EmpID { get; set; }
public string EmpName { get; set; }
public List<Mobile> EmpMobile { get; set; }
}
public Class Mobile
{
public string MobID { get; set; }
public string MobNumber { get; set; }
public bool IsPreferred { get; set; }
}
The Values are
Employee EmpInfo = new Employee()
{
EmpID = "100",
EmpName = "John",
EmpMobile = new List<Mobile>()
{
{ MobNumber = "55566610", IsPreferred = true },
{ MobNumber = "55566611", IsPreferred = false },
}
}
BsonDocument _employee = new BsonDocument()
{
{ "Emp_ID", EmpInfo.EmpID },
{ "Emp_Name", EmpInfo.EmpName },
{ "Emp_Mobile", new BsonArray (EmpInfo.EmpMobile.Select(m => new
{
MobID = new ObjectId(),
MobNumber = m.MobNumber,
IsPreferred = m.IsPreferred
})) }
};
var collection = _database.GetCollection<BsonDocument>("EmployeeInfo");
collection.InsertOne(_employee);
I wish to save the above EmpInfo of type Employee in a MongoDB. But I can't able to create a BsonDocument. Kindly assist me is there is anything wrong in the above code. If yes kindly assist me.
there is no need to serialize to bson document
You can use TYPED collection and just insert data
Please see attached code snipet with updated class structure
void Main()
{
// To directly connect to a single MongoDB server
// or use a connection string
var client = new MongoClient("mongodb://localhost:27017");
var database = client.GetDatabase("test");
var collectionEmpInfo = database.GetCollection<Employee>("Employee");
Employee EmpInfo = new Employee
{
EmpID = "100",
EmpName = "John",
EmpMobile = new List<Mobile>
{
new Mobile{ MobNumber = "55566610", IsPreferred = true, MobID = ObjectId.GenerateNewId() },
new Mobile{ MobNumber = "55566611", IsPreferred = false, MobID = ObjectId.GenerateNewId() },
}
};
collectionEmpInfo.InsertOne(EmpInfo);
var empList = collectionEmpInfo.Find(new BsonDocument()).ToList();
empList.Dump(); //dump is used in linqPad
}
public class Employee
{
public ObjectId Id { get; set; }
public string EmpID { get; set; }
public string EmpName { get; set; }
public List<Mobile> EmpMobile { get; set; }
}
public class Mobile
{
public ObjectId MobID { get; set; }
public string MobNumber { get; set; }
public bool IsPreferred { get; set; }
}
In addition to answer above, I can suggest following code if you want to deal directly with Bson for some reason:
BsonDocument _employee = new BsonDocument()
{
{ "Emp_ID", EmpInfo.EmpID },
{ "Emp_Name", EmpInfo.EmpName },
{ "Emp_Mobile", BsonArray.Create(EmpInfo.EmpMobile.Select(m => new BsonDocument()
{
{ "MobID" , new ObjectId() },
{ "MobNumber", m.MobNumber },
{ "IsPreferred", m.IsPreferred }
})) }
};
The reason of the error you've got is that BsonArray.Create creates an array of values, not an array of objects. See this question for details.

Categories

Resources