Combining Tables With Different Data Using Linq in MVC? - c#

I have Two classes Named OfflineOrderLineItem.cs and OnlineOrderLineItem.cs both have diff Order table named offline and Online
In that i want to Combine the two tables data to search and Display the Fields from both tables
How to do that using linq in mvc4 ??? any idea.....
public virtual IPagedList<OnlineOrderLineItem> SearchOrderLineItems(string PoNumber)
{
var query1 = (from ol in _offlineOrderLineItemRepository.Table
select new
{
ol.Name
}).ToList();
var query2 = (from opv in _onlineOrderLineItemRepository.Table
select new
{
opv.Name
}).ToList();
var finalquery = query1.Union(query2);
if (!String.IsNullOrWhiteSpace(Name))
finalquery = finalquery.Where(c => c.Name == Name);
var orderlineitems = finalquery.ToList(); //its not working it throw a error
return new PagedList<OnlineOrderLineItem>(orderlineitems);//error
}
Error
cannot convert from 'System.Collections.Generic.List<AnonymousType#1>'
to 'System.Linq.IQueryable<Nop.Core.Domain.Management.OnlineOrderLineItem>'
to 'System.Linq.IQueryable<Nop.Core.Domain.Management.OnlineOrderLineItem>'

query1 and query2 are lists of an anonymous type with a single property of type string. (I assmume the ol.Name and opv.Name are strings.) Hence finalQuery and orderlineitems are collections of this anonymous as well. By specifying PagedList<T> you require that the collection passed into the constructor is an enumeration of type T. T is OnlineOrderLineItem, but the enumeration passed into the constructor is the anonymous type which is a different type. Result: compiler error.
To solve the problem I suggest that you define a named helper type that you can use to union the two different types OfflineOrderLineItem and OnlineOrderLineItem:
public class OrderLineItemViewModel
{
public int Id { get; set; }
public string PoNumber { get; set; }
public string Name { get; set; }
// maybe more common properties of `OfflineOrderLineItem`
// and `OnlineOrderLineItem`
}
Then your SearchOrderLineItems method should return a paged list of that helper type:
public virtual IPagedList<OrderLineItemViewModel> SearchOrderLineItems(
string PoNumber)
{
var query1 = from ol in _offlineOrderLineItemRepository.Table
select new OrderLineItemViewModel
{
Id = ol.Id,
PoNumber = ol.PoNumber,
Name = ol.Name,
// maybe more properties
};
// don't use ToList here, so that the later Union and filter
// can be executed in the database
var query2 = from opv in _onlineOrderLineItemRepository.Table
select new OrderLineItemViewModel
{
Id = opv.Id,
PoNumber = opv.PoNumber,
Name = opv.Name,
// maybe more properties
};
// don't use ToList here, so that the later Union and filter
// can be executed in the database
var finalquery = query1.Union(query2);
// again no ToList here
if (!string.IsNullOrWhiteSpace(PoNumber))
finalquery = finalquery.Where(c => c.PoNumber == PoNumber);
var orderlineitems = finalquery.ToList(); // DB query runs here
return new PagedList<OrderLineItemViewModel>(orderlineitems);
}
It is important to use ToList only at the very end of the query. Otherwise you would load the whole tables of all OnlineOrderLineItems and all OfflineOrderLineItems into memory and then filter out the items with the given PoNumber in memory which would be a big overhead and performance desaster.

Instead of
var orderlineitems = finalquery.ToList();
Try
var orderlineitems = finalquery.AsQueryable();
From https://github.com/TroyGoode/PagedList/blob/master/src/PagedList/PagedList.cs, PagedList takes a IQueryable<T>
Queryable.AsQueryable<TElement> Method

Related

How to Select one element from a linq multiple join table?

