How to improve query with two contexts - MVC5, LINQ n EF - entity-framework

I have two contexts. In one of them i have two views of which i get cods related to an entity from the another context. This query is taking too long time. How to improve it?
var negociacoes = _db.Negociacoes.Include(o=> o.User).ToArray();
var produtos = _oriDb.Vw_Produtos.ToArray();
var clientesVendedor = _oriDb.Vw_ClientesVendedores.ToArray();
var query = from n in negociacoes
join p in produtos on n.ProdutoId equals p.ProdutoId
join c in clientesVendedor on n.ClienteId equals c.codigo_entidade
select new NegociacaoView
{
NegociacaoId = n.NegociacaoId,
ProdutoId = n.ProdutoId,
Produto = p.descricao,
ClienteId = n.ClienteId,
Cliente = c.razao_social,
Rca = n.Rca,
Quantidade = n.Quantidade,
Preco = n.Preco,
Situacao = n.Situacao,
UserId = n.User.UserName,
Atendente = n.Atendente,
CondicaoId = n.CondicaoId,
DataCriacao = n.DataCriacao,
DataLiberacao = n.DataLiberacao,
Observacao = n.Observacao,
User = n.User
};
return query.ToList();

There are a couple of ways to speed this up:
It helps to run the smallest most efficient query first, then use those results to constrain the following queries.
Defining a select list so the database doesn't have to materialize every column will speed things up and use less memory.
Unfortunately, no matter how you do it in LINQ, you will end up with sql that uses large IN statements. A sproc would give you access to temp tables and joins that would be even better.
var negociacoes = _db.Negociacoes.Include(o=> o.User).ToArray();
//Use results of first query to constrain the second two. You could maybe combine the second two into one query.
var clientIds = negociacoes.Select(x => x.ClienteId);
var productIds = negociacoes.Select(x => x.ProdutoId);
var produtos = _oriDb.Vw_Produtos
.Where(x => productIds.Contains(x.ProdutoId))
//add a select. You're only using two columns from this table.
//.Select(x => new { })
.ToArray();
var clientesVendedor = _oriDb.Vw_ClientesVendedores
.Where(x => clientIds.Contains(x.codigo_entidade))
//add a select. You're only using two columns from this table.
//.Select(x => new { })
.ToArray();
var query = from n in negociacoes
join p in produtos on n.ProdutoId equals p.ProdutoId
join c in clientesVendedor on n.ClienteId equals c.codigo_entidade
select new NegociacaoView
{
NegociacaoId = n.NegociacaoId,
ProdutoId = n.ProdutoId,
Produto = p.descricao,
ClienteId = n.ClienteId,
Cliente = c.razao_social,
Rca = n.Rca,
Quantidade = n.Quantidade,
Preco = n.Preco,
Situacao = n.Situacao,
UserId = n.User.UserName,
Atendente = n.Atendente,
CondicaoId = n.CondicaoId,
DataCriacao = n.DataCriacao,
DataLiberacao = n.DataLiberacao,
Observacao = n.Observacao,
User = n.User
};
return query.ToList();

Related

Split One Row into many based on splitting string in multiple cells

Trying to split one row into many based on string in two cells. it is similar to the question
LINQ to separate column value of a row to different rows in .net
but i need to split based on Product & Cost Columns rather than product column only
SNo.
Product
Cost
1
colgate,closeup,pepsodent
50,100,150
2
rin,surf
100
into
SNo.
Product
Cost
1
colgate
50
1
closeup
100
1
pepsodent
150
2
rin
100
2
surf
100
I'm using Linq to Object with Entity Framework
Try the following. Since you have not presented any model it can be inaccurate in names.
var loaded = ctx.Products.ToList();
var query =
from p in loaded
from sp in p.Product.Split(',').Zip(p.Cost.Split(','), (p, c) => (p, c))
select new
{
Sno = p.Sno,
Product = sp.p,
Cost = sp.c
};
var splitted = query.ToList();
Using #SvyatoslavDanyliv naming, here is an answer:
var loaded = ctx.Products.ToList();
var query =
from p in loaded
from sp in p.Product.Split(',').Zip(p.Cost.Split(','), (p, c) => (p, c))
select new
{
Sno = p.Sno,
Product = sp.p,
Cost = sp.c
};
var splitted = query.ToList();
It feels a bit complicated to me. I would prefer using an extension method to create a variant of Zip that repeats the last element of a shorter sequence to match the longer sequence:
public static class EnumerableExt {
public static IEnumerable<(T1 First,T2 Second)> ZipExtend<T1,T2>(this IEnumerable<T1> s1, IEnumerable<T2> s2) {
var s1e = s1.GetEnumerator();
var s2e = s2.GetEnumerator();
T1 s1eLast = default;
T2 s2eLast = default;
bool has_s2 = false;
if (s1e.MoveNext()) {
do {
s1eLast = s1e.Current;
if (s2e.MoveNext()) {
s2eLast = s2e.Current;
has_s2 = true;
}
else if (!has_s2)
yield break;
yield return (s1eLast, s2eLast);
} while (s1e.MoveNext());
if (has_s2)
while (s2e.MoveNext())
yield return (s1eLast, s2e.Current);
}
yield break;
}
}
Then the answer is:
var query =
from p in loaded
from pr in p.Product.Split(',').ZipExtend(p.Cost.Split(','))
select new
{
Sno = p.Sno,
Product = pr.First,
Cost = pr.Second
};
var splitted = query.ToList();

