I need to Optimize this Linq Query - c#

I have a Linq query that needs optimizing.
Edit
I guess over all what I am trying to do is select all the rows in the first table, but select the last or default row in Survey, CB and Science tables...
Edit
When this creates a query, I hit the database once for the entire query and then once more for EVERY single result I get back from the first query. So if TestClass returns 30 results, I then hit the DB 30 more times for the Survey Object.
I really need a better way to make this work.
Thanks in advance!
Example Query
return (from xx in db.test
select new TestClass {
Added_By_User_ID = xx.Added_By_User_ID,
Survey = (from ru in db.Utopia
where ru.Province_ID == xx.Province_ID
orderby ru.uid descending
select ru).Take(1).ToList(),
}).ToList();
Real Query
return (from xx in db.Utopia_Province_Data_Captured_Gens
where xx.Owner_Kingdom_ID == ownerKingdomID
where kingdomList.Contains((Guid)xx.Kingdom_ID)
select new ProvinceClass
{
Kingdom_ID = xx.Kingdom_ID,
Kingdom_Island = xx.Kingdom_Island,
Kingdom_Location = xx.Kingdom_Location,
Owner_Kingdom_ID = xx.Owner_Kingdom_ID,
Province_ID = xx.Province_ID,
Province_Name = xx.Province_Name,
Owner_User_ID = xx.Owner_User_ID,
Race_ID = xx.Race_ID,
Updated_By_DateTime = xx.Updated_By_DateTime,
Networth = xx.Networth,
Land = xx.Land,
Monarch_Display = xx.Monarch_Display,
Owner = xx.Owner,
Sub_Monarch = xx.Sub_Monarch,
CB_Updated_By_Province_ID = xx.CB_Updated_By_Province_ID,
uid = xx.uid,
Formatted_By = xx.Formatted_By,
Utopian_Day_Month = xx.Utopian_Day_Month,
Utopian_Year = xx.Utopian_Year,
Ruler_Name = xx.Ruler_Name,
Personality_ID = xx.Personality_ID,
Nobility_ID = xx.Nobility_ID,
Money = xx.Money,
Daily_Income = xx.Daily_Income,
Food = xx.Food,
Runes = xx.Runes,
Population = xx.Population,
Peasents = xx.Peasents,
Peasents_Non_Percentage = xx.Peasents_Non_Percentage,
Trade_Balance = xx.Trade_Balance,
Building_Effectiveness = xx.Building_Effectiveness,
Military_Efficiency_Off = xx.Military_Efficiency_Off,
Military_Efficiency_Def = xx.Military_Efficiency_Def,
Draft = xx.Draft,
Soldiers = xx.Soldiers,
Soldiers_Regs_Off = xx.Soldiers_Regs_Off,
Soldiers_Regs_Def = xx.Soldiers_Regs_Def,
Soldiers_Elites = xx.Soldiers_Elites,
War_Horses = xx.War_Horses,
//Prisoners = xx.Prisoners,
Military_Net_Off = xx.Military_Net_Off,
Military_Net_Def = xx.Military_Net_Def,
Military_Current_Off = xx.Military_Current_Off,
Military_Current_Def = xx.Military_Current_Def,
Mil_Training = xx.Mil_Training,
Mil_Wage = xx.Mil_Wage,
Mil_Overall_Efficiency = xx.Mil_Overall_Efficiency,
Mil_Total_Generals = xx.Mil_Total_Generals,
Wizards = xx.Wizards,
Wizards_Value_Type = xx.Wizards_Value_Type,
Thieves = xx.Thieves,
Thieves_Value_Type = xx.Thieves_Value_Type,
Plague = xx.Plague,
Monarch_Vote_Province_ID = xx.Monarch_Vote_Province_ID,
Protected = xx.Protected,
Hit = xx.Hit,
Honor = xx.Honor,
Province_Notes = xx.Province_Notes,
CB_Export_Line = xx.CB_Export_Line,
Army_Out = xx.Army_Out,
Army_Out_Expires = xx.Army_Out_Expires,
Updated_By_Province_ID = xx.Updated_By_Province_ID,
SOM_Updated_By_Province_ID = xx.SOM_Updated_By_Province_ID,
SOM_Updated_By_DateTime = xx.SOM_Updated_By_DateTime,
CB_Updated_By_DateTime = xx.CB_Updated_By_DateTime,
CB_Requested = xx.CB_Requested,
CB_Requested_Province_ID = xx.CB_Requested_Province_ID,
SOM_Requested = xx.SOM_Requested,
SOM_Requested_Province_ID = xx.SOM_Requested_Province_ID,
SOS_Requested = xx.SOS_Requested,
SOS_Requested_Province_ID = xx.SOS_Requested_Province_ID,
Survey_Requested = xx.Survey_Requested,
Survey_Requested_Province_ID = xx.Survey_Requested_Province_ID,
Last_Login_For_Province = xx.Last_Login_For_Province,
Date_Time_User_ID_Linked = xx.Date_Time_User_ID_Linked,
Added_By_User_ID = xx.Added_By_User_ID,
NoteCount = (from yy in db.Utopia_Province_Notes
where yy.Province_ID == xx.Province_ID
select yy).Count(),
SOM = (from uu in db.Utopia_Province_Data_Captured_Type_Militaries
where uu.Province_ID == xx.Province_ID
where uu.Owner_Kingdom_ID == ownerKingdomID
where uu.DateTime_Added == (from ru in db.Utopia_Province_Data_Captured_Type_Militaries //datetime can be same for multiple items.
where ru.Province_ID == xx.Province_ID
where ru.Owner_Kingdom_ID == ownerKingdomID
orderby ru.uid descending // To get the last most inserted rows
select ru.DateTime_Added).FirstOrDefault()
select uu).ToList(),
SOS = (from zz in db.Utopia_Province_Data_Captured_Sciences
where ru.Province_ID == xx.Province_ID
where ru.Owner_Kingdom_ID == ownerKingdomID
orderby ru.uid descending
select ru).Take(1).ToList(),
Survey = (from ru in db.Utopia_Province_Data_Captured_Surveys
where ru.Province_ID == xx.Province_ID
where ru.Owner_Kingdom_ID == ownerKingdomID
orderby ru.uid descending
select ru).Take(1).ToList(),
CB = (from ru in db.Utopia_Province_Data_Captured_CBs
where ru.Province_ID == xx.Province_ID
where ru.Owner_Kingdom_ID == ownerKingdomID
orderby ru.uid descending
select ru).Take(1).ToList()
}).ToList();