var queryInfo = (from p in table1
join q in table2 on p.TABLEID equals q.USERNAME
join b in table3 on p.ORIGINAL_USER equals b.USERNAME
where p.NAME == IdVal
select new
{
p.NAME,
p.ID,
p.EXCHANGE,
p.CREATION,
q.USERNAME,
q_email = q.EMAIL,
q_fullname = q.FULL_NAME,
b_email = b.EMAIL,
p.ORIGINAL_USER,
b_fullname = b.FULL_NAME
});
Name = queryInfo.ToList().ElementAt(0).ToString();
ID = queryInfo.ToList().ElementAt(1).ToString();
exchange = queryInfo.ToList().ElementAt(2).ToString();
Creation = queryInfo.ToList().ElementAt(3).ToString();
AUsername = queryInfo.ToList().ElementAt(4).ToString();
AEmail = queryInfo.ToList().ElementAt(5).ToString();
AFullName = queryInfo.ToList().ElementAt(6).ToString();
EEmail = queryInfo.ToList().ElementAt(7).ToString();
EUsername = queryInfo.ToList().ElementAt(8).ToString();
EFullName = queryInfo.ToList().ElementAt(9).ToString();
The query is correct and working, I'm having problem trying to select and assign one to each declared variable.
I tried
queryInfo.ToList().ElementAt(0).ToString();
but this is not working. What is the proper syntax?
Create a custom class so you can map your resut into
class:
public class User
{
public string Name { get; set; }
public string ID { get; set; }
public string exchange { get; set; }
public string Creation { get; set; }
public string AUsername { get; set; }
public string AEmail { get; set; }
public string AFullName { get; set; }
public string EEmail { get; set; }
public string EUsername { get; set; }
public string EFullName { get; set; }
}
mapping:
User result = (from p in table1
join q in table2 on p.TABLEID equals q.USERNAME
join b in table3 on p.ORIGINAL_USER equals b.USERNAME
where p.NAME == IdVal
select new User()
{
Name = p.NAME,
ID = p.ID,
exchange = p.EXCHANGE,
Creation = p.CREATION,
AUsername = q.USERNAME,
AEmail = q.EMAIL,
AFullName = q.FULL_NAME,
EEmail = b.EMAIL,
EUsername = p.ORIGINAL_USER,
EFullName = b.FULL_NAME
}).FirtstOrDefault();
Materialize query via FirstOrDefault() and retrieve property values:
var queryInfo =
(from p in table1
join q in table2 on p.TABLEID equals q.USERNAME
join b in table3 on p.ORIGINAL_USER equals b.USERNAME
where p.NAME == IdVal
select new
{
p.NAME,
p.ID,
p.EXCHANGE,
p.CREATION,
q.USERNAME,
q_email = q.EMAIL,
q_fullname = q.FULL_NAME,
b_email = b.EMAIL,
p.ORIGINAL_USER,
b_fullname = b.FULL_NAME
})
.FirstOrDefault();
Name = queryInfo?.NAME;
ID = queryInfo?.ID.ToString();
exchange = queryInfo?.EXCHANGE;
Creation = queryInfo?.CREATION.ToString();
AUsername = queryInfo?.USERNAME;
AEmail = queryInfo?.q_email;
AFullName = queryInfo?.q_fullname;
EEmail = queryInfo?.b_email;
EUsername = queryInfo?.ORIGINAL_USER;
EFullName = queryInfo?.b_fullname;
First some background information
Your code is very inefficient.
queryInfo is an IQueryable<...>, meaning that it holds a query: the potential to fetch some data. It does not hold the data itself.
For this, the IQueryable holds an Expression and a Provider. The Expression represents the query in some generic format. The Provider knows who should execute this query (usually a database management system) and what language is used to communicate with this DBMS.
As long as you concatenate LINQ methods that return IQueryable<TResult>, only the Expression changes. The query is not executed, there is no communication with the DBMS. Concatenating this kind of LINQ methods is efficient.
IQueryable also implements IEnumerable. This means, that to execute the query and to enumerate the fetched sequence, at its lowest level you use GetEnumerator() to get the enumerator, and repeatedly call MoveNext() / Current to access the enumerated element:
IQueryable<TResult> query = dbContext.Students.Where(...).OrderBy(...);
// execute the query:
using (IEnumerator<TResult> enumerator = query.GetEnumerator())
{
// and enumerate the fetched data:
while (enumerator.MoveNext())
{
// There is another element, process it:
TResult fetchedElement = enumerator.Current;
ProcessFetchedElement(fetchedElement);
}
}
Well, this is a lot of code. Usually we use high level methods, which deep inside will call GetEnumerator() / MoveNext() / Current:
// execute the query and process the fetched data:
foreach (TResult fetchedElement in query)
{
ProcessFetchedElement(fetchedElement);
}
All LINQ methods that return IQueryable<...> will not execute the query. The other LINQ methods (= the ones that return List<TResult>, TResult, Boolean, etc, anything not IQueryable) will call foreach or deep inside GetEnumerator / MoveNext / Current. These other methods will contact the database to execute the query.
What does this have to do with my question?
Let's look at your code:
var queryInfo = ...
// queryInfo is an IQueryable. The query is not executed yet!
Name = queryInfo.ToList().ElementAt(0).ToString();
// ToList will execute the query and put all data in a List,
// from this List you take the first element and call ToString()
ID = queryInfo.ToList().ElementAt(1).ToString();
// ToList will execute the query again and put all data in a second List,
// from this List you take the second element and call ToString()
etc. You execute the query 10 times. You join the three tables 10 times, you keep only the one with p.Name equal to IdVal and send the remaining data to your process. You do this 10 times.
It would be much more efficient to do this only once:
// execute the query once and put all fetched data in a List
var fetchedData = queryInfo.ToList();
// access the fetched data. Since it is a List, we can use indexes
Name = fetchedData[0].ToString();
Id = fetchedData[1].ToString();
exchange = fetchedData[2].ToString();
Creation = fetchedData[3].ToString();
I don't think that is what you want.
If I look closer at your query, then I see that you join three tables:
var queryInfo = (from p in table1
join q in table2 on p.TABLEID equals q.USERNAME
join b in table3 on p.ORIGINAL_USER equals b.USERNAME
From the joined table (=sequence of rows), you keep only those rows that have p.Name == idVal:
where p.NAME == IdVal
There might be one such element, there might be more, or maybe none.
From each remaining row, you make one new object:
select new
{
p.NAME,
p.ID,
p.EXCHANGE,
...
});
As said before: the result is a query. The query represents the potential to fetch a sequence of objects with properties Name, Id, Exchange, ....
I think, that you want the Name / Id / Exchange / etc from the first element of the sequence
If that is the case, we don't have to fetch all elements, we only need to fetch the first element (if there is one)
var queryInfo = ...
// execute the query, and ask only for the first element.
// In SQL this is something like SELECT TOP 1 ... FROM ...
var fetchedElement = queryInfo.FirstOrDefault();
Now if your query yields one or more elements, you will have only the first one. However, if your query results in an empty sequence, fetchedElement will be null
if (fetchedElement != null)
{
// There is an element with Name == IdVal
Name = fetchedElement.Name;
Id = fetchedElement.Id;
...
}
else
{
// There is no such element; TODO: report to operator?
}
Be aware, that it if the query yields more than one element, it is not guaranteed what the first element of your sequence might be. Therefore, if you expect that in some cases there might be more than one element, consider to order the sequence, so the first element is defined. For instance order by ascending creation date. Sometimes the DBMS does not accept a FirstOrDefault of an unordered sequence.