EF Append to an IQueryable a Where that generates an OR

I'm trying to achieve dynamic filtering on a table. My UI has filters that can be enabled or disabled on demand, and as you can imagine, my query should be able to know when to add filters to the query.
What I have so far is that I check if the filter object has a value, and if it does it adds a where clause to it. Example:
var q1 = DBContext.Table1
if (!string.IsNullOrEmpty(filterModel.SubjectContains))
q1 = q1.Where(i => i.Subject.Contains(filterModel.SubjectContains));
if (filterModel.EnvironmentId != null)
q1 = q1.Where(i => i.EnvironmentId == filterModel.EnvironmentId);
if (filterModel.CreatedBy != null)
q1 = q1.Where(i => i.CreatedByUserId == filterModel.CreatedBy);
var final = q1.Select(i => new
{
IssuesId = i.IssuesId,
Subject = i.Subject,
EnvironmentId = i.EnvironmentId,
CreatedBy = i.CreatedByUser.FullName,
});
return final.ToList();
The code above generates T-SQL that contains a WHERE clause for each field that uses AND to combine the conditions. This is fine, and will work for most cases.
Something like:
Select
IssueId, Subject, EnvironmentId, CreatedById
From
Table1
Where
(Subject like '%stackoverflow%')
and (EnvironmentId = 1)
and (CreatedById = 123)
But then I have a filter that explicitly needs an IssueId. I'm trying to figure out how the EF Where clause can generate an OR for me. I'm looking something that should generate a Tsql that looks like this:
Select
IssueId, Subject, EnvironmentId, CreatedById
From
Table1
Where
(Subject like '%stackoverflow%')
and (EnvironmentId = 1)
and (CreatedById = 123)
or (IssueId = 10001)
Found a solution for this that doesn't have to do multiple database call and works for me.
//filterModel.StaticIssueIds is of type List<Int32>
if (filterModel.StaticIssueIds != null)
{
//Get all ids declared in filterModel.StaticIssueIds
var qStaticIssues = DBContext.Table1.Where(i => filterModel.StaticIssueIds.Contains(i.IssuesId));
//Let's get all Issues that isn't declared in filterModel.StaticIssueIds from the original IQueryable
//we have to do this to ensure that there isn't any duplicate records.
q1 = q1.Where(i => !filterModel.StaticIssueIds.Contains(i.IssuesId));
//We then concatenate q1 and the qStaticIssues.
q1 = q1.Concat(qStaticIssues);
}
var final = q1.Select(i => new
{
IssuesId = i.IssuesId,
Subject = i.Subject,
EnvironmentId = i.EnvironmentId,
CreatedBy = i.CreatedByUser.FullName,
});
return final.ToList();

How to avoid writing duplicate queries when needing counts and then items

