I have two entity classes:
public class Invoice
{
public int ID { get; set;}
public int Amount { get { return InvoiceLines.Sum(il => il.Amount); }}
public EntitySet<InvoiceLines> InvoiceLines {get;set;};
}
public class InvoiceLine
{
public Invoice Invoice {get;set;}
public int InvoiceID {get;set;}
public int Amount {get;set;}
public string SomeHugeString {get;set;}
}
(The real classes are sqlmetal generated, I shortened it down here to get to the point).
Querying for all amounts:
var amounts = from i in invoice select i.Amount;
This will cause all invoicelines to be lazy loaded with one database call per invoice. I can solve it with data load options, but that would cause the entire InvoiceLine objects to be read, including SomeHugeString.
Repeating the amount calculation in the query will get a good SQL translation:
var amounts = from i in invoice select i.InvoiceLines.Sum(il => il.Amount);
I sould like to have linq-to-sql somehow get part of the expression tree from a function/property. Is there a way to rewrite Invoice.Amount so that the first amounts query will give the same SQL translation as the second one?
You can do something similar using AsExpandable() from LINQKit:
Expression<Func<Invoice, int>> getAmount =
i => i.InvoiceLines.Sum(il => il.Amount);
var amounts = from i in invoice.AsExpandable() select getAmount.Invoke(i);
You can create your own functions using IQueryable interface.
I've used standard NorthWind DB:
public static class LinqExtensions
{
public static IQueryable<int> CalculateAmounts(this IQueryable<Order> order)
{
return from o in order select o.Order_Details.Sum(i => i.Quantity);
}
}
var amounts = (from o in context.Orders select o).CalculateAmounts();
This code generates such SQL:
SELECT [t2].[value]
FROM [dbo].[Orders] AS [t0]
OUTER APPLY (
SELECT SUM(CONVERT(Int,[t1].[Quantity])) AS [value]
FROM [dbo].[Order Details] AS [t1]
WHERE [t1].[OrderID] = [t0].[OrderID]
) AS [t2]
I'd suggest you set the 'SomeHugeString' property to be lazy loaded. This way you can load InvoiceLine without getting that huge string, which means you can use DataLoadOptions.LoadWith():
Related
I am creating a Xamarin Cross Platform app in VS2017 that uses an SQLite database and I am trying to work out how to perform a query using an inner join. I am using the sqlite-net-pcl NuGet package.
I created the databse using DB Browser for SQLite. I have 2 simple tables.
class Crime
{
[PrimaryKey, AutoIncrement]
public int Id {get; set; }
[NotNull]
public string Name { get; set; }
[NotNull]
public int Legislation { get; set; }
}
class Legislation
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
[NotNull]
public string Title { get; set; }
}
Using DB Browser I have a foreign key set up on Crime.Legislation linked to Legislation.Id.
I am easily able to query a single table and populate a list using the code below.
public partial class LegislationListPage : ContentPage
{
private SQLiteAsyncConnection _connetion;
private ObservableCollection<Legislation> _legislations;
public LegislationListPage ()
{
InitializeComponent ();
_connetion = DependencyService.Get<ISQLiteDb>().GetConnection();
}
protected override async void OnAppearing()
{
await _connetion.CreateTableAsync<Legislation>();
var legislations = await _connetion.Table<Legislation>().OrderBy(x => x.Title).ToListAsync();
_legislations = new ObservableCollection<Legislation>(legislations);
legislationList.ItemsSource = _legislations;
}
}
What I want to do on another page is run a query that returns the Name column from Crime and the Title column from Legislation.
I can achieve this in SQL in DB Browser using
select c.Name, l.Title from Legislation as l inner join Crimes as c on l.Id = c.Legislation
However I don't know how to do this using c# as I have been doing with previous queries. I can't find any .join methods or anything similar. I'm hoping to store the results in a list based on a class that is created on the fly, but I'm not sure if this is possible so I'm happy to create a new class to hold the result of the two strings that will be returned. The end goal is just to populate a list with the results.
If someone could point me in the right direction that would be great.
Thanks in advance.
Ok I managed to find a solution. Quite simple really. I couldn't find a .join method cos i hadn't been 'using' System.Linq.
There are two ways of doing it. Using query syntax and lambda. Lambda is what I was hoping to find so I'm pleased I got it working.
For anyone else looking for something similar here is the two ways I
// First I put both tables into separate lists ( var crimes and var legislation) using the
// way shown in my question. Code for that is omitted here
//Query style
var crimesListItems = from c in crimes
join l in legislations on c.Legislation equals l.Id
select new { c.Name, l.Title };
// Lambda style
var crimesListItems = crimes.Join(
legislations,
c => c.Legislation,
l => l.Id,
(c, l) => new {c.Name, l.Title} );
Credit to Jeremy Clark and his tutorial at https://www.youtube.com/watch?v=tzR2qY6S4yw
I have a function that runs a somewhat complex LINQ query, but I've verified that the simplified code below also has the problem. I specifically tell the query to order by RequiredDate, which is a DateTime. This is completely ignored, however--the sorting actually occurs by another property, PONumber. The database is all random test data, so nothing is ordered except the Id column. I'm not sure why the other property is being used instead of the column I'm trying to sort by. I use Kendo UI, so the IEnumerable is converted to a Kendo type in the controller, but the LINQ to Entities query returns the incorrect order. What is causing this problem?
(simplified versions are below)
Class:
public partial class PurchaseOrder : BaseEntity
{
public virtual int PONumber { get; set; }
public virtual DateTime RequiredDate { get; set; }
}
Mapping:
public PurchaseOrderMap()
{
ToTable("PurchaseOrder");
HasKey(c => c.Id);
Property(u => u.PONumber).IsRequired();
Property(u => u.RequiredDate).IsRequired();
}
Service (this fetches the data):
public virtual IEnumerable<PurchaseOrder> GetAllPOs()
{
var query = _poRepository.Table;
query = query.Where(p => p.Shipment == null);
query = query.OrderBy(p => p.RequiredDate);
return query;
}
Function is called in the controller by this code. DataSourceRequest and DataSourceResult are functions in Kendo UI.
public ActionResult POList([DataSourceRequest]DataSourceRequest request)
{
var pos = _poService.GetAllPOs();
DataSourceResult result = pos.ToDataSourceResult(request, o => PreparePOModelForList(o));
return Json(result);
}
The actual query against the DB (courtesy of SQL Profiler) is:
SELECT
[Extent1].[Id] AS [Id],
[Extent1].[PONumber] AS [PONumber],
[Extent1].[RequiredDate] AS [RequiredDate],
[Extent1].[LastUpdateDate] AS [LastUpdateDate],
FROM [dbo].[PurchaseOrder] AS [Extent1]
ORDER BY [Extent1].[PONumber] ASC
OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY
Based on the OFFSET 0 ROWS FETCH NEXT 10 ROWS ONLY I'm guessing you have some additional logic somewhere which is attempting to apply pagination via the Skip() and Take() methods. My guess is you do some additional sorting there that you are missing. I can't prove that based on the code you have given, but try to figure out what is generating your OFFSET ... FETCH NEXT ... and I suspect you'll find your answer.
Is it possible to Load References when instead of using the code below:
SqlExpression<Customer> q = db.From<Customer>();
q.Join<Customer,CustomerAddress>((cust,address) => cust.Id == address.CustomerId);
List<Customer> dbCustomers = db.LoadSelect(q);
Using this:
public class KpiTotal : IKpiTotal
{
public DateTime Date { get; set; }
public int TeamId { get; set; }
public Team Team { get; set; }
public int AccountId { get; set; }
public Account Account { get; set; }
public double Total { get; set; }
}
var result = dbCon.SelectFmt<KpiTotal>(#"select convert(date, t.TransactionDate) [Date], tm.TeamId,a.AccountNumber, count(distinct(t.RequisitionNumber)) Total
from task.tblTransactions t
inner join task.tblRequisitions r on r.RequisitionNumber = t.RequisitionNumber
inner join task.tblAccounts a on a.AccountNumber = r.AccountNumber
inner join Team tm on tm.DivisionId = a.DivisionId
where t.TransactionTypeNumber = 201 and a.IsActive = 1
and t.TransactionDate between {0} and {1}
group by convert(date, t.TransactionDate), tm.TeamName, a.AccountName
order by 1,2 desc", dateRange.Start, dateRange.End);
Because my result object (KpiTotal) has references to two child tables, and I would like to automatic load the references, instead of getting it with a foreach block.
I'm assuming you want to load in Team and Account from the above query. The LoadSelect method sniffs the POCO model and generates a query that pulls back all related DB records based on the foreign key relationships to the core object you're querying. It generates a query similar to this for each referenced / joined POCO (very pseudo-coded):
SELECT * FROM Team /* Related POCO */
WHERE Team.Id IN (SELECT TeamId FROM [original query with WHERE clase])
Basically, it does a single query to bring back all Teamss or Accounts.
With ServiceStack.OrmLite v4.0.40, there is now a new Merge extension method that will stitch together object references based in a more manual process.
In your case, you can query your KpiTotal results, then run just two separate queries to fetch back Team and Account lists, then merge them in. Basically:
var result = dbCon.SelectFmt<KpiTotal>(/* gnarly SQL */);
var teams = dbCon.Select(/* get all relevant teams */);
var accounts = dbCon.Select(/* get all relevant accounts */);
result.Merge(teams);
result.Merge(accounts);
Debug.WriteLine(result.Dump()); // Output to console / debug window, whatever
I have 2 different classes that represent 2 types of data. The first is the unposted raw format. The second is the posted format.
public class SalesRecords
{
public long? RecordId { get; set; }
public DateTime RecordDate { get; set; }
public string RecordDesc { get; set; }
// Other non-related properties and methods
}
public class PostedSalesRecords
{
public long? CorrelationId { get; set; }
public DateTime RecordDate { get; set; }
public DateTime? PostedDate { get; set; }
public string RecordDesc { get; set; }
// Other non-related properties and methods
}
Our system has a list of sales records. These sales records are posted to a different system at a time determined by the users. I am creating a screen that will show all of the posted sales records along with the unposted sales records as a reconciliation. The datasource for my grid will be a list of PostedSalesRecords. What I need to do is find out which records out of the List<SalesRecords> that are not in List<PostedSalesRecords> and then map those unposted SalesRecords to a PostedSalesRecords. I am having trouble finding a way to quickly compare. Basically I tried this, and it was EXTREMELY slow:
private List<SalesRecords> GetUnpostedSalesRecords(
List<SalesRecords> allSalesRecords,
List<PostedSalesRecords> postedSalesRecords)
{
return allSalesRecords.Where(x => !(postedSalesRecords.Select(y => y.CorrelationId.Value).Contains(x.RecordId.Value))).ToList();
}
My biggest issue is that I am filtering through a lot of data. I am comparing ~55,000 total sales records to about 17,000 posted records. It takes about 2 minutes for me to process this. Any possible way to speed this up? Thanks!
You can try an outer join, please see if this helps with the performance:
var test = (from s in allSalesRecords
join p in postedSalesRecords on s.RecordId equals p.CorrelationId into joined
from j in joined.DefaultIfEmpty()
where j == null
select s).ToList();
Or in your implementation, you can create a dictionary of only Ids for postedSalesRecords and then use that collection in your query, it'll definitely help with performance because the lookup time will be O(1) instead of traversing through the whole collection for each record.
var postedIds = postedSalesRecords.Select(y => y.CorrelationId.Value)
.Distinct().ToDictionary(d=>d);
return allSalesRecords.Where(x => !(postedIds.ContainsKey(x.RecordId.Value))).ToList();
Using a left outer join as described on MSDN should work much more efficiently:
private List<SalesRecords> GetUnpostedSalesRecords(
List<SalesRecords> allSalesRecords,
List<PostedSalesRecords> postedSalesRecords)
{
return (from x in allSalesRecords
join y in postedSalesRecords on x.RecordId.Value
equals y.CorrelationId.Value into joined
from z in joined.DefaultIfEmpty()
where z == null
select x).ToList();
}
This will probably be implemented with a hash set. You could implement this yourself (arguably clearer that way): build a HashSet<long> of the ID values in one or both lists to ensure that you don't need repetitive O(N) lookups each time you go through the outer list.
I have a one class to one table mapping; unfortunately this table has 110+ columns, and queries take a long time process, especially when most of the time I only want to view <10 columns.
My problem is that the queries are dynamically generated based on what the user wants to look at. I can't really create different mappings with different columns because there would be a very large number of combinations. I'm using the criteria API to generate the queries. Can I also use this to only select the columns the user wants? Or some other method?
Thanks
Easy to do with LINQ (assuming you're using NHibernate 3.0 or later):
var products = from p in Session.Query<Product>()
where // ...some query (snip)
select new
{
Name = p.ProductName,
Description = p.ShortDesc,
Price = p.Price,
Units = p.Quantity
};
Also, if you're using HQL, you can just select the columns you need similar to using T-SQL, but use a Transformer to get a strongly typed object back:
First create a class with your narrowed down columns:
public class ProductReport
{
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public int Units { get; set; }
}
Then your query:
string hql = "select p.ProductName as Name, p.ShortDesc as Description ...(snip) " +
"from Product p " +
"where ...some query (snip)";
IQuery query = Session.CreateQuery(hql)
.SetResultTransformer(Transformers.AliasToBean<ProductReport>());
IList<ProductReport> products = query.List<ProductReport>();
Just sure make the aliases in your query (as Name, as Description etc.) match the property names in your class.
In addition to the example Tim gave you can do something like this:
IList selection =
session.QueryOver<Cat>()
.Select(
c => c.Name,
c => c.Age)
.List<object[]>();
Above example was taken from: http://nhforge.org/blogs/nhibernate/archive/2009/12/17/queryover-in-nh-3-0.aspx
Use a ProjectionList to select the columns you want. See here for the examples.