Join LINQ-query with predicate

I've some problems with a LINQ query in C#.
I have in the database the same tables that have the same structure.
So, today, I've been troubling with my LINQ query.
More details, I want to join some tables using predicates.
I have a function that has two parameters.
The first parameter is some kind of Context (For example, it may be ProductContext, CarContext, CatContext and etc).
The second parameter is a List<something> that I will join with my first parameter - Context.
I do not want a set of methods.
I've added the sample:
public Element[] GetByIds( MyPredicateContext, Guid[] ids)
{
return
from id in ids
join element in MyPredicateContext on id equals element.Id
select
new Element
{
Id = element.Id,
Description = element.JobDescription,
};
}
If the query is correct, one basic issue that I can see is the return type is Element array whereas you are trying to return IEnumerable. Maybe doing a .ToArray() on the result set might solve the problem.
Why not
return MyPredicateContext.Where(element=>ids.Contains(element.Id))
.Select(e=>new Element()
{
Id = element.Id,
Description = element.JobDescription
}).ToArray();
First of all you can't create a new IQueryable from an array this will revert to pulling everything in memory and filtering there. You are working with expressions and not c# code when you do LINQ with SQL, this will only work on in memory stuff (IEnumerable).
Your query will work in SQL if you do it like this
from element in MyPredicateContext
where ids.Contains(element.Id)
select new Element
{
Id = element.Id,
Description = element.JobDescription,
}
Given that the type of IQueryable where T is an Interface or class.
The end method will look something like this
public interface IElement
{
Guid Id { get; }
string JobDescription { get; }
}
public Element[] GetByIds<T>(IQueryable<T> myPredicateContext, Guid[] ids) where T:IElement
{
return (from element in myPredicateContext
where ids.Contains(element.Id)
select new Element
{
Id = element.Id,
Description = element.JobDescription,
}).ToArray();
}
There are ways to do it with no Generics but they are a bit more advanced and will be hard to maintain.
Here is a method that will work on all T types and proper IQueryable will produce good sql just as I pointed out is a bit more advanced and you will need to lookup how expression work.
public static Element[] GetById<T, Tkey>(IQueryable<T> items,Tkey[] ids)
{
var type = typeof(T);
ParameterExpression param = Expression.Parameter(type);
var list = Expression.Constant(ids);
//The names of the properties you need to get if all models have them and are named the same and are the same type this will work
var idProp = Expression.Property(param, "Id");
var descriptionProp = Expression.Property(param, "JobDescription");
var contains = typeof(Enumerable).GetMethods().First(m => m.Name == "Contains" && m.GetParameters().Count() == 2).MakeGenericMethod(typeof(Tkey));
var where = Expression.Lambda<Func<T, bool>>(Expression.Call(contains, list, idProp), param);
return (items.
Where(where).
Select(Expression.Lambda<Func<T, Element>>(
Expression.MemberInit(
Expression.New(typeof(Element)),
Expression.Bind(typeof(Element).GetProperty("Id"), idProp),
Expression.Bind(typeof(Element).GetProperty("Description"), descriptionProp)),
param))).ToArray();
}
Call GetById(items, new Guid[] { Guid.NewGuid() })

