Entity Framework generated sql restrict related records - c#

I have the following linq select query that loops round all the 'Places' linked to 'adrianMember' that start with B, I only want to show the PlaceName. I have a navigation association from Member to Place but not from Place to Member.
using (var fdb = new FALDbContext())
{
var adrianMember = fdb.Members.Find(1);
foreach (string s in adrianMember.Places.Where(p=>p.PlaceName.StartsWith("B")).Select(p => p.PlaceName))
{
Console.WriteLine("- " + s);
}
}
I have experimented with various linq syntax too, for example not using Find...
var adrianMember = fdb.Members.Where(m => m.MemberId == 1).FirstOrDefault();
and providing two linq queries, one to retrieve the member and then later retrieve the related places (and hopefully making the EF do some lazy deferred loading) but that still results in very inefficient sql.
using (var fdb = new FALDbContext())
{
//Need the FirstOrDefault otherwise we will return a collection (first or default will return the inner collection
//could have multiple members with multiple places
var members = fdb.Members.Where(m=>m.FirstName == "Bob");
foreach (var member in members)
{
var places = member.Places.Where(p => p.PlaceName.StartsWith("B")).Select(p => p.PlaceName);
foreach (var place in places)
{
Console.WriteLine(place);
}
}
}
The SQL output gets all rows and all columns
exec sp_executesql N'SELECT
[Extent1].[PlaceId] AS [PlaceId],
[Extent1].[PlaceName] AS [PlaceName],
[Extent1].[PlaceLocation] AS [PlaceLocation],
[Extent1].[Member_MemberId] AS [Member_MemberId]
FROM [dbo].[Places] AS [Extent1]
WHERE [Extent1].[Member_MemberId] = #EntityKeyValue1',N'#EntityKeyValue1 int',#EntityKeyValue1=1
Is there a way to restrict the sql to something like
SELECT PlaceName FROM Places WHERE MemberId = 1 AND PlaceName like 'B%'
I have a couple of situations in my project where the above generated sql will make the query too slow (20,000 records per member over 20 columns). Is linq clever enough to make the change if I do have more records?

Try this:
using (var fdb = new FALDbContext())
{
var members = fdb.Members.Where(m=>m.FirstName == "Bob");
foreach (var member in members)
{
fdb.Places.Where(p => p.PlaceName.StartsWith("B") && p.MemberId == member.Id).Select(p => p.PlaceName);
foreach (var place in places)
{
Console.WriteLine(place);
}
}
}
There is a similar question here

After a month away from this problem I thought I'd take a second look...
Firstly, I'm not really sure what I wanted to achieve!!! However, I do seem to have found some interesting and more efficient sql queries for what I wanted.
My first option is to use explicit loading using the Entry & Collection methods. This creates a SQL query to retrieve each corresponding Place record and, importantly, projects the columns I want (just the description) and restricts the rows (those Places beginning with L). This does create many queries though if I have many associated places but I can do this on the fly if I don't know which Places I want to retrieve.
using (var fdb = new FALDbContext())
{
foreach (var member in fdb.Members)
{
var places = fdb.Entry(member)
.Collection(m => m.Places)
.Query()
.Where(p => p.PlaceName.StartsWith("L"))
.Select(p => p.PlaceName);
foreach (var place in places)
{
Console.WriteLine(place);
}
}
}
The second option is to use lazy loading but specify a tightly controlled bit of LINQ to Entities.
This goes to the database once and retrieves just the members I want and projects and restricts both tables and all in one lovely efficient looking sql query!
using (var fdb = new FALDbContext())
{
var myList = fdb.Members
.Where(m => m.GenderShareWithId > 0)
.Select(m => new { m.MemberId, m.DisplayName, Places = m.Places.Where(p => p.PlaceName.StartsWith("L")).Select(p => p.PlaceName) });
foreach (var item in myList)
{
Console.WriteLine(item.DisplayName);
foreach (var place in item.Places)
{
Console.WriteLine(" - " + place);
}
}
}
SELECT
[Extent1].[MemberId] AS [MemberId],
[Extent1].[DisplayName] AS [DisplayName],
[Extent2].[PlaceName] AS [PlaceName]
FROM [dbo].[Members] AS [Extent1]
LEFT OUTER JOIN [dbo].[Places] AS [Extent2]
ON ([Extent1].[MemberId] = [Extent2].[Member_MemberId]) AND ([Extent2].[PlaceName] LIKE N'L%')
WHERE [Extent1].[GenderShareWithId] > 0

Related

LINQ troubles in C# using Entity Framework

