How do I optimize this SQL query? - c#

The following query gets all the forum posts of a category. And I also need to display the latest comment time for each post. But the query seem to take more time to make because of it. (which makes sense).
But I am not able to figure out how I can optimize it, and I'm not sure where to look for information on this.
Any tips or solutions to my problem would be greatly appreciated.
Query to get the initial posts for the category
var query = context.ctm_Forum_Post.Where(x => x.Deleted == false && x.FK_Categori_ID == Id)
.Select(x => new ForumPostModel()
{
Id = x.Id,
Title = x.Title,
BodyText = x.BodyText,
Summary = x.Summary,
Archieved = x.Archieved,
Created = x.Created,
Deleted = x.Deleted,
MemberID = x.FK_Member_ID,
Sticky = x.Sticky,
Updated = x.Updated,
CategoryId = x.FK_Categori_ID
}).ToList();
foreach (var item in query)
{
item.LatestCommentTime = this.GetNewestCommentDateByPost(item.Id);
}
return query.OrderByDescending(x=> x.Created);
And for each and every post I have to make one more call to the database.
var query = (from comments in context.ctm_Comments
join posts in context.ctm_Forum_Post on comments.Page_ID equals posts.Id
where posts.Id == id && comments.Deleted == false
orderby comments.Reqistration_timestamp descending
select comments.Reqistration_timestamp).FirstOrDefault();
Comments table
public partial class ctm_Comments
{
public int ID { get; set; }
public int Page_ID { get; set; }
public int Member_ID { get; set; }
public string Comment { get; set; }
public Nullable<System.DateTime> Reqistration_timestamp { get; set; }
public bool Deleted { get; set; }
public Nullable<System.Guid> Page_Guid { get; set; }
}
Post table
public partial class ctm_Forum_Post
{
public int Id { get; set; }
public string Title { get; set; }
public string BodyText { get; set; }
public string Summary { get; set; }
public int FK_Categori_ID { get; set; }
public bool Archieved { get; set; }
public bool Deleted { get; set; }
public bool Sticky { get; set; }
public int FK_Member_ID { get; set; }
public System.DateTime Created { get; set; }
public System.DateTime Updated { get; set; }
public virtual ctm_Forum_Category ctm_Forum_Category { get; set; }
}

Are you trying to pull all in one query? Why don't you pull latest 10-20 posts using paging that will make query fast and then use a separate query to pull comments of that post?
And in case you need to pull large data then I would suggest to use a stored procedure as that is optimized with the fast execution plan.
I am not sure why you are using Join to pull comments, you can add Post ID in Comment field and then use a simple query to pull comments of that post without using Joins unless you are not trying to pull other data from Post table..

try this code.
add Reqistration_timestamp property to ForumPostModel
public class ForumPostModel
{
//other property...............................
public Nullable<System.DateTime> Reqistration_timestamp { get; set; }
}
query
var query = context.ctm_Forum_Posts
.Join(context.ctm_Comments,
post => post.Id,
comment => comment.Page_ID,
(post, comment) => new
{
p = post,
c = comment
}
).Where(x => x.p.Deleted == false && x.p.FK_Categori_ID == Id).OrderByDescending(x => x.c.Reqistration_timestamp).Take(1)
.Select(x => new ForumPostModel()
{
Id = x.p.Id,
Title = x.p.Title,
BodyText = x.p.BodyText,
Summary = x.p.Summary,
Archieved = x.p.Archieved,
Created = x.p.Created,
Deleted = x.p.Deleted,
MemberID = x.p.FK_Member_ID,
Sticky = x.p.Sticky,
Updated = x.p.Updated,
CategoryId = x.p.FK_Categori_ID,
LatestCommentTime = this.GetNewestCommentDateByPost(x.p.Id),
Reqistration_timestamp = x.c.Reqistration_timestamp
}).OrderByDescending(x => x.Created).ToList();

Related

c# Linq Method with GroupBy and Select

I have the following model:
public class HeadCount
{
public int Id { get; set; }
public int ContractId { get; set; }
public string FiscalYear { get; set; }
public string TaskOrder { get; set; }
public decimal ContractHours { get; set; }
public int PersonId { get; set; }
public string CompanyName { get; set; }
public string UserId { get; set; }
public int ClinAssignId { get; set; }
}
Using standard sql, I have the following script I run in SSMS:
select personid, sum(contracthours) as hours from headcounts where personid != 0 group by personid
I am stubbling trying to convert this to a LINQ menthod call. This is what I tried:
var results = _context.HeadCounts
.GroupBy(p => p.PersonId)
.Select(n => new {personid = n.Key, hours = n.Sum(ContractHours)});
It does not like the ContractHours in the Sum. I have even tried n.ContractHours
What am I doing wrong?
Sum takes a selector if the collection is not a primitive numeric collection, so you need:
n.Sum(p => p.ContractHours)
Thanks to Robs comment in the original question, this was the answer I was looking for.

