Entity Framework: Query is slow - c#

I am having some problems with Entity Framework.
I have simplified this to make it easier to explain.
These are my mssql tables
I use the following code to get all cities for each of the countries in my MSSQL database
var country = new Country()
{
Cities = obj.Counties.SelectMany(e => e.Cities).Select(city => new DCCity
{
Name = city.Name,
Population = city.Population
})
};
This is returned as json
There is a bit more then 40.000 records in the city table. To retrieve a list with all the countries and their respective cities it takes around 8 seconds. I am trying to reduce this. Anyone know some optimization tips to achieve this?

You need to query the Cities table first to get all data:
var cities = _context.Cities.Select(x => new {
ContryId = x.County.Country.CountryId,
ContryName = x.County.Country.Name,
CityId = x.Id,
CityName = x.Name
});
var countryLookup = new Dictionary<int, CountryDto>(approximatelyCountOfCountries);
foreach (var city in cities)
{
CountryDto country;
if (!countryLookup.TryGetValue(city.CountryId, out country))
{
country = new CountryDto {
Name = city.CountryName,
Id = city.CountryId
Cities = new List<CityDto>(approximatelyCountOfCities)
};
countryLookup.Add(country.Id, country);
}
country.Cities.Add(new CityDto { Name = city.Name, Id = city.Id });
}
In this way the result will be the:
countryLookup.Values

Try to do somthing like this:
var result = from c in countries
join conty in counties on c.id equals conty.CountryId
join city in cities on conty.id equals city.CountyId
group city by c.Name into g
select new
{
Name = g.Key,
Cities = g.Select(x =>
new
{
x.Name,
x.Population
})
};

Related

LINQ: Grouping SubGroup

How to group SubGroup to create list of Continents where each Continent has it own counties and each country has its own cities like this table
Here is the t-sql:
select Continent.ContinentName, Country.CountryName, City.CityName
from Continent
left join Country
on Continent.ContinentId = Country.ContinentId
left join City
on Country.CountryId = City.CountryId
and the result of t-sql:
I tried this but it groups the data in wrong way i need to group exactly like the above table
var Result = MyRepository.GetList<GetAllCountriesAndCities>("EXEC sp_GetAllCountriesAndCities");
List<Continent> List = new List<Continent>();
var GroupedCountries = (from con in Result
group new
{
con.CityName,
}
by new
{
con.ContinentName,
con.CountryName
}
).ToList();
List<Continent> List = GroupedCountries.Select(c => new Continent()
{
ContinentName = c.Key.ContinentName,
Countries = c.Select(w => new Country()
{
CountryName = c.Key.CountryName,
Cities = c.Select(ww => new City()
{
CityName = ww.CityName
}
).ToList()
}).ToList()
}).ToList();
You need to group everything by continent, these by country and the countries by city:
List<Continent> List = MyRepository.GetList<GetAllCountriesAndCities>("EXEC sp_GetAllCountriesAndCities")
.GroupBy(x => x.ContinentName)
.Select(g => new Continent
{
ContinentName = g.Key,
Countries = g.GroupBy(x => x.CountryName)
.Select(cg => new Country
{
CountryName = cg.Key,
Cities = cg.GroupBy(x => x.CityName)
.Select(cityG => new City { CityName = cityG.Key })
.ToList()
})
.ToList()
})
.ToList();
You should apply grouping twice
var grouped = Result
.GroupBy(x => x.CountryName)
.GroupBy(x => x.First().ContinentName);
var final = grouped.Select(g1 => new Continent
{
ContinentName = g1.Key,
Countries = g1.Select(g2 => new Country
{
CountryName = g2.Key,
Cities = g2.Select(x => new City { CityName = x.CityName }).ToList()
}).ToList()
});
I know this is old, but I wanted to mention a much easier way per microsoft that is a bit more readable. This is an example with only 2 levels though but it will most likely work for others who reach this page (like me)
var queryNestedGroups =
from con in continents
group con by con.ContinentName into newGroup1
from newGroup2 in
(from con in newGroup1
group con by con.CountryName)
group newGroup2 by newGroup1.Key;
The documentation for that is at
https://learn.microsoft.com/en-us/dotnet/csharp/linq/create-a-nested-group
and is using a Student object as an example
I do want to mention that the method they use for printing is harder than just creating a quick print function on your continent object

