I want to write a query which should get an user object and the amount of messages the user has posted already. I did this the following way:
var query = (from u in _db.Repository<User>()
where u.IsDeleted != true
select new UserWithMessagecount()
{
User = u
MessageCount = GetUserMessageCount(u.Documents).Count(),
});
I'm using a method because some messages should be filtered out (in a dynamic way).
To keep things simple I'll post the function without sorting logic (which still produces the same error).
private EntitySet<Document> GetUserMessageCount(EntitySet<Document> set)
{
return set;
}
The error returned is:
Method 'x' has no supported translation to SQL.
Any ideas on how to fix this issue?
use this syntax instead:
var query = (from u in _db.Repository<User>()
let MessageCount = GetUserMessageCount(u.Documents).Count()
where u.IsDeleted != true
select new UserWithMessagecount()
{
User = u,
MessageCount = MessageCount
});
Linq-to-SQL will be trying to convert your entire statment into SQL, an of course there is no GetUserMessageCount() available.
You will need to take the results of the SQL query by enumerating it -- then apply the C# side logic.
What you need to do is to use grouping in your projection.
var query = from u in _db.Repository<User>()
where !u.IsDeleted
group u by u.UserId into g
select new UserWithMessageCount {
User = g.First(x => x.UserId == g.Key),
MessageCount = g.Sum(x => x.Messages.Count())
}
This should work.
Related
I am currently using a linq and accessing some meta data using a for loop:
public SignUpMeta GetSignUpMeta(User user)
{
var users = (from u in User
where user.Email == u.Email || user.UserName == u.UserName
select u);
var result = new SignUpMeta();
foreach (var u in users)
{
if (user.Email == u.Email) result.IsDuplicateEmail = true;
if (user.UserName == u.UserName) result.IsDuplicateUserName = true;
}
return result;
}
Is there any way for the Linq to generate the SignUpMeta directly?
This function is inside the DBContext class and I am looking for a way to get the exact data directly from the db (without raw sql).
Update:
The User is DbSet and the whole code runs inside the db context class. I am trying to write something that will make EF fetch the value directly in a single query.
Update 2:
The SQL equivalent of what I am looking for would be:
SELECT MAX(username), MAX(email)
(SELECT CAST((UserName = #user) AS bit) username,
CAST((Email = #email) AS bit) email
FROM User WHERE UserName = #user OR Email = #email)
Update 3:
SignUpMeta object that needs to be fetched contains the metadata that provides information required for server side validation.
The above C# code runs a query that fetches up to two columns in this instance. When there are more such conditions, there would be more lines. I am trying to find a way that EF would give only the two booleans alone in a single record.
This'll be my try, if you truly must use LINQ:
from u in stuff.Users
group u by 0 into grp
select new
{
IsDuplicateEmail = grp.Any(x => x.Email == user.Email),
IsDuplicateUserName = grp.Any(x => x.UserName == user.UserName)
}
Entity Framework will translate that into sub-selects. If you're using SQL Server and have both columns indexed, this should result in the same amount of I/O as your sample SQL.
I don't believe there is any query that will generate your desired sample SQL.
I think this will be the fastest query:
public SignUpMeta GetSignUpMeta(User user)
{
return new SignUpMeta()
{
IsDuplicateEmail = User.Where(u => u.Email == user.Email).Take(1).Any(),
IsDuplicateUserName = User.Where(u => u.UserName == user.UserName).Take(1).Any(),
};
}
Caching on the DB server should make the two queries quite fast.
I've got a table Installation which can contains one or many Equipements.
And for functionnal reasons, I've overwritten my table Installation and added a field NbrEquipements.
I want to fill this field with Linq, but I'm stuck...
Due to special reasons, there is no relation between these to tables. So, no Installation.Equipements member into my class. Therefore, no Installation.Equipements.Count...
I'm trying some stuff. Here is my code:
var query = RepoInstallation.AsQueryable();
// Some filter
query = query.Where(i => i.City.RegionId == pRegionId));
int?[] etatIds = { 2, 3 };
query = (from i in query
select new Installation
{
NbrEquipements= (from e in RepoEquipement.AsQueryable()
where e.InstallationSpecialId == i.SpecialId
&& (etatIds.Contains(e.EquEtat))
select e.SasId
).Count()
});
But with this try, I got this error:
The entity or complex type 'myModel.Installation' cannot be constructed in a LINQ to Entities query
I've tried some other stuff but I'm always turning around...
Another thing that can be useful for me: It would be great to fill a field called Equipements which is a List<Equipement>.
After that, I would be able to Count this list...
Is it possible ?
Tell me if I'm not clear.
Thanks in advance.
Here is the final code:
//In the class:
[Dependency]
public MyEntities MyEntities { get; set; }
//My Methode code:
var query = MyEntities .SasInstallations.AsQueryable();
// Some filter
query = query.Where(i => i.City.RegionId == pRegionId));
var liste = new List<Installation>();
var queryWithListEquipements =
from i in query
select new
{
Ins = i,
EquipementsTemp = (from eq in MyEntities.Equipements.AsQueryable()
where eq.SpecialId == i.SpecialId
&& (etatIds.Contains(eq.SasEquEtat))
select eq
).ToList()
};
var listWithListEquipements = queryWithListEquipements.ToList();
foreach (var anonymousItem in listWithListEquipements)
{
var ins = anonymousItem.Ins;
ins.Equipements = anonymousItem.EquipementsTemp;
ins.NumberEquipements = ins.Equipements.Count();
liste.Add(ins);
}
return liste;
By the way, this is very very fast (even the listing of Equipements). So this is working exactly has I wished. Thanks again for your help everyone!
Use an anonymous type. EF does not like to instantiate entity classes inside a query.
var results = (from i in query
select new
{
NbrEquipements= (from e in RepoEquipement
where e.InstallationSpecialId == i.SpecialId
&& (etatIds.Contains(e.EquEtat))
select e.SasId
).Count()
})
.ToList();
Notice how I used select new instead of select new Installation.
You can then use the data inside the list (which is now in memory) to create instances of type Installation if you want like this:
var installations = results.Select(x =>
new Installation
{
NbrEquipements = x.NbrEquipements
}).ToList();
Here is how to obtain the list of equipment for each installation entity:
var results = (from i in query
select new
{
Installation = i,
Equipment = (from e in RepoEquipement
where e.InstallationSpecialId == i.SpecialId
&& (etatIds.Contains(e.EquEtat))
select e).ToList()
})
.ToList();
This will return a list of anonymous objects. Each object will contain a property called Installation and another property called Equipment (which is a list). You can easily convert this list (of anonymous objects) to another list of whatever type that you want.
I am trying to pull the results from the database and set the child property while selecting using the Linq (EF V5.0). The reason I am doing this is because there is no relation in the database to use include..
var lamdaResult = from u in model.Entity_Users
join s in model.Entity_Staff on u.UserID equals s.ST_UserID
select new { u, s };
return lamdaResult.Select(x => x.u.Staff = x.s; return x.u;).FirstOrDefault();
I am learning Linq.. the above expression is giving me error.. can someone help me the best way to set the child property...
I could also do this.. but I am wondering is there any better way to fulfill the same result instead of following 2 expressions
var user=null;
var lamdaResult = from u in model.Entity_Users
join s in model.Entity_Staff on u.UserID equals s.ST_UserID
select new { u, s };
user = lamdaResult.Select(x => x.u).FirstOrDefault();
user.Staff = lamdaResult.Select(x => x.s).FirstOrDefault();
Linq is for querying, not for mutating objects. You'll need to use something other than LINQ to do the mutation, generally a foreach is appropriate, although given that you only have a single item, there's no need for even that:
var item = (from u in model.Entity_Users
join s in model.Entity_Staff on u.UserID equals s.ST_UserID
select new { User = u, Staff = s })
.FirstOrDefault();
item.User.Staff = item.Staff;
return item.User;
i'm very new to linq to sql and in need of a little assistance.
Basically i'm building a message board in C#. I have 3 database tables - basic info is as follows.
FORUMS
forumid
name
THREADS
threadid
forumid
title
userid
POSTS
postid
threadid
text
userid
date
Basically I want to bring back everything I need in one query. I want to list a page of THREADS (for a particular FORUM) and also display the number of POSTS in that THREAD row and when the last POST was for that THREAD.
At the moment i'm getting back all THREADS and then looping through each the result set and making calls to the POST table seperately for the POST count for a Thread and the Latest Post in that thread but obviously this will cause problems in terms of hitting the database as the Message Board gets bigger.
My Linq To SQL so far:
public IList<Thread> ListAll(int forumid)
{
var threads =
from t in db.Threads
where t.forumid == forumid
select t;
return threads.ToList();
}
basicaly i now need to get the number of POSTS in each thread and the date of the last post in each thread.
Any help would be most appreciated :)
EDIT
Hi guys. Thanks for tyour help so far. Basically i'm almost there. However, I left an important part out of my initial question in the fact that I need to retrieve the user name of the person making the last POST. Therefore I need to join p.userid with u.userid on the USERS table. So far I have the following but just need to amend this to join the POST table with the USER table:
public IList<ThreadWithPostInfo> ListAll(int forumid)
{
var threads = (from t in db.Threads
where t.forumid == forumid
join p in db.Posts on t.threadid equals p.threadid into j
select new ThreadWithPostInfo() { thread = t, noReplies = j.Count(), lastUpdate = j.Max(post => post.date) }).ToList();
return threads;
}
UPDATE:
public IList<ThreadWithPostInfo> ListAll(int forumid)
{
var threads = (from t in db.Threads
from u in db.Users
where t.forumid == forumid && t.hide == "No" && t.userid == u.userid
join p in db.Posts on t.threadid equals p.threadid into j
select new ThreadWithPostInfo() { thread = t, deactivated = u.deactivated, lastPostersName = j.OrderByDescending(post => post.date).FirstOrDefault().User.username, noReplies = j.Count(), lastUpdate = j.Max(post => post.date) }).ToList();
return threads;
}
I finally figured that part of it out with thanks to all of you guys :). My only problem now is the Search Results method. At the moment it is like this:
public IList<Thread> SearchThreads(string text, int forumid)
{
var searchResults = (from t in db.Threads
from p in db.Posts
where (t.title.Contains(text) || p.text.Contains(text)) && t.hide == "No"
&& p.threadid == t.threadid
&& t.forumid == forumid
select t).Distinct();
return searchResults.ToList();
}
Note that I need to get the where clause into the new linq code:
where (t.title.Contains(text) || p.text.Contains(text)) && t.hide == "No"
so incorporating this clause into the new linq method. Any help is gratefully received :)
SOLUTION:
I figured out a solution but I don't know if its the best one or most efficient. Maybe you guys can tell me because i'm still getting my head around linq. James I think your answer was closest and got me to near to where I wanted to be - thanks :)
public IList<ThreadWithPostInfo> SearchThreads(string text, int forumid)
{
var searchResults = (from t in db.Threads
from p in db.Posts
where (t.title.Contains(text) || p.text.Contains(text)) && t.hide == "No"
&& p.threadid == t.threadid
&& t.forumid == forumid
select t).Distinct();
//return searchResults.ToList();
var threads = (from t in searchResults
join p in db.Posts on t.threadid equals p.threadid into j
select new ThreadWithPostInfo() { thread = t, lastPostersName = j.OrderByDescending(post => post.date).FirstOrDefault().User.username, noReplies = j.Count(), lastUpdate = j.Max(post => post.date) }).ToList();
return threads;
}
May be Too many database calls per session ....
Calling the database,. whether to query or to write, is a remote call, and we want to reduce the number of remote calls as much as possible. This warning is raised when the profiler notices that a single session is making an excessive number of calls to the database. This is usually an indication of a potential optimization in the way the session is used.
There are several reasons why this can be:
A large number of queries as a result of a Select N + 1
Calling the database in a loop
Updating (or inserting / deleting) a large number of entities
A large number of (different) queries that we execute to perform our task
For the first reason, you can see the suggestions for Select N + 1. Select N + 1 is a data access anti-pattern where the database is accessed in a suboptimal way. Take a look at this code sample :
// SELECT * FROM Posts
var postsQuery = from post in blogDataContext.Posts
select post;
foreach (Post post in postsQuery)
{
//lazy loading of comments list causes:
// SELECT * FROM Comments where PostId = #p0
foreach (Comment comment in post.Comments)
{
//print comment...
}
}
In this example, we can see that we are loading a list of posts (the first select) and then traversing the object graph. However, we access the collection in a lazy fashion, causing Linq to Sql to go to the database and bring the results back one row at a time. This is incredibly inefficient, and the Linq to Sql Profiler will generate a warning whenever it encounters such a case.
The solution for this example is simple. Force an eager load of the collection using the DataLoadOptions class to specify what pieces of the object model we want to load upfront.
var loadOptions = new DataLoadOptions();
loadOptions.LoadWith<Post>(p => p.Comments);
blogDataContext.LoadOptions = loadOptions;
// SELECT * FROM Posts JOIN Comments ...
var postsQuery = (from post in blogDataContext.Posts
select post);
foreach (Post post in postsQuery)
{
// no lazy loading of comments list causes
foreach (Comment comment in post.Comments)
{
//print comment...
}
}
next is updating a large number of entities is discussed in Use Statement Batching, and can be achieved by using the PLinqO project, which is a set of extensions on top of Linq to Sql. How cool would it be to store items in cache as a group. Well, guess what! PLINQO is cool! When storing items in cache, just tell PLINQO the query result needs to belong to a group and specify the name. Invalidating cache is where the coolness of grouping really shows up. No coupling of cache and actions taken on that cache when they are in a group. Check out this example :
public ActionResult MyTasks(int userId)
{
// will be separate cache for each user id, group all with name MyTasks
var tasks = db.Task
.ByAssignedId(userId)
.ByStatus(Status.InProgress)
.FromCache(CacheManager.GetProfile().WithGroup("MyTasks"));
return View(tasks);
}
public ActionResult UpdateTask(Task task)
{
db.Task.Attach(task, true);
db.SubmitChanges();
// since we made an update to the tasks table, we expire the MyTasks cache
CacheManager.InvalidateGroup("MyTasks");
}
PLinqO supports the notion of query batching, using a feature called futures, which allow you to take several different queries and send them to the database in a single remote call. This can dramatically reduce the number of remote calls that you make and increase your application performance significantly.
cmiiw ^_^
public IList<Thread> ListAll(int forumid)
{
var threads =
from t in db.Threads
where t.forumid == forumid
select new
{
Thread = t,
Count = t.Post.Count,
Latest = t.Post.OrderByDescending(p=>p.Date).Select(p=>p.Date).FirstOrDefault()
}
}
Should be something like that
I think what you're really looking for is this:
var threadsWithPostStats = from t in db.Threads
where t.forumid == forumid
join p in db.Posts on t.threadid equals p.threadid into j
select new { Thread = t, PostCount = j.Count(), LatestPost = j.Max(post => post.date) };
Per your comment and updated question, I'm adding this restatement:
var threadsWithPostsUsers = from t in db.Threads
where t.forumid == forumid
join p in db.Posts on t.threadid equals p.threadid into threadPosts
let latestPostDate = threadPosts.Max(post => post.date)
join post in db.Posts on new { ThreadID = t.threadid, PostDate = latestPostDate } equals new { ThreadID = post.threadid, PostDate = post.date} into latestThreadPosts
let latestThreadPost = latestThreadPosts.First()
join u in db.Users on latestThreadPost.userid equals u.userid
select new { Thread = t, LatestPost = latestThreadPost, User = u };
Wouldn't hurt to get familiar with group by in LINQ and aggregates (Max, Min, Count).
Something like this:
var forums = (from t in db.Threads
group t by t.forumid into g
select new { forumid = g.Key, MaxDate = g.Max(d => d.ForumCreateDate) }).ToList();
Also check out this article for how to count items in a LINQ query with group by:
LINQ to SQL using GROUP BY and COUNT(DISTINCT)
LINQ aggregates:
LINQ Aggregate with Sub-Aggregates
I have the following LINQ query:
var allocations =
from ta in dc.TransactionAllocations
where ta.Allocated == false
group ta by new { ta.ReceiptReference, ta.Customer } into tag
select new
{
Customer = tag.Key.Customer,
ReceiptReference = tag.Key.ReceiptReference,
Invoices = tag.ToDictionary(a => new AllocationDictionaryKey()
{
ID = a.ID,
InvoiceReference = a.InvoiceReference
},
a => a.Amount)
}
But when I try to execute this, the ToDictionary call fails as it's not a supported LINQ-to-SQL operator. The only way around this I have seen is to call ToDictionary at the end of the query, but I only want one property of my anonymous type to be a dictionary!
Any ideas on how to go about doing this?
Have a look at using AsEnumerable. This is designed to get round operators that are not supported by a specific platform. It means that the data will be processed where the code is rather than where the data is though.
Invoices = tag.AsEnumerable().ToDictionary(a => new AllocationDictionaryKey() { ID = a.ID, InvoiceReference = a.InvoiceReference }, a => a.Amount)
Quite old, but here goes.
I solved my problem with
from ta in dc.TransactionAllocations.AsEnumerable()
i.e. directly making the datatable as Enumerable.