Get List ordered by number of records from another table using Linq

I'm using EF6 and I created this two models:
public class Publication
{
public int Id { get; set; }
public string Name { get; set; }
public List<Product> Products { get; set; }
}
public class ViewLog
{
public int Id { get; set; }
public int? UserId { get; set; }
[ForeignKey("UserId")]
public User.User User { get; set; }
public int? SessionId { get; set; }
[ForeignKey("SessionId")]
public User.Session Session { get; set; }
public int PublicationId { get; set; }
[ForeignKey("PublicationId")]
public Publication.Publication Publication { get; set; }
public DateTime Created { get; set; }
}
Every time I visit a Publication I create a new record into ViewLog table.
Now, using Linq, I need to get all Publications ordered by the number of ViewLogs (visits) per publication in the last 24hs. (If Publication has no ViewLogs they need to appear too, but obviously after the publications that has viewlogs)
You can use GroupJoin when you don't have navigation properties and need a left outer join
The lambda syntax goes like this:
var publicationQuery = new List<Publication>().AsQueryable();
var viewLogQuery = new List<ViewLog>().AsQueryable();
var leftJoinQuery = publicationQuery
.GroupJoin(viewLogQuery, x => x.Id, x => x.PublicationId, (pub, logs) => new
{
PublicationId = pub.Id,
LogCount = logs.Count()
})
.OrderByDescending(x => x.LogCount);
I also found this Query Expression Translation Cheat Sheet very useful to go from query expressions (which are closer to SQL) to lambda method syntax (which I prefer)
If you do have a navigation property Publication.ViewLogs (which is a List<ViewLog>), then you can just use Select() with a projection
var publicationQuery = new List<Publication>().AsQueryable(); // From a DbSet...
var query = publicationQuery
.Select(x => new
{
PublicationId = x.Id,
LogCount = x.ViewLogs.Count
})
.OrderByDescending(x => x.LogCount);

Lookup data from second database from key in first database asp.net mvc