Creating table which changes a column in order to remove repeated values in rows

Sorry if the title was confusing.
Currently I am practicing with Entity Framework and LINQ expressions and got stuck on this.
I have a table with columns:"Personal ID", "Name", "Surname", "Phone ID" and all values on Phone ID are unique. I would like to create another table in which there would be same columns except for last being "Phone Count" which shows how many phones are associated with same Person(Personal ID). I want the table to show only 3 first highest count rows.
Here is the code i've wrote to make table that i've described above.
using (var db = new PRDatabaseEntities())
{
var query = from a in db.Person
join t in db.Repairs on a.PID equals t.Repairman_PID
orderby a.PID
select new
{
a.PID,
a.Name,
a.Surname,
t.Phone_ID
};
}
You could try with following group by LINQ query:
// First, generate a linq query
var query = from a in Persons
join t in Repairs on a.PID equals t.Repairman_PID
group new { a, t } by new { a.PID, a.Name, a.Surname } into g
select new
{
PID = g.Key.PID,
Name = g.Key.Name,
Surname = g.Key.Surname,
PhoneCount = g.Count()
};
// Then order by PhoneCount descending and take top 3 items
var list = query.OrderByDescending(t => t.PhoneCount).Take(3).ToList();
Try this:
using (var db = new PRDatabaseEntities())
{
var query = db.Person
.GroupJoin(db.Repairs,
p => p.PID, r => r.PID,
(p, r) => new {
PID = p.PID,
Name = p.Name,
Surname = p.Surname,
Count = r.Count()
};
}

Linq query for group by multiple item

I have a linq query which is working fine.How can i use group by in this query.I need to group by username and itemid and i should get sum(Amount)(All are in table called Carts)
FoodContext db = new FoodContext();
List<CartListing> fd = (from e in db.FoodItems
join o in db.Carts on e.itemid equals o.itemid
where e.itemid == o.itemid
select new CartListing
{
Itemname =e.itemname,
Amount =o.amount,
Price=(float)(e.price*o.amount),
}).ToList();
CartModel vm = new CartModel { CartListings = fd };
I can't see username anywhere in your code example, but to group by Itemname and sum Amount, you would something like:
var grouped = fd.GroupBy(
cl => cl.Itemname,
(key, group) => new CartListing
{
Itemname = key,
Amount = group.Sum(cl => cl.Amount),
Price = group.Sum(cl => cl.Price)
});
To also group by username, just generate a text key containing both values, for instance delimited by a character you know will be contained in neither.
Use:
var grouped = fd.GroupBy((a => new { a.itemid,a.name }) into grp
select new MyClass
{
MyProperty1=grp.key.itemid,
MyProperty2 =grp.Sum(x=>x.whatever)
}
Public MyClass
{
public string MyProperty1 {get;set;}
public int MyProperty2 {get;set;}
}
This way it won't be anonymous

conditions in linq query

I have a linq query that gets all data from customers and customer contacts.
But sometimes i don't want all contacts so i would like to specify a condition that if this value equals get contacts then run the query. Similar too..
switch (options)
{
case CustomerOptions.DefaultContacts:
break;
}
I currently have this linq query
var customersToReturn = new ContentList<CustomerServiceModel>()
{
Total = customers.Total,
List = customers.List.Select(c => new CustomerServiceModel
{
Id = c.Id,
ContractorId = c.ContractorId,
CompanyName = c.CompanyName,
Active = c.Active,
Address = new Address
{
Address1 = c.Address1,
Address2 = c.Address2,
Address3 = c.Address3,
Address4 = c.Address4,
},
CustomerContacts = c.CustomersContacts.Select(a => new ContactServiceModel
{
Name = a.Name,
Telephone = a.Telephone
}).Where(e => e.IsDefault)
}).ToList()
};
Is there a way I can set a condition or do I need to repeat the process twice one just for customers and one for customers and customer contacts?
If I understand it right, you want some of CustomServiceModel objects to have CustomerContacts, while others to have not? Then I'd do it like that
List = customers.List.Select(c => new CustomerServiceModel
{
Id = c.Id,
ContractorId = c.ContractorId,
CompanyName = c.CompanyName,
Active = c.Active,
Address = new Address
{
Address1 = c.Address1,
Address2 = c.Address2,
Address3 = c.Address3,
Address4 = c.Address4,
},
CustomerContacts = condition ?
c.CustomersContacts.Select(a => new ContactServiceModel
{
Name = a.Name,
Telephone = a.Telephone
}).Where(e => e.IsDefault)
:null
}).ToList()
If you need to use switch, create yourself a method that returns bool and put it instead of condition phrase in above example.

Remove items from list<a> AND list<b> when a.Foo == b.Bar

I'm looking for an elegant way of comparing 2 different List<> collections and removing the items in which a specific field value matches. For example:
Customer object
class Customer
{
string CustomerName;
string Email;
}
class Employee
{
string EmployeeName;
string EmployeeID;
}
List<Customer> Customers = new List<Customers(GetCustomers());
List<Employee> Employees = new List<Employees(GetEmployees());
And then I know I can do this to remove from the Employees list the Employees that are in the Customers list by doing like this:
Employees.RemoveAll(e => Customers.Any(c.CustomerName == e.EmployeeName));
What I would like to know is how I can also remove from the Customers list the corresponding Employee? Is it possible to do with a simple statement or do I need to build something out to handle this?
what about:
Employees.Where(e => Customers.Any(c=> c.CustomerName == e.EmployeeName)).ToList()
.ForEach(e=> {
Employees.Remove(e);
Customers.RemoveAll(c => c.CustomerName == e.EmployeeName);
});
Create one list of all customers to be deleted and another of all employees to be deleted first. Then delete all these from your original lists. I am not firm enough with lambda to write this out from memory, but it should be doable.
lets try:
List<Customer> c = new List<Customer>();
c.Add(new Customer() {CustomerName = "test", Email = "email" });
c.Add(new Customer() { CustomerName = "test1", Email = "email1" });
c.Add(new Customer() { CustomerName = "test2", Email = "email2" });
c.Add(new Customer() { CustomerName = "test3", Email = "email3" });
List<Employee> e = new List<Employee>();
e.Add(new Employee() { EmployeeName = "test2", EmployeeID = "1" });
e.Add(new Employee() { EmployeeName = "test1", EmployeeID = "2" });
e.Add(new Employee() { EmployeeName = "test5", EmployeeID = "3" });
e.Add(new Employee() { EmployeeName = "test4", EmployeeID = "4" });
//remove from the Customers list the corresponding Employee
e.RemoveAll(x => c.Any(y => y.CustomerName == x.EmployeeName));
foreach (var employee in e)
{
//TODO:
}
var inBoth = customers.Join(employees, customer => customer.CustomerName, employee => employee.EmployeeName, (customer, employee) => employee.EmployeeName).ToList();
customers.RemoveAll(customer => inBoth.Contains(customer.CustomerName));
employees.RemoveAll(employee => inBoth.Contains(employee.EmployeeName));
It can be done like this:
var toBeDeleted = employees.Select(e => e.EmployeeName)
.Where(name => customers.Any(c => c.CustomerName == name))
.ToList();
customers.RemoveAll(c => toBeDeleted.Contains(c.CustomerName));
employees.RemoveAll(e => toBeDeleted.Contains(e.EmployeeName));
You might be able to use Intersect instead to get the elements that should be deleted. I am unaware of any one line command that can do this.

Categories

Resources