I've wired up the MvcMiniProfiler to my app, and it's reporting Duplicate Queries.
I've set a BreakPoint in my Repository
Public Function Read() As System.Linq.IQueryable(Of [Event]) Implements IEventRepository.Read
Dim events = (From e In dc.Events
Select e)
Return events.AsQueryable ''# BREAKPOINT HERE
End Function
And I've hit the page in question.
My code hits the Read() function twice through my service layer (this is by design since I can't figure out how to reduce the calls)
Dim eventcount = EventService.GetHotEventCount() ''# First Hit
Dim eventlist = EventService.GetHotEvents((page - 1) * 5) ''# Second Hit
Dim model As EventsIndexViewModel = New EventsIndexViewModel(eventlist, page, eventcount)
Return View("Index", model)
The EventService does a simple query against the IQueryable Read
Public Function GetHotEvents(ByVal skip As Integer) As List(Of Domain.Event) Implements IEventService.GetHotEvents
Return _EventRepository.Read() _
.Where(Function(e) e.EventDate >= Date.Today AndAlso
e.Region.Name = RegionName) _
.OrderByDescending(Function(e) (((e.TotalVotes) * 2) + e.Comments.Count)) _
.ThenBy(Function(e) e.EventDate) _
.Skip(skip) _
.Take(5) _
.ToList()
End Function
Unfortunately I can't figure out why MiniProfiler is saying there are 8 Duplicate queries (13 in total).
Revised
So it appears as though Sam has stated that I'm not pre-loading my relationships within my queries.
How do I appropriately pre-load relationships in Linq to SQL? Can anyone lend any advice?
Edit
Here's the ViewModel that's being created.
Public Class EventsIndexViewModel
Public Property Events As List(Of Domain.ViewModels.EventPreviewViewModel)
Public Property PageNumber As Integer
Public Property TotalEvents As Integer
Public Property MapEventsList As List(Of Domain.Pocos.MapPin)
Public Property JsonMapEventsList As String
Sub New()
End Sub
Sub New(ByVal eventlist As List(Of Domain.Event), ByVal page As Integer, ByVal eventcount As Integer)
_PageNumber = page
__TotalEvents = eventcount
Dim mel As New List(Of MapPin)
_Events = New List(Of Domain.ViewModels.EventPreviewViewModel)
For Each e In eventlist
_Events.Add(New Domain.ViewModels.EventPreviewViewModel(e))
mel.Add(New MapPin(e.Location.Latitude, e.Location.Longitude, e.Title, e.Location.Name, e.Location.Address))
Next
_MapEventsList = mel
_JsonMapEventsList = (New JavaScriptSerializer()).Serialize(mel)
End Sub
End Class
Edit - added screenshot
You basically have two options to avoid SELECT n+1 with LINQ to SQL:
1) Use DataLoadOptions - http://msdn.microsoft.com/en-us/library/system.data.linq.dataloadoptions.loadwith.aspx
DataLoadOptions enables you to specify per entity exactly that related tables should be eager-loaded. In your case, for the entity Event, you could specify LoadWith for both Comments and Locations. Whenever you load Events, Comments and Locations will then be preloaded.
The DataLoadOptions is a property you can set on the DataContext itself.
2) Use projection to fetch all the data you need in one specific query, instead of relying on lazy loading the related entities.
You have imposed a repository on top of your DataContext, so this might not be the approach you want to take, but:
Instead of selecting a list of Events and then using this entity's properties Comments and Locations, you could have your query return exactly what you need in a specific ViewModel class. LINQ to SQL would then fetch everything in a single SQL query.
I consider this the best approach if you don't absolutely NEED to abstract away the DataContext behind a repository interface. Even if you do, you could consider having the repository return View specific results, i.e.
dc.Events
.Where(something)
.Skip(something)
.Select(event => new EventViewModel
{
Event = event
Locations = event.Locations,
Comments = event.Comments
}
);
with EventViewModel being
public class EventViewModel
{
Event Event;
List<Location> Locations;
List<Comment> Comments;
}
you're going to want to .Include("Locations") and .Include("Comments") in the respective queries. I believe it goes before the .Where(), but I'm not positive about that.
Related
I'm writing a simple ApiController for getting product stocks, but I'm having a strange issue. I get the data from a method that returns a System.Linq.IQueryable (In a library), but I can't apply any of the Linq methods, like Count or ToList(). The import directive is present and doesn't report any problem. Also intellisense shows Data as System.Linq.IQueryable.
The code:
Imports System.Web.Http
Imports System.Linq
Public Class ProductSearchController
Inherits ApiController
Public Function Get(...) As IQueryable
Dim nvc As NameValueCollection = HttpUtility.ParseQueryString(Request.RequestUri.Query)
Dim sEcho As String = nvc("sEcho").ToString()
Dim iDisplayStart As Integer = CType(nvc("iDisplayStart"), Integer)
'Signature of Stock: public IQueryable Stock(...)
Dim Data = New GenericSearches().Stock(...)
Dim Count As Integer = Data.Count() 'Error Here!
Dim result = New DataTableResult With {
.sEcho = sEcho,
.iTotalRecords = Count,
.iTotalDisplayRecords = Count,
.aaData = Data.ToList() 'Error Here!
}
Return result
End Function
End Class
Also, I noticed that error correction and intellisense asks me to choose those methods from a weird Devexpress library something like DevXpress.Data.Helpers.Linq.
The non-generic IQueryable interface doesn't have extension methods of Count and ToList(). Only the generic IQueryable<T> does.
If you can modify the library to return a suitable IQueryable<T>, I suggest you do so. If you can't, you may need to call Cast<T>() or OfType<T>() to create an appropriate IQueryable<T> with the right extension methods.
I'm new to NHibernate and not great at Linq, but this is seriously kicking my butt, and I can't find any really clear examples on SO.
I need to get Thread information from the database, but I need to include a subquery that does a count on the number of Posts on a particular thread.
Here is a SQL Statement
Select ID, ThreadName,
(Select Count(Posts.ID) From Posts Where ThreadID = Threads.ID) as Replies
From Threads
And Class Structure:
Class Thread
Property ID as Integer
Property ThreadName as String
Property Replies as Integer
End Class
Class Post
Property ID as Integer
Property ThreadID as Integer
Property PostText as String
End Class
Any help would be much appreciated. And bonus points to supply both a LINQ example and a one using the NHibernate syntax.
So the query would be like this:
C#
var query =
from thrs in session.Query<YourNamespace.Thread>() // in C# Thread would need
select new YourNamespace.Thread // precise NS to distinguish System.Threading
{
ID = thrs.ID,
ThreadName = thrs.ThreadName,
Replies = thrs.Posts.Count()
};
var list = query.ToList(); // the above statement was executed
VB:
Dim query = From t As Thread In session.Query(Of Thread)()
Select New Thread With {
.ID = t.ID,
.ThreadName= t.ThreadName,
.Replies = t.Posts.Count
}
Dim list as List(of Thread) = query.ToList()
The very important think here is, that the Thread must have a mapping to the collection of Posts
C#
public class Thread
{
...
// really sorry for C# ... I will learn VB syntax ...
public virtual IList<Post> Posts { get; set; }
VB
Public Class Thread
Public Overridable Property Posts As IList(Of Post)
If this collection of Posts, would be mapped in NHibernate, then the above LINQ syntax will work out of the box
I am trying to copy the grid data to one object.
Code :
object obj = GrdReport.ItemsSource;
PrepareDataForStackedChart1(obj);
The function is defined as
private void PrepareDataForStackedChart1(object categoies)
{
var Salespersons = (from cat in categoies
select cat.Salesperson);
}
I am getting error :
Cannot convert from 'lambda expression' to 'System.Linq.Expressions.LambdaExpression'
Can anyone tell me how I can access the object in a linq query?
Edit: Just happened to learn you are using a third party control (from your duplicate question) whose ItemSource property takes System.Object and not IEnumerable. In that case either cast your object (ItemSource) back to the original type, or maintain the original collection you used to bind the control somewhere and pass that collection to your PrepareDataForStackedChart1 method.
Some guess work:
1) Either
object obj = GrdReport.ItemsSource;
PrepareDataForStackedChart1((IEnumerable<Category>)obj);
private void PrepareDataForStackedChart1(IEnumerable<Category> categories)
{
var Salespersons = (from cat in categories
select cat.Salesperson);
}
2) or when you do
GrdReport.ItemsSource = GetCategories();
Copy a back up as well like this:
categories = GetCategories(); //categories is defined in proper scope.
GrdReport.ItemsSource = categories;
And later you do;
PrepareDataForStackedChart1(categories);
private void PrepareDataForStackedChart1(IEnumerable<Category> categories)
{
var Salespersons = (from cat in categories
select cat.Salesperson);
}
3) or may be you will get the collection from Items or Rows property (perhaps) defined on your GridView. Good luck..
Pre edit:
The error is because you cant enumerate a plain object. It has to be enumerable. Why are you passing an object value to your PrepareDataForStackedChart1 method? Instead can't you pass the IEnumerable itself? Try
var categories = GrdReport.ItemsSource.OfType<Category>();
PrepareDataForStackedChart1(categories);
private void PrepareDataForStackedChart1(IEnumerable<Category> categories)
{
var Salespersons = (from cat in categories
select cat.Salesperson);
}
I am assuming you have parent class Category
I have this bit of code that does not work because Entity Framework doesn't recognize the CreateItemDC method. CreateItemDC is a modular private method that creates a data contract for the given Item entity. I use CreateItemDC all throughout my service whenever I need to return an Item data contract, but I can't use it here. I can realize the sequence of ProjectItems into an array or enumerable because I would have to do this to all ProjectItem entities in my database as the query criteria is specified on the client and I don't have access to it here. Do I have any better options here? It seems that RIA Services is not worth the trouble. I'm really wishing I had used plain WCF with this project.
[Query]
public IQueryable<ProjectItemDC> GetProjectItems()
{
return from projectItem in ObjectContext.ProjectItems
select new ProjectItemDC
{
ID = projectItem.ID,
LibraryItem = CreateItemDC(projectItem.LibraryItem),
LibraryItemID = projectItem.LibraryItemID,
ProjectID = projectItem.ProjectID,
Quantity = projectItem.Quantity,
Width = projectItem.Width,
Height = projectItem.Height,
Depth = projectItem.Depth,
SheetMaterialID = projectItem.SheetMaterialID,
BandingMaterialID = projectItem.BandingMaterialID,
MaterialVolume = projectItem.MaterialVolume,
MaterialWeight = projectItem.MaterialWeight
};
}
P.S. I do love LINQ and E.F. though. :)
Well, if you want to go with plain WCF, you can, no problem, just change the code to
[Query(IsComposable=false)]
public IEnumerable<ProjectItemDC> GetProjectItems(string myParm1, string myParm2)
{
return from projectItem in ObjectContext.ProjectItems
select new ProjectItemDC
{
ID = projectItem.ID,
LibraryItem = CreateItemDC(projectItem.LibraryItem),
LibraryItemID = projectItem.LibraryItemID,
ProjectID = projectItem.ProjectID,
Quantity = projectItem.Quantity,
Width = projectItem.Width,
Height = projectItem.Height,
Depth = projectItem.Depth,
SheetMaterialID = projectItem.SheetMaterialID,
BandingMaterialID = projectItem.BandingMaterialID,
MaterialVolume = projectItem.MaterialVolume,
MaterialWeight = projectItem.MaterialWeight
}.ToArray();
}
write your own filtering/sorting logic and you're done.
Yes, you've lost WCF Ria Services dynamic query capabilities, but this is pretty much what you get with plain old WCF, isnt'it ?
If you instead need WCF Ria dynamic sorting/filtering/grouping you must take some additional steps, involving the visit of the Expression that WCF Ria Services create for you.
HTH
You can call ToArray() against ObjectContext.ProjectItems to force EF to load all the items, however, your query will no longer be composable on the client.
[Query]
public IQueryable<ProjectItemDC> GetProjectItems()
{
return from projectItem in ObjectContext.ProjectItems.ToArray()
select new ProjectItemDC
{
ID = projectItem.ID,
LibraryItem = CreateItemDC(projectItem.LibraryItem),
LibraryItemID = projectItem.LibraryItemID,
ProjectID = projectItem.ProjectID,
Quantity = projectItem.Quantity,
Width = projectItem.Width,
Height = projectItem.Height,
Depth = projectItem.Depth,
SheetMaterialID = projectItem.SheetMaterialID,
BandingMaterialID = projectItem.BandingMaterialID,
MaterialVolume = projectItem.MaterialVolume,
MaterialWeight = projectItem.MaterialWeight
};
}
Edit:
As mentioned in your comment, it gets all of the data out of the database at once which is not ideal. In order to create the LibraryItem with your private method, you cannot compose the query on the client. Instead, you should filter within the query method and then create the array.
[Query]
public IQueryable<ProjectItemDC> GetProjectItems(int id, string filter, object blah)
{
var projectItems = ObjectContext.ProjectItems.Where(...).ToArray();
return projectItems.Select(projectItem => new ProjectItemDC{...};
}
I have my project using MVC and I've got my controller that instantiates a service, the service manages the repository and in the repository makes the CRUD operations. The problem is, once a I populate my grid control (Telerik) with the data from my service, if I make an update and I refresh the data, it appears the old data instead of the new one. I think it's a problem of the persistance of my context variable that requires to be disposed/instantiated but not quite sure about when and where (the Unit Of Work variable is located at the service).
<HttpPost()>
<GridAction()>
Function Edit(id As Integer, name As String, clientNo As String, image As String, unit As String) As ActionResult
Try
Dim org = Me._orgService.GetOrgById(id)
With org
.orgNAME = name
.orgCLIENTNO = clientNo
.orgIMAGE = image
.orgUNIT = unit
End With
TryUpdateModel(org)
Me._orgService.EditOrg(org)
Catch ex As Exception
'Log the error
ModelState.AddModelError("", MS_UNABLE_SAVE_CHANGES)
Response.StatusCode = 500
Return Content(String.Join("", (From state In ModelState Select state).SelectMany(Function(s) s.Value.Errors).Select(Function(e) e.ErrorMessage).ToArray()))
End Try
Return View(New GridModel(All()))
End Function
This is the service
Public Sub EditOrg(org As hdmtORG) Implements IOrgService.EditOrg
Me._context.OrgRepository.Edit(org)
Save()
End Sub
This is the repository (generic)
Public Overridable Sub Update(entity As TEntity) Implements IEntityRepository(Of TEntity).Edit
Me._objectSet.Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified)
End Sub
Any idea?
Thanks so much.
Try to use <OutputCache(Duration:=0)> _
<OutputCache(Duration:=0)>
<HttpPost()>
<GridAction()>
Function Edit(id As Integer, name As String, clientNo As String, image As String, unit As String) As ActionResult