I have the following error when running my get method for a list in my controller:
The specified LINQ expression contains references to queries that are associated with different contexts.
Having debugged the controller it is the orderby statement that produces the error
The Method is:
public ActionResult OwnerList()
{
var owners = (from s in db.Owners
orderby Peopledb.Posts.FirstOrDefault(x => x.PostId == s.PostId).PostName
select s).ToList();
var viewModel = owners.Select(t => new OwnerListViewModel
{
Created = t.Created,
PostName = Peopledb.Posts.FirstOrDefault(x => x.PostId == t.PostId).PostName,
Dormant = t.Dormant,
OwnerId = t.OwnerId,
});
return PartialView("_OwnerList", viewModel);
}
The Class for Owner in the first database is, dbcontext = IARContext:
public class Owner
{
public int OwnerId { get; set; }
[Column(TypeName = "int")]
public int PostId { get; set; }
[Column(TypeName = "bit")]
public bool Dormant { get; set; }
[Column(TypeName = "datetime2")]
public DateTime Created { get; set; }
public virtual ICollection<Asset> Assets { get; set; }
public People.Models.Post Post { get; set; }
}
The Class for Post in the second database is, dbcontext = PeopleContext:
public class Post
{
public int PostId { get; set; }
[StringLength(50)]
[Column(TypeName = "nvarchar")]
public string PostName { get; set; }
[Column(TypeName = "bit")]
public bool Dormant { get; set; }
[StringLength(350)]
[Column(TypeName = "nvarchar")]
public string Description { get; set; }
public virtual ICollection<Contract> Contracts { get; set; }
public virtual ICollection<Owner> Owners { get; set; }
}
I am trying to look up PostName from Post in the People db when displaying a list of Owners from the IAR db
for me the message is quite clear: you are mixing two contexts. That is not possible. This would nearly mean creating an hadhoc dblinq.
The solutions are :
a global context
a separation by code (if the context can't be globalized):
for me you should have
public ActionResult OwnerList()
{
var owners = (from s in db.Owners
//can't order from here without a dbling/global context
select s);
//may be a where is missing here ?
List<DAOSomeName> viewModel = owners.Select(t => new DAOSomeName
{
Created = t.Created,
Dormant = t.Dormant,
OwnerId = t.OwnerId,
});// .ToList(); the materialization is done by the following foreach
//until here, no run to the db, no data transfered.
foreach (DAOSomeName m in viewModel ) {
m.PostName = Peopledb.Posts.Where(x => x.PostId == t.PostId).
Select(x => x.PostName).FirstOrDefault();
//this way you also handle the null case pointed by Trevor
}
//please note that this way, yout view model is not anymore linked
//to the context, except if one property is a navigation property
return PartialView("_OwnerList", viewModel.OrderBy(x => x.PostName));
}
public class DAOSomeName {
public DateTime Created {get; set;}
//Dormant, OwnerId, PostName...
}
By amending my controller to this:
public ActionResult OwnerList()
{
var posts = new List<People.Models.Post>(Peopledb.Posts);
var owners = new List<Owner>(db.Owners);
var ownerposts = (from c in posts
join d in owners on c.PostId equals d.PostId
orderby c.PostName
select new OwnerPost { OwnerId = d.OwnerId, PostName = c.PostName, Created = d.Created, Dormant = d.Dormant }).ToList();
var viewModel = ownerposts.Select(t => new OwnerListViewModel
{
Created = t.Created,
PostName = t.PostName,
Dormant = t.Dormant,
OwnerId = t.OwnerId,
});
return PartialView("_OwnerList", viewModel);
}
and adding a OwnerPost Class:
public class OwnerPost
{
public int OwnerId { get; set; }
public string PostName { get; set; }
public bool Dormant { get; set; }
public DateTime Created { get; set; }
}
I solved the issue

Linq select nested rows

I have the following class:
public class PingtreeTier
{
public BuyerType BuyerType { get; set; }
public int ID { get; set; }
public int MaxRequests { get; set; }
public IEnumerable<PingtreeNode> Nodes { get; set; }
public int Seq { get; set; }
public int Timeout { get; set; }
public bool Weighted { get; set; }
}
As you can see PingtreeTier contains an IEnumerable<PingtreeNode> class. This PingtreeNode class has a property named Status. Using Linq, I need to select only the Tiers/Nodes where PingtreeNode Status = 'Active'.
Anyone help as I'm struggling with the syntax for this.
How about using .Any or .All here:
var results = tiers.Where(t => t.Nodes.Any(n => n.Status == "Active"));
This will select any PingtreeTiers that contain at least one PingTreeNode with Status equal to "Active".
If you wanted to select only PingtreeTiers whose PingTreeNodes are all active, you could use the .All extension method instead:
var results = tiers.Where(t => t.Nodes.All(n => n.Status == "Active"));

How can I get the data from fields of an .Include in a LINQ Query?

I am using Entity Framework 5 and I have these classes. What I want to do is to be able to get the data to populate the view listed below:
public partial class Subject
{
public int SubjectId { get; set; }
public string Name { get; set; }
public virtual ICollection<Topic> Topics { get; set; }
}
public partial class Topic
{
public int TopicId { get; set; }
public string Name { get; set; }
public int SubjectId { get; set; }
public virtual Subject Subject { get; set; }
public virtual ICollection<SubTopic> SubTopics { get; set; }
}
public partial class SubTopic
{
public int SubTopicId { get; set; }
public string Name { get; set; }
public int TopicId { get; set; }
public virtual Topic Topic { get; set; }
}
Now I am trying to write a LINQ query to populate this class:
public class TopicSubTopicSelect
{
public int TopicId { get; set; }
public int SubTopicId { get; set; }
public string TopicName { get; set; }
public string SubTopicName { get; set; }
}
So far I have this:
return _subjectsRepository
.GetAll()
.Where(s => s.SubjectId == subjectId)
.Include(s => s.Topics.SelectMany(t => t.SubTopics))
.AsEnumerable()
.Select(item => new TopicSubTopicSelect(item.TopicId <<<
item.SubTopicId <<
item.Topic.Name <<
item.Name <<))
.ToList();
Can someone tell me how I can get data from the fields I marked with <<. I tried to do .item.Topic.TopicId etc but that does not seem to work.
You shouldn't start from Subject. You just start from SubTopic Repository, and you won't even need to use .Include. Do it like this:
_subTopicRepository
.GetAll()
.Where(s => s.Topic.SubjectId == subjectId)
.Select(s => new TopicSubTopicSelect()
{
TopicId = s.TopidId,
SubTopicId = s.SubTopicId,
TopicName = s.Topic.Name,
SubTopicName = s.Name
})
.ToList();
As I mentioned in my comment on ataravati's answer, you shouldn't actually have a SubTopicRepository so you are correct in starting at SubjectsRepository however you are querying by the Subject ID so you shouldn't be going via GetAll(), you should have a Get(int id) method. The include should be handled as an implementation detail inside Get as the children (SubTopics) are part of the Subject. That makes the method call look like this instead:
return _subjectsRepository
.Get(subjectId)
.SelectMany(subject => subject.SubTopics))
.Select(subTopic => new TopicSubTopicSelect
{
TopicId = subTopic.TopicId,
SubTopicId = subTopic.SubTopicId,
TopicName = subTopic.Topic.Name,
SubTopicName = subTopic.Name
}).ToList();

Categories

Resources