Select 2 elements with linq - c#

I need to select 2 cities in a table with linq:
SOLUTION 1: just one query
var CityQuery = db.Cities.Where(c => c.CityId == City1Id || c.CityId == City2Id).Take(2)
foreach (var item in CityQuery)
{
if (item.CityId == City1Id)
{
City1Name = item.CityName;
}
else
{
City2Name = item.CityName;
}
}
or
SOLUTION 2: execute 2 queries
var City1Query = db.Cities.Where(c => c.CityId == City1Id).FirsOrDefault();
City1Name = City1Query.CityName;
var City2Query = db.Cities.Where(c => c.CityId == City2Id).FirsOrDefault();
City2Name = City2Query.CityName;
Which query is the most efficient ? What is the best practice ?

Generally speaking, your Solution 1 should be faster since it makes a single round-trip to the database. However, whether that difference is significant or not depends on your use case.
Here's an alternative solution that only brings back the city names from the database (vs bringing back all columns). At first look, this might look like an inefficient cartesian product but the db engine will most likely optimize this, especially if there exist an index on the CityId column.
var result = from city1 in CityQuery where city1.CityId == City1Id
from city2 in CityQuery where city2.CityId == City2Id
select new
{
City1Name = c1.CityName,
City2Name = c2.CityName
};
City1Name = result.City1Name;
City2Name = result.City2Name;

Related

C# Dynamic Linq Query Multiple OR Logic