You can select from your Utopia table and join the second one for getting the user_id like:
from ru in db.Utopia
join xx in test on ru.Province_ID equals xx.Province_ID
orderby ru.uid descending
select new TestClass
{
Added_By_User_ID = xx.Added_By_User_ID,
Survey = ru
}
Update for the real query
This is one massive query, you should consider an approach based on joins as i suggested and based on local caching as Jim McKeeth suggested. Or you should create a new table all together.
When you need to query from multiple tables with optional data, you can perform a left join:
from ru in db.Utopia
join user in db.Users on ru.UserId equals user.Id // User is required
join survey in db.Surveys on ru.Province_ID equals survey.Province_ID into j1
from survey in j1.DefaultIfEmpty() // Survey is optional (left join)
join address in db.Address on ru.UserId equals address.UserId into j2
from survey in j2.DefaultIfEmpty() // Address is optional (left join)
orderby ru.uid descending
select new TestClass
{
Added_By_User_ID = xx.Added_By_User_ID,
Survey = survey,
Street = address.Street,
UserName = new UserName
{
FirstName = user.FirstName,
LastName = user.LastName
}
}
Be aware that the amount of joins will influence the performance of your query. Sooner or later it will become more efficient do find a better way with caching.
Another note is that calling ToList() in your query will actually execute that part of the query (or the whole query).

Something like:
from xx in db.test
join ru in db.Utopia on xx.Province_ID equals ru.Province_ID
orderby ru.uid descending
select new ..

The complexity of your real query to me indicates you should be moving this LINQ statement into a stored procedure, then calling that instead.
I recommend this because (a) you will have complete control over the how the query executes and, (b) profiling and performance optimizing your query is much easier in TSQL. This is the strategy we have used in larger projects in the past when result-set generation becomes non-trivial.
I would also suggest you use AutoMapper to perform the task of transferring the sproc results into your DTO, because you appear to have a near 1:1 mapping

Related

Join List Data from two separate Data Contexts