I have a few tables and this is what I need to achieve.
This gets all the rows from one table
var FRA = from prod in _cctDBContext.Fra
where prod.ActTypeId == 1
From within that, I get all the rows where ActTypeID.
Then I need to query another table from with the ID's get from that
foreach (var item in FRA)
{
var FRSA = _cctDBContext.Frsa
.Select(p => new { p.Fraid, p.Frsa1,
p.Frsaid, p.CoreId,
p.RelToEstId, p.ScopingSrc,
p.Mandatory })
.Where(p => p.Fraid == item.Fraid)
.ToList();
}
I then need to push each one of these to Entity Framework. I usually do it this way:
foreach (var item in FRA)
{
var FinanicalReportingActivity = new FinancialReportingActivity { FinancialReportingActivityId = item.Fraid, ScopingSourceType = item.ScopingSrc, Name = item.Fra1, MandatoryIndicator = item.Mandatory, WorkEffortTypeId = 0 };
_clDBContext.FinancialReportingActivity.AddRange(FinanicalReportingActivity);
}
But because I have used 2 for each loops, I cannot get the variables to work because I cannot find a way to get local variables as the entity context.
Can anyone think of a better way to code this?
Thanks
It looks like you can do this as a single join:
var query =
from prod in _cctDBContext.Fra
where prod.ActTypeId == 1
join p in _cctDBContext.Frsa on prod.Fraid equals p.Fraid
select new
{
p.Fraid,
p.Frsa1,
p.Frsaid,
p.CoreId,
p.RelToEstId,
p.ScopingSrc,
p.Mandatory
};
It looks like you are loading data from one set of entities from one database and want to create matching similar entities in another database.
Navigation properties would help considerably here. Frsa appear to be a child collection under a Fra, so this could be (if not already) wired up as a collection within the Fra entity:
Then you only need to conduct a single query and have access to each Fra and it's associated Frsa details. In your case you look to be more interested in the associated FRSA details to populate this ReportingActivity:
var details = _cctDBContext.Fra
.Where(x => x.ActTypeId == 1)
.SelectMany(x => x.Frsa.Select(p => new
{
p.Fraid,
p.Frsa1,
p.Frsaid,
p.CoreId,
p.RelToEstId,
p.ScopingSrc,
p.Mandatory
}).ToList();
though if the relationship is bi-directional where a Fra contains Frsas, and a Frsa contains a reference back to the Fra, then this could be simplified to:
var details = _cctDBContext.Frsa
.Where(x => x.Fra.ActTypeId == 1)
.Select(p => new
{
p.Fraid,
p.Frsa1,
p.Frsaid,
p.CoreId,
p.RelToEstId,
p.ScopingSrc,
p.Mandatory
}).ToList();
Either of those should give you the details from the FRSA to populate your reporting entity.

Create a LINQ query comparing arrays

I'm very new to LINQ and I'm searching to filter an SQL view which has a column called 'Readers' containing several group names separated by '#' (es. "Administrators#HR Group#Employees Group").
Having a list of user's groups, I need to extract all the records where Readers contains at least one of the user's groups.
In other words, the user must see only those records belonging to him.
I found this solution, but I think it's extremely inefficient:
private List<vwFax>getmyFaxes(List<string> myGroups)
{
var myFax = db.vwFax.AsQueryable();
var res = db.vwFax.AsQueryable();
List<vwFax> outRes= new List<vwFax>();
foreach (string elem in myGroups)
{
res = (from a in myFax
where a.Readers.Contains(elem)
select a);
if(res.Count() > 0)
{
outRes.AddRange(res);
}
}
return outRes.ToList();
}
Any help please?
Basically what you are saying in the following query is: For each item in myFax take it only if that item.Readers contains Any (at least 1) of the items in myGroups
outRes = db.myFax.Where(item => myGroups.Any(grp => item.Readers.Contains(grp)));
and in query-syntax:
outRes = from item in db.myFax
where myGroups.Any(grp => item.Readers.Contains(grp))
select item;

How can I efficiently query against a tree structure stored in a database using Entity Framework?

I have a table of elements that can be associated to each other (within the same table).
So for example a structure like:
[Id] Int
[ParentWithinThisTableId] Int
[AFieldToQueryAgainst] NVarCharMax
At the beginning of the search, I will be given a string query and a single Id (call it StartId that points to an element in this table. What I want to do is compare the string query to the [AFieldToQueryAgainst] on the element at the Id given, but also query against the same column for all rows that have [ParentWithinThisTableId] == StartId and then all of the rows that have these ids as [ParentWithinThisTableId].
The only way I can think to do it is recursively like:
var forms = db.Forms.Where(m => m.ParentWithinThisTableId == this.Id);
var searchMatches = new List<Form>();
foreach (var form in forms)
{
forms = forms.Concat(AddSearches(query, form.Id));
}
foreach (var form in forms)
{
if (form.AFieldToQueryAgainst.Contains(query))
{
searchMatches.Add(form);
}
}
With AddSearches being recursive like:
private IQueryable<Form> AddSearches(string query, int startId)
{
var items = RepositoryProxy.Context.Forms.Where(m => m.ParentWithinThisTableId == startId);
foreach (var item in items)
{
items = items.Concat(AddSearches(query, item.Id));
}
return items;
}
But this takes about 1.8 seconds run time in my testing on a relatively shallow tree. Is there a more efficient way to do this? Possibly avoid iterating each of the IQueryables until they're all compiled somehow?
why can't you just use a self join ?
var these = (from p in items
join x in items on p.Id equals x.ParentId
where x != null
select x).ToList();

C# List grouping and assigning a value

I have a list of Orders. This list contains multiple orders for the same item, see the table below.
I then want to assign each item that is the same (i.e. ABC) the same block ID. So ABC would have a block ID of 1 & each GHJ would have a block ID of 2 etc. What is the best way of doing this?
Currently I order the list by Order ID and then have a for loop and check if the current Order ID is equal to the next Order ID if so assign the two the same block ID. Is there a better way of doing this using linq or any other approach?
Order ID Block ID
ABC
ABC
ABC
GHJ
GHJ
GHJ
MNO
MNO
You can do this that way, it will assign same blockid for same orderid
var ordered = listOrder.GroupBy(x => x.OrderId).ToList();
for (int i = 0; i < ordered.Count(); i++)
{
ordered[i].ForEach(x=>x.BlockId=i+1);
}
it will group orders by orderid then assign each group next blockid. Note that it won't be done fully in linq, because linq is for querying not changing data.
Always depends of what better means for you in this context.
There are a bunch of possible solutions to this trivial problem.
On top of my head, I could think of:
var blockId = 1;
foreach(var grp in yourOrders.GroupBy(o => o.OrderId))
{
foreach(var order in grp)
{
order.BlockId = blockId;
}
blockId++;
}
or (be more "linqy"):
foreach(var t in yourOrders.GroupBy(o => o.OrderId).Zip(Enumerable.Range(1, Int32.MaxValue), (grp, bid) => new {grp, bid}))
{
foreach(var order in t.grp)
{
order.BlockId = t.bid;
}
}
or (can you still follow the code?):
var orders = yourOrders.GroupBy(o => o.OrderId)
.Zip(Enumerable.Range(1, Int16.MaxValue), (grp, id) => new {orders = grp, id})
.SelectMany(grp => grp.orders, (grp, order) => new {order, grp.id});
foreach(var item in orders)
{
item.order.BlockId = item.id;
}
or (probably the closest to a simple for loop):
Order prev = null;
blockId = 1;
foreach (var order in yourOrders.OrderBy(o => o.OrderId))
{
order.BlockId = (prev == null || prev.OrderId == order.OrderId) ?
blockId :
++blockId;
prev = order;
}
Linq? Yes.
Better than a simple loop? Uhmmmm....
Using Linq will not magically make your code better. Surely, it can make it often more declarative/readable/faster (in terms of lazy evaluation), but sure enough you can make otherwise fine imperative loops unreadable if you try to force the use of Linq just because Linq.
As a side note:
if you want to have feedback on working code, you can ask at codereview.stackexchange.com

Join array to EF query

I get the following error when trying to join an array to the Linq-to-EF query
An error occurred while executing the command definition. See the inner exception for details. Some part of your SQL statement is nested too deeply. Rewrite the query or break it up into smaller queries.
The code is as follows:
var vids = new List<string>();
using (var ctx = new MyDbContext())
{
var qry = ctx.Pickups.Where(p => p.UserName == User.Identity.Name);
if (someBoolean)
{
var v = GetVids(); // get the list of Ids from a web service
vids.AddRange(v);
}
if (vids.Count() > 0)
{
qry = qry.Join(vids, p => p.VId, v => v, (v, p) => p);
}
var data = qry
.Select(p => new
{
// etc.
});
}
The problem is that the web service is not associated with the DB I'm using EF with, otherwise, I'd just do the join against the tables in the DB. The number of Id's I get back from the web service could be upwards of a hundred or so (usually 5-10). If I comment out the Join, the code works fine, so I know the error is in the Join. With just a few (up to about 30) ids in vids, the join works perfectly.
What do you recommend to remedy this problem? The only thing I could think of was to insert the list of IDs into the DB and do the join that way. That doesn't seem too appealing to me.
Try to replace if (vids.Count() > 0) with:
if (vids.Count > 0)
{
qry = qry.Where(arg => vids.Contains(arg.VId));
}
This will work only if vids is less then 2100 elements, as this will be translated to IN (x, y, .., n) condition.
If you use entity framework 3.5, then Contains will not work. You can find possible solution here: 'Contains()' workaround using Linq to Entities?

Categories

Resources