Is there any way to reduce duplication in these two linq queries

Building a bunch of reports, have to do the same thing over and over with different fields
public List<ReportSummary> ListProducer()
{
return (from p in Context.stdReports
group p by new { p.txt_company, p.int_agencyId }
into g
select new ReportSummary
{
PKi = g.Key.int_agencyId,
Name = g.Key.txt_company,
Sum = g.Sum(foo => foo.lng_premium),
Count = g.Count()
}).OrderBy(q => q.Name).ToList();
}
public List<ReportSummary> ListCarrier()
{
return (from p in Context.stdReports
group p by new { p.txt_carrier, p.int_carrierId }
into g
select new ReportSummary
{
PKi = g.Key.int_carrierId,
Name = g.Key.txt_carrier,
Sum = g.Sum(foo => foo.lng_premium),
Count = g.Count()
}).OrderBy(q => q.Name).ToList();
}
My Mind is drawing a blank on how i might be able to bring these two together.
It looks like the only thing that changes are the names of the grouping parameters. Could you write a wrapper function that accepts lambdas specifying the grouping parameters? Or even a wrapper function that accepts two strings and then builds raw T-SQL, instead of using LINQ?
Or, and I don't know if this would compile, can you alias the fields in the group statement so that the grouping construct can always be referenced the same way, such as g.Key.id1 and g.Key.id2? You could then pass the grouping construct into the ReportSummary constructor and do the left-hand/right-hand assignment in one place. (You'd need to pass it as dynamic though, since its an anonymous object at the call site)
You could do something like this:
public List<ReportSummary> GetList(Func<Record, Tuple<string, int>> fieldSelector)
{
return (from p in Context.stdReports
group p by fieldSelector(p)
into g
select new ReportSummary
{
PKi = g.Key.Item2
Name = g.Key.Item1,
Sum = g.Sum(foo => foo.lng_premium),
Count = g.Count()
}).OrderBy(q => q.Name).ToList();
}
And then you could call it like this:
var summary = GetList(rec => Tuple.Create(rec.txt_company, rec.int_agencyId));
or:
var summary = GetList(rec => Tuple.Create(rec.txt_carrier, rec.int_carrierId));
Of course, you'll want to replace Record with whatever type Context.stdReports is actually returning.
I haven't checked to see if that will compile, but you get the idea.
Since all that changes between the two queries is the group key, parameterize it. Since it's a composite key (has more than one value within), you'll need to create a simple class which can hold those values (with generic names).
In this case, to parameterize it, make the key selector a parameter to your function. It would have to be an expression and the method syntax to get this to work. You could then generalize it into a function:
public class GroupKey
{
public int Id { get; set; }
public string Name { get; set; }
}
private IQueryable<ReportSummary> GetReport(
Expression<Func<stdReport, GroupKey>> groupKeySelector)
{
return Context.stdReports
.GroupBy(groupKeySelector)
.Select(g => new ReportSummary
{
PKi = g.Key.Id,
Name = g.Key.Name,
Sum = g.Sum(report => report.lng_premium),
Count = g.Count(),
})
.OrderBy(summary => summary.Name);
}
Then just make use of this function in your queries using the appropriate key selectors.
public List<ReportSummary> ListProducer()
{
return GetReport(r =>
new GroupKey
{
Id = r.int_agencyId,
Name = r.txt_company,
})
.ToList();
}
public List<ReportSummary> ListCarrier()
{
return GetReport(r =>
new GroupKey
{
Id = r.int_carrierId,
Name = r.txt_carrier,
})
.ToList();
}
I don't know what types you have mapped for your entities so I made some assumptions. Use whatever is appropriate in your case.