I have been searching for the solution to this problem and this is what I have so far:
var ProductInfo = (from p in twd.Products
orderby p.PC
where p.DELMARK == active
select p).AsEnumerable();
var BuyersData =
(from x in db.MinimumProductInfo
where x != null
orderby x.ItemCode, x.Region
let pnote =
(from pn in db.ProductNotes
where pn != null
where x.MinimumProductInfoID == pn.MinimumProductInfoID
&& pn.NoteTypeFlag == "p"
orderby pn.NoteDate descending
select pn).FirstOrDefault()
let cnote =
(from c in db.ProductNotes
where c != null
where x.MinimumProductInfoID == c.MinimumProductInfoID
&& c.NoteTypeFlag == "c"
orderby c.NoteDate descending
select c).FirstOrDefault()
let product =
(from p in ProductInfo
where x.ItemCode == p.PC
select p).FirstOrDefault()
select new ProductInfoWithNoteList
{
MinimumProductInfoID = x.MinimumProductInfoID,
ItemCode = x.ItemCode,
EquivCode = x.EquivCode,
Description = product.PDESC,
MinimumOnHandQuantity = x.MinimumOnHandQuantity,
MaximumOHandQuantity = x.MaximumOHandQuantity,
MinimumOrderQuantity = x.MinimumOrderQuantity,
LeadTimeInWeeks = x.LeadTimeInWeeks,
Region = x.Region,
Comment = cnote.ItemNote,
PermanentNote = pnote.ItemNote
}).ToArray();
It looks correct but I am getting an error,
'The specified LINQ expression contains references to queries that are
associated with different contexts.'
What this code is supposed to do is pull out all the active product codes from the first table using the twd datacontext then use data from that database in the db.MinimumProductInfo table. The reason they have 2 separate data contexts are they are completely different databases, the first is our ERP and the second is one that we are building in house.
What am I missing? I know it is possible to do this by separating the two datacontexts then adding them together because I have seen it done with single instances but I cannot find how to do it with list data.
Instead of this:
let product =
(from p in ProductInfo
where x.ItemCode == p.PC
select p).FirstOrDefault()
select new ProductInfoWithNoteList
{
MinimumProductInfoID = x.MinimumProductInfoID,
ItemCode = x.ItemCode,
EquivCode = x.EquivCode,
Description = product.PDESC,
MinimumOnHandQuantity = x.MinimumOnHandQuantity,
MaximumOHandQuantity = x.MaximumOHandQuantity,
MinimumOrderQuantity = x.MinimumOrderQuantity,
LeadTimeInWeeks = x.LeadTimeInWeeks,
Region = x.Region,
Comment = cnote.ItemNote,
PermanentNote = pnote.ItemNote
}).ToArray();
Try this by removing the let product clause and not filling the properties associated with ProductInfo because we will do that afterwards (See I have commented out the Description property):
select new ProductInfoWithNoteList
{
MinimumProductInfoID = x.MinimumProductInfoID,
ItemCode = x.ItemCode,
EquivCode = x.EquivCode,
//Description = product.PDESC,
MinimumOnHandQuantity = x.MinimumOnHandQuantity,
MaximumOHandQuantity = x.MaximumOHandQuantity,
MinimumOrderQuantity = x.MinimumOrderQuantity,
LeadTimeInWeeks = x.LeadTimeInWeeks,
Region = x.Region,
Comment = cnote.ItemNote,
PermanentNote = pnote.ItemNote
}).ToArray();
Now that you have your BuyersData and ProductInfo in memory, set the Description property or all the items in BuyersData:
foreach(var thisBuyerData in BuyersData)
{
var thisPi = ProductInfo.SingleOrDefault(x => x.PC == thisBuyerData.ItemCode);
thisBuyerData.Description = thisPi?.PDESC;
}

Duplicate values on join linq