I'm not a C# programmer, nor did I design the database for this, but I've been tasked with creating a Linq query that doesn't know what columns to use at compile time. Trying to be succinct, here's the gist of what I'm trying to do:
From the frontend, the user can select one or more groups and expect back a JSON string containing those users that will fill up a paginated table.
Let's say I have these groups:
Group1
Group2
Group3
Group4
and the user selects Group1, Group2, and Group4. They expect back from the database users that belong to any one of these groups.
In the database, these group names are column names.
If I were to write a straight SQL statement it would look something like this:
SELECT EmailAddress, FirstName, LastName
FROM Contacts AS C
JOIN GroupContacts AS D
ON C.ID = D.ContactId
WHERE D.DealId = 'some unique id'
AND (
D.Group1 = 1
OR
D.Group2 = 1
OR
D.Group4 = 1
)
ORDER BY C.LastName
OFFSET 0 ROWS
FETCH NEXT 10 ROWS ONLY
Where the = 1 means that Contact is in that group.
All I want to be able to do is to make that AND (...) part dynamic, depending on what the user has selected.
I've investigated how to do it with C# Expressions, but the functions I'm coming across (Expression.OrElse()) can only take two parameters.
What is my approach here?
EDIT:
Here is my sad C# code:
String[] GroupNames = { "Group1", "Group2", "Group4" }; // user selected groups
List<Expression> expressionList = new List<Expression>();
foreach (String name in GroupNames)
{
expressionList.Add(Expression.Equal(Expression.Constant(name), 1));
}
Here is where I'm really not sure what to do. I think I could write the rest of the expression if I could just get this expressionList to evaluate.
I know that I will have a finite number of groups. At first I tried writing a switch statement sort of solution:
List<Expression> expressionList = new List<Expression>();
Expression expressionOR = Expression.Empty();
Expression rightSide = Expression.Constant(true, typeof(bool));
foreach (string name in GroupNames)
{
switch (name)
{
case "Group1":
leftSide = Expression.Constant("Group1");
e1 = Expression.Equal(leftSide, rightSide);
break;
case "Group2":
leftSide = Expression.Constant("Group2");
e2 = Expression.Equal(leftSide, rightSide);
break;
case "Group3":
leftSide = Expression.Constant("Group3");
e3 = Expression.Equal(leftSide, rightSide);
break;
case "Group4":
leftSide = Expression.Constant("Group4");
e4 = Expression.Equal(leftSide, rightSide);
break;
}
expressionOR = Expression.Equal(leftSide, rightSide);
expressionList.Add(expressionOR);
}
And then I would try to use Expression.OrElse(expressionList); is some way.
Forgive my naivety on C#, and thanks so far to everybody who has responded. It's really appreciated.
You don't post a lot of code to go on but if you're just trying to use expressions to dynamically add conditions you don't need to. You could do something like:
where DealId == "some unique id"
&& ((GroupNames.Contains("Group1") && Group1 = 1) ||
(GroupNames.Contains("Group2") && Group2 = 1) ||
(GroupNames.Contains("Group3") && Group3 = 1) ||
(GroupNames.Contains("Group4") && Group4 = 1))
Need using System.Linq.Dynamic NuGet relation
Sample:
var gropString = String.Format("new ({0})", GroupDocumentByFields().ToSeparatedString());
var groupping = list1.AsQueryable().GroupBy(gropString, "it");
foreach (IGrouping<dynamic, ITAP.Database.OrderItems> items in groupping) {
var idorders = items.Select(p => p.IDOrder).Distinct().ToList();
var goods = listGoods.Where(p => idorders.Contains(p.IDOrder));
var services = listServices.Where(p => idorders.Contains(p.IDOrder));
...
protected List<string> GroupDocumentItemsByFields() {
var fields = new List<string>();
if (GetNameVariant == Selling.GetNameVariant.OrderItemsName)
fields.Add("Name as ItemName");
if (GetNameVariant == Selling.GetNameVariant.OrderItemsShortName)
fields.Add("ShortName as ItemName");
if (GetNameVariant == Selling.GetNameVariant.SellingName)
fields.Add("SellingName as ItemName");
if(this.PriceVariant == Selling.PriceVariant.PriceWithoutNDS)
fields.Add("PriceWithoutNDS as Price");
if (this.PriceVariant == Selling.PriceVariant.Price)
fields.Add("Price as Price");
return fields;
}

avoid loop to generate nullable collection, increase performance

I've been using Stopwatch and it looks like the below query is very expensive in terms of performance, even though what I already have below I find most optimal based on various reading (change foreach loop with for, use arrays instead of collection, using anonymous type not to take the whole table from DB). Is there a way to make it faster? I need to fill the prices array, which needs to be nullable. I'm not sure if I'm missing something?
public float?[] getPricesOfGivenProducts(string[] lookupProducts)
{
var idsAndPrices = from r in myReadings select
new { ProductId = r.ProductId, Price = r.Price };
float?[] prices = new float?[lookupProducts.Length];
for(int i=0;i<lookupProducts.Length;i++)
{
string id = lookupProducts[i];
if (idsAndPrices.Any(r => r.ProductId == id))
{
prices[i] = idsAndPrices.Where(p => p.ProductId == id)
.Select(a=>a.Price).FirstOrDefault();
}
else
{
prices[i] = null;
}
}
return prices;
}
It's likely every time you call idsAndPrices.Any(r => r.ProductId == id), you are hitting the database, because you haven't materialized the result (.ToList() would somewhat fix it). That's probably the main cause of the bad performance. However, simply loading it all into memory still means you're searching the list for a productID every time (twice per product, in fact).
Use a Dictionary when you're trying to do lookups.
public float?[] getPricesOfGivenProducts(string[] lookupProducts)
{
var idsAndPrices = myReadings.ToDictionary(r => r.ProductId, r => r.Price);
float?[] prices = new float?[lookupProducts.Length];
for (int i = 0; i < lookupProducts.Length; i++)
{
string id = lookupProducts[i];
if (idsAndPrices.ContainsKey(id))
{
prices[i] = idsAndPrices[id];
}
else
{
prices[i] = null;
}
}
return prices;
}
To improve this further, we can identify that we only care about products passed to us in the array. So let's not load the entire database:
var idsAndPrices = myReadings
.Where(r => lookupProducts.Contains(r.ProductId))
.ToDictionary(r => r.ProductId, r => r.Price);
Now, we might want to avoid the 'return null price if we can't find the product' scenario. Perhaps the validity of the product id should be handled elsewhere. In that case, we can make the method a lot simpler (and we won't have to rely on having the array in order, either):
public Dictionary<string, float> getPricesOfGivenProducts(string[] lookupProducts)
{
return myReadings
.Where(r => lookupProducts.Contains(r.ProductId))
.ToDictionary(r => r.ProductId, r => r.Price);
}
And a note unrelated to performance, you should use decimal for money
Assuming that idsAndPrices is an IEnumerable<T>, you should make it's initialization:
var idsAndPrices = (from r in myReadings select
new { ProductId = r.ProductId, Price = r.Price })
.ToList();
It's likely that the calls to:
idsAndPrices.Any(r => r.ProductId == id)
and:
idsAndPrices.Where(p => p.ProductId == id)
..are causing the IEnumerable<T> to be evaluated every time it's called.
Based on
using anonymous type not to take the whole table from DB
I assume myReadings is the database table and
var idsAndPrices =
from r in myReadings
select new { ProductId = r.ProductId, Price = r.Price };
is the database query.
Your implementation is far from optimal (I would rather say quite inefficient) because the above query is executed twice per each element of lookupProducts array - idsAndPrices.Any(...) and idsAndPrices.Where(...) statements.
The optimal way I see is to filter as much as possible the database query, and then use the most efficient LINQ to Objects method for correlating two in memory sequences - join, in your case left outer join:
var dbQuery =
from r in myReadings
where lookupProducts.Contains(r.ProductId)
select new { ProductId = r.ProductId, Price = r.Price };
var query =
from p in lookupProducts
join r in dbQuery on p equals r.ProductId into rGroup
from r in rGroup.DefaultIfEmpty().Take(1)
select r?.Price;
var result = query.ToArray();
The Any and FirstOrDefault are O(n) and redundant. You can get a 50% speed up just by removing theAll call. FirstOrDefault will give you back a null, so use it to get a product object (remove the Select). If you want to really speed it up you should just loop through the products and check if prices[p.ProductId] != null before setting prices[p.ProductId] = p.Price.
bit of extra code code there
var idsAndPrices = (from r in myReadings select
new { ProductId = r.ProductId, Price = r.Price })
.ToList();
for(int i=0;i<lookupProducts.Length;i++)
{
string id = lookupProducts[i];
prices[i] = idsAndPrices.FirstOrDefault(p => p.ProductId == id);
}
better yet
Dictionary<Int, Float?> dp = new Dictionary<Int, Float?>();
foreach(var reading in myReadings)
dp.add(r.ProductId, r.Price);
for(int i=0;i<lookupProducts.Length;i++)
{
string id = lookupProducts[i];
if(dp.Contains(id)
prices[i] = dp[id];
else
prices[i] = null;
}

How to sort a list after AddRange?

new to C#, SQL and Linq. I have two lists, one "dataTransactions" (fuel from gas stations) and a similar one "dataTransfers" (fuel from slip tanks).
They each access a different table from SQL and get combined later.
List<FuelLightTruckDataSource> data = new List<FuelLightTruckDataSource>();
using (SystemContext ctx = new SystemContext())
{
List<FuelLightTruckDataSource> dataTransactions
= ctx.FuelTransaction
.Where(tx => DbFunctions.TruncateTime(tx.DateTime) >= from.Date && DbFunctions.TruncateTime(tx.DateTime) <= to.Date
//&& tx.AssetFilled.AssignedToEmployee.Manager
&& tx.AssetFilled.AssignedToEmployee != null
//&
&& tx.AssetFilled.AssetType.Code == "L"
&& (tx.FuelProductType.FuelProductClass.Code == "GAS" || tx.FuelProductType.FuelProductClass.Code == "DSL"))
.GroupBy(tx => new { tx.AssetFilled, tx.DateTime, tx.FuelProductType.FuelProductClass, tx.FuelCard.FuelVendor, tx.City, tx.Volume, tx.Odometer}) //Added tx.volume to have individual transactions
.Select(g => new FuelLightTruckDataSource()
{
Asset = g.FirstOrDefault().AssetFilled,
Employee = g.FirstOrDefault().AssetFilled.AssignedToEmployee,
ProductClass = g.FirstOrDefault().FuelProductType.FuelProductClass,
Vendor = g.FirstOrDefault().FuelCard.FuelVendor,
FillSource = FuelFillSource.Transaction,
Source = "Fuel Station",
City = g.FirstOrDefault().City.ToUpper(),
Volume = g.FirstOrDefault().Volume,
Distance = g.FirstOrDefault().Odometer,
Date = g.FirstOrDefault().DateTime
})
.ToList();
In the end, I use
data.AddRange(dataTransactions);
data.AddRange(dataTransfers);
to put the two lists together and generate a fuel consumption report.
Both lists are individually sorted by Date, but after "AddRange" the "dataTransfers" just gets added to the end, losing my sort by Date. How do I sort the combined result again by date after using the "AddRange" command?
Try this:
data = data.OrderBy(d => d.Date).ToList();
Or if you want to order descending:
data = data.OrderByDescending(d => d.Date).ToList();
You can call List<T>.Sort(delegate).
https://msdn.microsoft.com/en-us/library/w56d4y5z(v=vs.110).aspx
Example:
data.Sort(delegate(FuelLightTruckDataSource x, FuelLightTruckDataSource y)
{
// your sort logic here.
});
Advantage: this sort doesn't create a new IList<T> instance as it does in OrderBy. it's a small thing, but to some people this matters, especially for performance and memory sensitive situations.

LINQ query optimization

I retrieve data from two different repositories:
List<F> allFs = fRepository.GetFs().ToList();
List<E> allEs = eRepository.GetEs().ToList();
Now I need to join them so I do the following:
var EFs = from c in allFs.AsQueryable()
join e in allEs on c.SerialNumber equals e.FSerialNumber
where e.Year == Convert.ToInt32(billingYear) &&
e.Month == Convert.ToInt32(billingMonth)
select new EReport
{
FSerialNumber = c.SerialNumber,
FName = c.Name,
IntCustID = Convert.ToInt32(e.IntCustID),
TotalECases = 0,
TotalPrice = "$0"
};
How can I make this LINQ query better so it will run faster? I would appreciate any suggestions.
Thanks
Unless you're able to create one repository that contains both pieces of data, which would be a far preferred solution, I can see the following things which might speed up the process.
Since you'r always filtering all E's by Month and Year, you should do that before calling ToList on the IQueryable, that way you reduce the number of E's in the join (probably considerably)
Since you're only using a subset of fields from E and F, you can use an anonymous type to limit the amount of data to transfer
Depending on how many serialnumbers you're retrieving from F's, you could filter your E's by serials in the database (or vice versa). But if most of the serialnumbers are to be expected in both sets, that doesn't really help you much further
Reasons why you might not be able to combine the repositories into one are probably because the data is coming from two separate databases.
The code, updated with the above mentioned points 1 and 2 would be similar to this:
var allFs = fRepository.GetFs().Select(f => new {f.Name, f.SerialNumber}).ToList();
int year = Convert.ToInt32(billingYear);
int month = Convert.ToInt32(billingMonth);
var allEs = eRepository.GetEs().Where(e.Year == year && e.Month == month).Select(e => new {e.FSerialNumber, e.IntCustID}).ToList();
var EFs = from c in allFs
join e in allEs on c.SerialNumber equals e.FSerialNumber
select new EReport
{
FSerialNumber = c.SerialNumber,
FName = c.Name,
IntCustID = Convert.ToInt32(e.IntCustID),
TotalECases = 0,
TotalPrice = "$0"
};

LINQ-To-SQL - slow query

I'm just wondering if anyone can offer any advice on how to improve my query.
Basically, it'll be merging 2 rows into 1. The only thing the rows will differ by is a 'Type' char column ('S' or 'C') and the Value. What I want to do is select one row, with the 'S' value and the 'C' value, and calculate the difference (S-C).
My query works, but it's pretty slow - it takes around 8 seconds to get the results, which is not ideal for my application. I wish I could change the database structure but I can't sadly!
Here is my query:
var sales = (from cm in dc.ConsignmentMarginBreakdowns
join sl in dc.SageAccounts on new { LegacyID = cm.Customer, Customer = true } equals new { LegacyID = sl.LegacyID, Customer = sl.Customer }
join ss in dc.SageAccounts on sl.ParentAccount equals ss.ID
join vt in dc.VehicleTypes on cm.ConsignmentTripBreakdown.VehicleType.Trim() equals vt.ID.ToString() into vtg
where cm.ConsignmentTripBreakdown.DeliveryDate >= dates.FromDate && cm.ConsignmentTripBreakdown.DeliveryDate <= dates.ToDate
where (customer == null || ss.SageID == customer)
where cm.BreakdownType == 'S'
orderby cm.Depot, cm.TripNumber
select new
{
NTConsignment = cm.NTConsignment,
Trip = cm.ConsignmentTripBreakdown,
LegacyID = cm.LegacyID,
Costs = dc.ConsignmentMarginBreakdowns.Where(a => a.BreakdownType == 'C' && a.NTConsignment == cm.NTConsignment && a.LegacyID == cm.LegacyID && a.TripDate == cm.TripDate && a.Depot == cm.Depot && a.TripNumber == cm.TripNumber).Single().Value,
Sales = cm.Value ?? 0.00m,
Customer = cm.Customer,
SageID = ss.SageID,
CustomerName = ss.ShortName,
FullCustomerName = ss.Name,
Vehicle = cm.ConsignmentTripBreakdown.Vehicle ?? "None",
VehicleType = vtg.FirstOrDefault().VehicleTypeDescription ?? "Subcontractor"
});
A good place to start when optimizing Linq to SQL queries is the SQL Server Profiler. There you can find what SQL code is being generated by Linq to SQL. From there, you can toy around with the linq query to see if you can get it to write a better query. If that doesn't work, you can always write a stored procedure by hand, and then call it from Linq to SQL.
There really isn't enough information supplied to make an informed opinion. For example, how many rows in each of the tables? What does the generated T-SQL look like?
One thing I would suggest first is to take the outputted T-SQL, generate a query plan and look for table or index scans.

Categories

Resources