LINQ query with SELECT and two GROUP-BY condition

What's the equivalent LINQ instruction for a Datatable of the following SQL query:
SELECT code_direction, count(TP) AS CN
FROM table1
WHERE cod_time = 'A011'
GROUP BY TP,code_direction;
and how to get the result into a new datatable?
I tried to convert it but I there're some errors. Someone could take a look on this:
var query = from t in table1.AsEnumerable()
group t by new { t.TP, t.code_direction }
into grp
select new
{
grp.Key.code_direction,
CN = grp.Count(t.TP)
};
foreach (var x in query)
{
Console.Write(x.code_direction);
Console.Write(x.CN);
}
As far as your first question goes. The LINQ equivalent of the SQL query is:
var query = from t in table1.AsEnumerable()
where t.cod_time == "A011"
group t by new { t.TP, t.code_direction }
into grp
select new
{
grp.Key.code_direction,
CN = grp.Count()
};
Note that you don't have to pass any argument to grp.Count(). (For the obvious reason that in SQL COUNT(TP) is the same as COUNT(*), i.e. just count the number of rows. The story would be different if you'd use COUNT(DISTINCT TP) or similar.)
As far as the second question goes, if your query just returned an IEnumerable<T> where T is DataRow (i.e. a query like table1.AsEnumerable().Where(r => r.cod_time == "A011")) then you could just the DataTableExtensions.CopyToDataTable extension method. As your query returns an anonymous type however, you will have to follow these instructions found on MSDN.
I Have been using LINQ to work on a JSON object returned from a remote sharepoint web service. I have posted this because most of the answers I found online were slightly different from what I needed.
a json list of daily activities is returned from a remote sharepoint list & is then summarised using LINQ
The simplified version of a custom object definition is shown below( & which is defined in the models area of an MVC application)
public class MyCustomObjectList
{
public string eventdate { get; set; }
public string userid { get; set; }
public string action { get; set; }
}
The JSON object is serialised into a MyCustomObjectList array.
var customobject = serializer.Deserialize<MyCustomObjectList>(jsonobject);
I wanted to work out how many actions of each type happened on a given day. NB eventdate is stored as a string in format yyyy-mm-dd hh:MM:ss. This was to simplify conversions between c#, JSON & Jquery ( where required I create DateTime objects elsewhere in the code using the
eventdate.
Some will argue this is inefficient, but I prefer to split processes into a sequential set of really simple operations, for the sake of easier debugging & to help other people follow my code. Thats why there are 2 Linq queries .
querya strips out the time component from the eventdate This ensures our later grouping happens by day, & not by second. To be doubly sure that there is no caching, I create it in a new field called actionday. I also rename action to activity, because intellisense was getting confused!! The other columns are copied as is.
var querya =
from c in customobject.rows
select new { actionday = c.eventdate.Substring(0, 10), activity = c.action, c.userid,
c.eventdate };
/* queryb produces a grouped count of querya, grouped on actionday & activity, creating new columns actionkey,ActionCount,Dte,action & DetailList ( which is a summary for debugging purposes)
*/
var queryb=
from p in querya group p by new { p.actionday, p.activity} into idGroup
actionkey = idGroup.Key,
ActionCount = idGroup.Count(),
Dte = idGroup.Key.actionday,
action = idGroup.Key.activity,
DetailList = idGroup
};
Here’s a version that sumarises by 3 columns
var queryc = from p in querya
group p by new { p.actionday, p.userid, p.activity} into idGroup
select new
{
actionday = idGroup.Key,
ActionCount = idGroup.Count(),
userid = idGroup.Key.userid,
Dte = idGroup.Key.actionday,
action = idGroup.Key.activity,
DetailList = idGroup
};