I am having some problem with the results, I get duplicates instead of two with different values on the join. The db contains those two values but one of them has different value inside the text field, how come I get duplicates, the only thing that is same is the CompanyPageId).
var query = (from pageEducation in _context.PageEducations
join companyPage in _context.Pages on pageEducation.CompanyPageId equals companyPage.Id into p
from companyPage in p.DefaultIfEmpty()
where pageEducation.PageId == pageId
select new PageEducation
{
Id = pageEducation.Id,
PageId = pageEducation.PageId,
CompanyPageId = pageEducation.CompanyPageId,
CustomCompany = pageEducation.CustomCompany,
CompanyPage = companyPage != null ? new Page {Name = companyPage.Name, Id = companyPage.Id} : null,
Education = pageEducation.Education
}).ToList();
My table looks like this:
CompanyPageId, education
33 edu1
33 edu2
and the result is list with two items but duplicates.
WHen i run this query in SQL i dont get any duplicates:
select * from Page_Educations
left join Pages on page_Educations.CompanyPageId = pages.Id
where page_Educations.PageId = 10
It even working when I run the linq in LinqPad
var query = from pageEducation in Page_Educations
join companyPage in Pages on pageEducation.CompanyPageId equals companyPage.ID into p
from companyPage in p.DefaultIfEmpty()
where pageEducation.PageId == 10
select new
{
Id = pageEducation.Id,
PageId = pageEducation.PageId,
CompanyPageId = pageEducation.CompanyPageId,
CustomCompany = pageEducation.CustomCompany,
CompanyPage = companyPage != null ? new {Name = companyPage.Name, Id = companyPage.ID} : null,
Education = pageEducation.Education
};
query.Dump();

linq query to return a string from a list

