I've got three classes User, Order & Project which are stored in single tables. The orders and projects both have a n:n relation with the users.
To implement that I've got two crosstables (UserOrders, UserProjects) which map these relations.
public class User
{
public string UserID {get;set;}
public List<string> Orders{get;set;}
public List<string> Projects {get;set;}
}
public class Order
{
public string OrderID {get;set}
...
}
public class Project
{
public string ProjectID {get;set}
...
}
As you can see the User object contains a list of every related orderID/projectID.
Now I want to query this with Dapper. I' ve got this solution which works pretty fine with one list. But if I try to query the complete user object for the 2nd list I'll get every result multiplied with the number of results in the first list.
So if a user got 3 orders and 2 projects the orderlist will be fine and the projectlist will contain both projects 3 times:
var lookup = new Dictionary<string, User>();
var multi = dbDapperFM.Query<User, string, string, User>("SELECT u.*, uo.OrderID, up.ProjectID "+
"FROM User u INNER JOIN UserOrders uo ON u.UserID=uo.UserID "+
"INNER JOIN UserProjects up ON u.UserID=up.UserID", (u, uo, up) =>
{
User user;
if (!lookup.TryGetValue(m.UserID, out user))
lookup.Add(u.UserID, user= u);
if (user.Orders == null)
user.Orders = new List<string>();
user.Orders.Add(uo);
if (user.Projects == null)
user.Projects = new List<string>();
user.Projects.Add(up);
return user;
}, splitOn: "UserID , OrderID, ProjectID ").AsQueryable();
I understand why this problem occures (2 inner joins), but I don't really get how to solve it.
I also had trouble coming to grips with the fact that Dapper doesn't do this automatically.
First, I'm not sure about comma-separated values for "splitOn." I thought you could only have one value there. So I have multiple columns in my result set named "ID" for example.
Second, to get the proper 1:N relationships you need to do an extra manual step. For example, I did a 2-table join of participants and their phone numbers. Then I had to do this:
private List<Participant> CollapseResultSet(List<Participant> rawdataset)
{
List<Participant> ret = new List<Participant>();
if (!rawdataset.Any())
{
return ret;
}
else
{
List<string> partIds = rawdataset.Select(p => p.ID).Distinct().ToList();
foreach (string pId in partIds)
{
Participant tmp = rawdataset.Where(p => p.ID == pId).FirstOrDefault();
tmp.PhoneNumbers = rawdataset.Where(p => p.ID == pId).Select(n => n.PhoneNumbers[0]).ToList();
ret.Add(tmp);
}
return ret;
}
}
Hope that helps.
Related
I have a SQL statement like this:
SELECT
projects.name, projects.id,
issues.subject, issues.description,
time_entries.spent_on, time_entries.hours
FROM
(time_entries
INNER JOIN
projects ON time_entries.project_id = projects.id)
INNER JOIN
issues ON time_entries.issue_id = issues.id
WHERE
(((projects.id) IN (26, 27))
AND ((issues.subject) NOT LIKE "*zlecane*"))
AND MONTH(spent_on) = MONTH(CURRENT_TIMESTAMP)
GROUP BY
name, id, subject, spent_on
and I need to write it in Linq.
I write it like this but it doesn't work - it returns an empty list:
ProjectIdsForBudgets - is list with 26 and 27
Projects.Include(x => x.Issues)
.ThenInclude(k => k.TimeEntries)
.Where(x => ProjectIdsForBudgets.Contains(x.Id) &&
x.Issues.Any(y => !y.Subject.Contains("zlecane") &&
y.TimeEntries.Any(K => K.SpentOn >= firstDayOfTheMonth)
)
)
Group by we can skip
Can you help me please?
So you have a sequence of Projects, and a sequence of Issues. Furthermore you have a sequence of TimeEntries where every TimeEntry belongs to exactly one Project using foreign key TimeEntry.ProjectId. Every TimeEntry also belongs to exactly one Issue using foreign key TimeEntry.IssueId
You want to join these three tables on their primary and foreign keys. You only want to keep some elements of the joined results (Where). The remaining elements should be grouped into groups with same name, id, subject and spentOn. Finally you want to select some properties from every group with its elements.
If you have something similar to entity framework, your Project will have a virtual collection of TimeEntries and every TimeEntry has a virtual reference to a Project. If that method is used, entity framework will understand that a join is needed. You can usee the references between the tables.
That is discussed later. First the method using the join three tables
Method: Join three tables
Normally I would use Method syntax. However method syntax looks hideous if you join three tables. Those are the only times I use Query syntax.
I do this in smaller steps. Feel free to make it one big linq
var joinedItems = from timeEntry in timeEntries
join project in project on timeEntry.ProjectId equals project.Id
join issue in issues on timeEntry.IssueId equals issue.Id
select new
{
TimeEntry = timeEntry,
Project = Project,
Issue = Issue,
};
Keep only some of the joined items:
int currentMonth = DateTime.Now.Month;
var subsetJoinedItems = joinedItems.Where(joinedItem =>
(joinedItem.Project.Id == 26 || joinedItem.Project.Id == 27)
&& joinedItem.Issue.Subject.Contains("zlecane") // consider ignore case
&& joinedItem.TimeEntry.SpentOn.Month == currentMonth);
Group the resulting elements into groups of same [name, id, subject, spent_on]:
var groups = subsetJoinedItems.GroupBy(joinedItem => new
{
ProjectId = joinedItem.Project.Id,
ProjectName = joinedItem.Project.Name,
Subject = joinedItem.Issue.Subject,
Spent_on = joinedItem.TimeEntry.SpentOn,
});
Finally, from every group select the items you want to keep:
var result = groups.Select(group => new
{
ProjectId = group.Key.ProjectId,
ProjectName = group.Key.ProjectName,
Subject = group.Key.Subject,
SpentOn = group.Key.SpentOn,
// remaining properties: SQL query seems incorrect
...
});
Using Class Relationships as in entity framework
If you have something similar to entity framework, then your one-to-many relationships would have been implemented using collections:
class TimeEntry
{
public int Id {get; set;}
// every TimeEntry belongs to exactly one Project using foreign key
public int ProjectId {get; set;}
public virtual Project Project {get; set;}
// every TimeEntry belongs to exactly one Issue using foreign key
public int IssueId {get; set;}
public virtual Issue Issue {get; set;}
}
If you have something like this, you don't have to do the joins yourself, entity framework would understand it:
var result = TimeEntries.Where(timeEntry =>
(timeEntry.ProjectId == 26 || timeEntry.ProjectId == 27)
&& timeEntry.Issue.Subject.Contains("zlecane") // consider ignore case
&& TimeEntry.SpentOn.Month == currentMonth)
.GroupBy(timeEntry => new
{
ProjectId = joinedItem.Project.Id,
ProjectName = joinedItem.Project.Name,
Subject = joinedItem.Issue.Subject,
Spent_on = joinedItem.TimeEntry.SpentOn,
})
.Select(group => new
{
ProjectId = group.Key.ProjectId,
ProjectName = group.Key.ProjectName,
Subject = group.Key.Subject,
SpentOn = group.Key.SpentOn,
...
});
I have a table that holds some id's on some different pictures. And with those id's I would like to get the picture id's that matches from another table. But I cant seem to get this to work.
Heres is what I got so far:
public List<Image> ImagesForSession()
{
var userID = User.Identity.GetUserId();
var newestSes = newSes.GetNewestSession(userID);
var imgsOnSes = mtm.GetImagesOnSession(newestSes).Select(i => i.ImgId);
var imgs = imgSes.GetImageOnId(imgsOnSes).Select(x => x);
return imgs.ToList();
}
What I need in the end is to be able to return all the images to a list, so I can use it as a data source for a repeater.
Here is the method where I find all the images that are on the session
public List<MtoMImg> GetImagesOnSession(int sesID)
{
var query = _db.MtoMImgs.Where(i => i.SessionId == sesID).Select(i => i);
return query.ToList();
}
And last, here is the method where I get the images on the id:
public List<Image> GetImageOnId(int ID)
{
var query = _db.Images.Where(i => i.id == ID).Select(i => i);
return query.ToList();
}
Here is a statement that combines them together. You need to update the on clause to match how the tables are related. If you are doing EntityFramework this becomes a whole lot simpler. Because you can probably select the Images directly.
public List<Image> GetImageOnId(int sessionID)
{
return (
from sessionImage in _db.MtoMImgs
join image in _db.Images on sessionImage.ImageId equals image.Id
where sessionImage.SessionId == sessionID
select image
).ToList();
}
Option 2 is you just get the images. Would need to see more of how your data model works, and also would need to be using EF.
return _db.Images.Where(x=>x.SessionId== sessionID).ToList();
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
Maybe I'm going about this the wrong way...
I have an Order table and an OrderItem table. I create a new Order using linq2sql generated classes.
I then attempt to get all orderable items out of my database using a query that goes after various tables.
I try to then create a new list of OrderItem from that query, but it squawks that I can't explicitly create the object.
Explicit construction of entity type OrderItem in query is not allowed.
Here is the query:
return (from im in dc.MasterItems
join c in dc.Categories
on im.CATEGORY equals c.CATEGORY1
select new OrderItem()
{
OrderItemId = im.ItemId
});
The idea is to populate the database with all orderable items when a new order is created, and then display them in a grid for updates. I'm taking the results of that query and attempting to use AddRange on Order.OrderItems
Is there a proper strategy for accomplishing this using linq2sql?
Thanks in advance for your help.
From my understanding of L2S, I don't think you can use explicit construction (in other words new SomeObj() { ... }) in a query because you aren't enumerating the results yet. In other words, the query has just been built, so how are you supposed to do this:
SELECT new OrderItem() FROM MasterItems im JOIN Categories c on c.CATEGORY1 = im.CATEGORY
This is what you're trying to do, which doesn't work because you can't return a POCO (unless you join the OrderItem back somehow and do OrderItem.* somewhere). Ultimately, what you would have to do is just enumerate the collection (either in a foreach loop or by calling ToList()) on the query first and then build your OrderItem objects.
var query = (from im in dc.MasterItems
join c in dc.Categories
on im.CATEGORY equals c.CATEGORY1
select new { MasterItem = im, Category = c});
List<OrderItem> returnItems = new List<OrderItem>();
foreach(var item in query)
{
returnItems.Add(new OrderItem() { OrderItemId = item.MasterItem.ItemId });
}
return returnItems;
OR
return (from im in dc.MasterItems
join c in dc.Categories
on im.CATEGORY equals c.CATEGORY1
select new { MasterItem = im, Category = c})
.ToList()
.Select(tr => new OrderItem() { OrderItemId = tr.MasterItem.ItemId });
Try that out and let me know if that helps.
Expand the order class by creating a partial file where that class OrderItem now has property(ies) which lend itself to business logic needs, but don't need to be saved off to the database.
public partial class OrderItem
{
public int JoinedOrderItemId { get; set; }
public bool HasBeenProcessed { get; set; }
}
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();