I have constructed a LINQ query that joins about a half dozen tables. The problem is, for paging purposes, I want to get a count first of how many items will be returned. So the issue I'm running into is having to write the exact same query twice: one to get the item count then another to build my collection of items.
Example:
using (var context = new DbContext())
{
var items = from i in context.Table1
join a in context.TableA on i.SomeProperty equals a.SomeProperty
join b in context.TableB on i.SomeOtherProperty equals b.SomeProperty
join c in context.TableC on i.AnotherProperty equals c.SomeProperty
etc.
etc.
select i;
count = items.Count();
}
return count;
.
.
.
using (var context = new DbContext())
{
var items = from i in context.Table1
join a in context.TableA on i.SomeProperty equals a.SomeProperty
join b in context.TableB on i.SomeOtherProperty equals b.SomeProperty
join c in context.TableC on i.AnotherProperty equals c.SomeProperty
etc.
etc.
select new
{
DynamicProp1 = i.SomeProperty,
DyanmicProp2 = a.SomeProperty,
DyanmicProp3 = b.SomePropery,
etc.
etc.
}
... do some stuff with 'items'...
}
I cannot think of any way to avoid this duplicate query. I need access to all the joined tables in order to build my collection. I would appreciate any tips or suggestions.
You can create method which get context and return IQueryable of items with all needed entities:
class Holder
{
TableAItem A{get;set;}
TableBItem B{get;set;}
...
}
IQueryable<Holder> GetQuery(DbContext context)
{
return from i in context.Table1
join a in context.TableA on i.SomeProperty equals a.SomeProperty
join b in context.TableB on i.SomeOtherProperty equals b.SomeProperty
join c in context.TableC on i.AnotherProperty equals c.SomeProperty
...
select new Holder
{
A = i,
B = b
....
};
}
using (var context = new DbContext())
{
var items = GetQuery(context);
count = items.Count();
}
return count;
using (var context = new DbContext())
{
var items = from r in GetQuery(context)
select new
{
DynamicProp1 = r.a.SomeProperty,
DyanmicProp2 = r.a.SomeProperty,
DyanmicProp3 = r.b.SomePropery,
etc.
etc.
}
... do some stuff with 'items'...
}
Remember that making a query doesn't execute it, this is called deferred execution. So why not make the query and then pass it around as an IQueryable<> object. For example, consider this code:
Just a simple method to return the last char from a string, but it also writes out what it's doing:
public char GetLastChar(string input)
{
Console.WriteLine("GetLastChar from {0}", input);
return input.Last();
}
Now this code using the method:
var listOfStuff = new List<string> { "string1", "string2", "string3" };
Console.WriteLine("Making the query");
var results = from s in listOfStuff
select GetLastChar(s);
Console.WriteLine("Before getting count");
var count = results.Count();
Console.WriteLine("Now enumerating the query");
foreach(var s in results)
{
Console.WriteLine(s);
}
You will see the output as follows:
Making the query
Before getting count
GetLastChar from string1
GetLastChar from string2
GetLastChar from string3
3
Now enumerating the query
GetLastChar from string1
1
GetLastChar from string2
2
GetLastChar from string3
3

How to search multi keywork in linq query

i have this code in homepage
CheckBox[] ch= new CheckBox[12];
ch[0] = ChkContextA;
ch[1]= ChkContextB;
ch[2]= ChkContextC;
ch[3]= ChkContextD;
ch[4]= ChkContextE;
ch[5]= ChkContextF;
ch[6]= ChkContextG;
ch[7]= ChkContextH;
ch[8]= ChkContextI;
ch[9]= ChkContextJ;
ch[10]= ChkContextK;
ch[11]= ChiContextL;
for (int i = 0; i < 11; i++)
if (ch[i].Checked) search += ch[i].Text + " ";
Response.Redirect("SearchEstate.aspx?content="+search);
and this code in SearchEstate
var content = Request.QueryString["content"];
RealEstateEntities db = new RealEstateEntities();
var query = from O in db.Owners
join E in db.Estates on O.OwnerID equals E.OwnerID
join P in db.Properties on E.PropertyID equals P.PropertyID
where P.Facilities.Contains(content)
select new
{
regdate = E.RegisterDate,
region = E.Region,
Estype = E.EstateType,
Fac = P.Facilities,
deal = P.DealType,
price = P.TotalCost,
img = E.Picture,
addrss = O.Address,
area = P.Area,
tel = P.TellNum,
bed = P.RoomNum,
park = P.ParikingNum
};
Repeater2.DataSource = query.OrderByDescending(x => x.regdate);
Repeater2.DataBind();
when user checked some checkbox "content" for example have this value:
SearchEstate.aspx?content=ContextB ContextE ContextJ
I Want search this values in Facility field in db
How can I do this? (Sorry for my bad English)
I have the feeling your are looking for something along the lines of this query:
var content = Request.QueryString["content"];
string[] contentArray = content.Split(' ');
//...
var query = //...
where P.Facilities.Any(f => contentArray.Contains(f.FacilityName))
//...
(or instead of FacilityName some other property of Facility)
But I am not sure.