Querying 2 Sets of Complex-Objects Using Linq

I have two lists comprised of different complex-objects, and each one is from 2 separate data-sources. One list may-or-may-not contain records. When any records exist in the "optional" list I need the "normal" list to be further-filtered.
Unfortunately, I can only find very simple examples here and online, which is why I am asking this question.
The Pseudo-Logic Goes Like This:
When QuickFindMaterial records exist, get all DataSource records where query.Name is in the QuickFindMaterial.Material collection. If no QuickFindMaterial records exist do not affect the final result. Lastly, select all distinct DataSourcerecords.
The Classes Looks Like:
public class QuickFindMaterial
{
public string SiteId { get; set; }
public string Material { get; set; }
}
The Code Looks Like:
I have commented-out my failed WHERE logic below
var dataSource = DocumentCollectionService.ListQuickFind();
var quickFindMaterial = ListMaterialBySiteID(customerSiteId);
var distinct = (from query in dataSource
select new
{
ID = query.DocumentID,
Library = query.DocumentLibrary,
ModifiedDate = query.DocumentModifiedDate,
Name = query.DocumentName,
Title = query.DocumentTitle,
Type = query.DocumentType,
Url = query.DocumentUrl,
})
//.Where(x => x.Name.Contains(quickFindMaterial.SelectMany(q => q.Material)))
//.Where(x => quickFindMaterial.Contains(x.Name))
.Distinct();
I think this is what you want:
.Where(x => !quickFindMaterial.Any() || quickFindMaterial.Any(y => x.Name == y.Material))
You could join on Name -> Material
Example:
var distinct = (from query in dataSource
join foo in quickFindMaterial on query.Name equals foo.Material
select new
{
ID = query.DocumentID,
Library = query.DocumentLibrary,
ModifiedDate = query.DocumentModifiedDate,
Name = query.DocumentName,
Title = query.DocumentTitle,
Type = query.DocumentType,
Url = query.DocumentUrl,
}).Distinct();

Categories