I have a products table and an activities table.Each product is made up of different activities.What i want is to display a product name,price,etc and the all the activities (i.e all the activities in one column) which are part of that product in a datagrid using linq
Below is my query
using (bungeedbEntities context = new bungeedbEntities())
{
var bookingData = from con in context.bookings
join agn in context.agents on con.main_agent_id equals agn.code
select new POS_LINK.BusinessObjects.Bookings
{
Product = con.product_name,
Activity = String.Join(",", (from con1 in context.bookings
join acp in context.booking_activity on con1.code equals acp.booking_code
join agn in context.agents on con1.main_agent_id equals agn.code
join act in context.activities on acp.activity_code equals act.code
select act.name).ToArray()),
ReservationCode = con.main_agent_voucher,
CostOfSale = 0.00M,
DateOfActivity = (DateTime)con.date_of_activity,
Notes = con.notes,
Quantity = (int)con.pax,
Child_quantity = 0,
Child_cost_percentage = 0,
CostPerPerson = 0.00M,
SubAgentRef = "56789",
SubAgentName = con.sub_agent_name,
ClientName = con.client_name,
MainAgent = agn.agent_name,
Consultant2 = con.sub_agent_consultant
};
return bookingData.ToList();
On running i get the following error- LINQ to Entities does not recognize the method 'System.String Join(System.String, System.String[])' method, and this method cannot be translated into a store expression.
I seem to have run out of ideas anyone with a better solution to this would save me from a lot of head scratching
You need to do it in two steps: first step selects the data, while the second step translates it to a string with string.Join:
var bookingData = (from con in context.bookings
join agn in context.agents on con.main_agent_id equals agn.code
select new { // Construct an anonymous type with the relevant parts
Product = con.product_name,
ActivityData = (from con1 in context.bookings
join acp in context.booking_activity on con1.code equals acp.booking_code
join agn in context.agents on con1.main_agent_id equals agn.code
join act in context.activities on acp.activity_code equals act.code
select act.name),
ReservationCode = con.main_agent_voucher,
DateOfActivity = (DateTime)con.date_of_activity,
Notes = con.notes,
Quantity = (int)con.pax,
SubAgentName = con.sub_agent_name,
ClientName = con.client_name,
MainAgent = agn.agent_name,
Consultant2 = con.sub_agent_consultant
}).AsEnumerable() // Bring this into memory
.Select(p => new POS_LINK.BusinessObjects.Bookings {
Product = p.Product,
Activity = string.Join(", ", p.ActivityData.ToArray()),
ReservationCode = p.ReservationCode,
CostOfSale = 0.00M,
Notes = p.Notes,
Quantity = p.Quantity,
Child_quantity = 0,
Child_cost_percentage = 0,
CostPerPerson = 0.00M,
SubAgentRef = "56789",
SubAgentName = p.SubAgentName,
ClientName = p.ClientName,
MainAgent = p.MainAgent,
Consultant2 = p.Consultant2
});
return bookingData.ToList();
The idea here is simple: first, you construct an anonymous type that has all relevant information, including an enumeration of acc.names, then you force it into memory by calling AsEnumerable(), and finally construct your POS_LINK.BusinessObjects.Bookings objects using LINQ-to-Object, which understands string.Join.

Count in select query of Linq to SQL

I am trying to convert below SQL query into Linq.
select eg.userid, eg.groupid, count(r.RID) as RecipientCount
from eg
join g on eg.groupid = g.groupid
join r on g.RID = r.RID
where eg.UserId = '7F813844-3B93-418E-8141-654082C4E37D'
and eg.IsDeleted = 0
and r.Isdeleted = 0
group by eg.groupid
Above query runs properly in SQL.
My Linq code is:
var v = from eml in dc.egs
join recpingroup in dc.g on eml.GroupID equals recpingroup.GroupID
where eml.aspnet_User.LoweredUserName.Equals(strUserName.ToLower())
&& !eml.IsDeleted
&& !recpingroup.r.IsDeleted
select new Info()
{
CreateDt = eml.CreateDt.ToShortDateString(),
UserId = eml.UserId.ToString(),
LastUpdateDt = eml.LastUpdateDt.ToShortDateString(),
Username = eml.aspnet_User.UserName,
GroupDescription = eml.GroupDescription,
GroupID = eml.GroupID.ToString().ToUpper(),
GroupName = eml.GroupName,
Count = dc.g.Count(r1 => r1.GroupID.Equals(eml.GroupID) && !r1.r.IsDeleted)
};
where dc is my DataContext.
But I am having problems in the last property i.e. Count is coming wrong. I want the counts of recipients from recpingroup.r as RecipientCount.
Also note that tables are linked in SQL internally by PK and FK references.
try following
Count = (from x in dc.g where(r1 => r1.GroupID.Equals(eml.GroupID) && !r1.r.IsDeleted) select x.g).count()

associated data in linq select

I need to select data from different tables associated, I have a hospital table, another specialty, and other services, and what is held to select all hospitals and their specialties and services, there is a way to do this in linq?
this is what I'm trying
var hospitalesQuery = from hospital in App.ViewModel.ChristusDB.Hospitales
where hospital.id_tipo == "2" && hospital.christus == "1"
orderby hospital.distancia ascending
from e in App.ViewModel.ChristusDB.EspHosp
where e.fk_hospital==hospital.pk_hospital
from s in App.ViewModel.ChristusDB.ServHosp
where s.fk_hospital==hospital.pk_hospital
select new Hospitale()
{
pk_hospital = hospital.pk_hospital,
nombre = hospital.nombre,
direccion = hospital.direccion,
ciudad = hospital.ciudad,
id_ciudad = hospital.id_ciudad,
estado = hospital.estado,
id_estado = hospital.id_estado,
id_tipo = hospital.id_tipo,
tipo_descripcion = hospital.tipo_descripcion,
imagen = hospital.imagen,
gps_lat = hospital.gps_lat,
gps_lng = hospital.gps_lng,
abierto_lv = hospital.abierto_lv,
abierto_sd = hospital.abierto_sd,
telefono_1 = hospital.telefono_1,
telefono_2 = hospital.telefono_2,
telefono_3 = hospital.telefono_3,
telefono_4 = hospital.telefono_4,
telefono_5 = hospital.telefono_5,
inactivo = hospital.inactivo,
christus = hospital.christus,
especialidades= new List<Especialidade>(e),
servicios= new List<Servicio>(s)
};
You are looking for joining tables by a foreign key. LINQ (asn SQL as well) provide join for this:
from hospital in App.ViewModel.ChristusDB.Hospitales
where hospital.id_tipo == "2" && hospital.christus == "1"
join e in App.ViewModel.ChristusDB.EspHosp on hospital.pk_hospital equals e.fk_hospital
join s in App.ViewModel.ChristusDB.ServHosp on hospital.pk_hospital on s.fk_hospital
orderby hospital.distancia ascending
select new Hospitale()
{...
Update. If you want to load list dependencies for each of the hospitals, I do not think you can do this with one query in EF. This is as close as you can get:
var hospitals = (from hospital in App.ViewModel.ChristusDB.Hospitales
where hospital.id_tipo == "2" && hospital.christus == "1"
orderby hospital.distancia ascending
select new Hospitale()
{
pk_hospital = hospital.pk_hospital,
...
}).ToList();
foreach (var h in hospitals)
{
h.especialidades = App.ViewModel.ChristusDB.EspHosp.Where(e => e.fk_hospital==h.pk_hospital).ToList();
h.servicios = App.ViewModel.ChristusDB.ServHosp.Where(e => s.fk_hospital==h.pk_hospital).ToList();
}
However this would be a lot of queries to the DB, which might perform poorly. You should really think about redesigning your EF data model properly so that the framework does this job of loading dependencies for you.

Categories

Resources