Entity Framework lambda query with groupby, sum and average

I have a entity called StockDetails using Entity Framework, see picture below
I want to fetch a list IEnumerable<StockDetail>, summarized by Reels, Qtyton, average date from Days (datetime) and grouping by the rest of the properties.
I'm building a datalayer (WCF Services) with Entity Framework as ORM, some of the services are old SQL queries I'm trying to convert to linq/lamdba expression. But I'm pretty new to how to write and want some help.
This is how I started the query in lambda, but I got stuck on the groupby/sum/average part.
public IEnumerable<StockDetail> ListStockDetailByCustomerNumber(int customerNumber)
{
var CustNo = customerNumber.ToString();
return _entities.StockDetails
.Where(x => x.Custno == CustNo)
.GroupBy(
x =>
new
{
x.Millcd,
x.Matercd,
x.Proddesc,
x.Grammage,
x.Reelwidth,
x.Ordercode,
x.Buyordno,
x.Whsedesc,
x.Co,
x.Finished,
x.Pm,
x.PurchaseOrder,
x.Diameter,
x.Rtadate,
x.Custno,
x.Reels,
x.Days,
x.Qtyton
})
.ToList();
}
Question solved:
public IEnumerable<StockDetail> ListStockDetailByCustomerNumber(int customerNumber)
{
var stockDetailsList = new List<StockDetail>();
var custNo = customerNumber.ToString();
var list = _entities.StockDetails
.Where(x => x.Custno == custNo )
.GroupBy(
x =>
new
{
x.Millcd,
x.Matercd,
x.Proddesc,
x.Grammage,
x.Reelwidth,
x.Ordercode,
x.Buyordno,
x.Whsedesc,
x.Co,
x.Finished,
x.Pm,
x.PurchaseOrder,
x.Diameter,
x.Rtadate,
x.Custno,
x.UpdDte
})
.Select(x => new
{
x.Key.Millcd,
x.Key.Matercd,
x.Key.Proddesc,
x.Key.Grammage,
x.Key.Reelwidth,
x.Key.Ordercode,
x.Key.Buyordno,
Reels = x.Sum(p => p.Reels),
Qtyton = x.Sum(p => p.Qtyton),
Day = x.Max(p => p.Days),
//Day = x.Average(p => p.Days.Ticks), // Want to calculate average datetime of date but linq dosn't support datetime.ticks
x.Key.Whsedesc,
x.Key.Co,
x.Key.Finished,
x.Key.Pm,
x.Key.PurchaseOrder,
x.Key.Diameter,
x.Key.Rtadate,
x.Key.Custno,
x.Key.UpdDte
});
foreach (var s in list)
{
stockDetailsList.Add(new StockDetail
{
Millcd = GetFriendlyNameForKey(s.Millcd),
Matercd = s.Matercd,
Proddesc = s.Proddesc,
Grammage = s.Grammage,
Reelwidth = s.Reelwidth,
Ordercode = s.Ordercode,
Buyordno = s.Buyordno,
Reels = s.Reels,
Qtyton = s.Qtyton,
Days = s.Day,
Whsedesc = s.Whsedesc,
Co = s.Co,
Finished = s.Finished,
Pm = s.Pm,
PurchaseOrder = s.PurchaseOrder,
Diameter = s.Diameter,
Rtadate = s.Rtadate,
Custno = s.Custno,
UpdDte = s.UpdDte
});
}
return stockDetailsList;
}
This is how the query looks in T-SQL
SELECT
Millcd, Matercd,
Proddesc, Grammage,
Reelwidth, Ordercode,
Buyordno,
SUM(Reels) as Reels,
SUM(Qtyton) as Qtyton,
Whsedesc, Co,
(cast(FLOOR(avg(cast(DateProd as float))) as datetime)) As Days,
Finished, Pm,
PurchaseOrder,
Diameter, Rtadate,
Custno, UpdDte
FROM StockDetail
WHERE custno = #custcode
GROUP BY Millcd, Matercd, Proddesc, Grammage, Reelwidth, Ordercode, Buyordno,
Whsedesc, Co, Finished, Pm, PurchaseOrder, Diameter, Rtadate, Custno, UpdDte
not sure if this will help you but you can add
Reels = (_entities.StockDetails
.Where(x => x.Custno == CustNo).Sum(x=>x.Reels))
instead of x.Reels in your select , and do the same with Qtyton
For your average use the average extension
your select will look something like .Select(x=>new {...}) after your where statement then the group by