I am trying to write a linq query which will exclude any records that have a child record with a certain integer ID.
The class I am querying against looks like:
public class Group {
public ICollection<Item> { get; set; } // This is the child collection
}
public class Item {
public int Id { get; set; }
}
My repository query method is:
public ICollection<Group> Get(int itemId) {
return from c in Set.... // Set is an EF collection of all Groups
}
I want to return all Groups that do not have an Item in their Items collection with the Id equal to the itemId passed to the method.
Not sure how to write this most efficiently in Linq.
This will work (I'm using method syntax though as I prefer method syntax above query syntax for anything other than joins):
var result = db.Groups.Where(g => !g.Items.Any(i => i.Id == itemID)).ToList();
Select all groups which don't contain an item with an Id equal to itemID. By the way I notice you have Set in your code? Does this mean you already fetched all the groups beforehand or something (so filtering in memory)? The easiest way is to work with your DbContext and access your tables from there.
Related
I've been looking at the aggregation and lookup functions of mongo to figure out how to search by a field in a different collection but could not work it out.
The data structure looks like this:
public class User
{
public string Id
public string Name
public string GroupId
}
and
public class Group
{
public string Id
public string Name
}
What I'm trying to accomplish here is: return a list of Users with Group name "xyz".
Below is my returned IExecutable with no matching field for the group name.
return userCollection.Aggregate(new AggregateOptions
{
Collation = new Collation("en",
strength: CollationStrength.Primary)
})
.Match(u=>u.Name.Contains("xyz")
.AsExecutable();
You can use a $lookup stage to achieve this; this stage looks up information in another collection and adds the matching documents to an array for further processing. In order to use it in a type-safe manner, create a class with a groups enumeration, e.g.:
public class UserWithGroups : User
{
public IEnumerable<Group> Groups { get; set; }
}
In addition, you need to create a collection for the groups, e.g.:
var grpCollection = db.GetCollection<Group>("groups");
Then you can extend your statement as follows:
return userCollection.Aggregate(new AggregateOptions
{
Collation = new Collation("en",
strength: CollationStrength.Primary)
})
.Match(u=>u.Name.Contains("xyz")
.Lookup<User, Group, UserWithGroups>(
grpCollection,
x => x.GroupId,
x => x.Id,
x => x.Groups)
.Match(x => x.Groups.Any(y => y.Name.Contains("abc")))
.AsExecutable();
If you need to query for group names often, you can also check out the Extended Reference pattern and store the group name with the user.
I have the following object, called Filter with the following properties:
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Type> Types{ get; set; }
public virtual ICollection<Step> Steps { get; set; }
public virtual ICollection<Flow> Flows { get; set; }
public virtual ICollection<Room> Rooms { get; set; }
When I select a list of Filters from the database, I have no idea how to include the collections (Types, Steps, Flows, Rooms). My code is as follows:
var filters = (
from filter in dbContext.DbSet<Filter>()
let rooms = (
from r in dbContext.DbSet<Room>()
select r
)
let eventTypes = (
from t in dbContext.DbSet<Type>()
select t
)
let processFlows = (
from f in dbContext.DbSet<Flow>()
select f
)
let processFlowSteps = (
from s in dbContext.DbSet<Step>()
select s
)
select filter
).ToList();
My collection of Filter is returned, but the collections inside are empty. Could you please tell me how can I achieve this?
Ps: I do not want to use Include because of performance issues, I don't like how Entity Framework generates the query and I would like to do it this way.
Your method works, you are just doing it slighly wrong.
To include a navigation property, all you have to do is a subselect (using linq), example:
var filters = (from filter in dbContext.DbSet<Filter>()
select new Filter
{
filter.Id,
filter.Name,
Rooms = (from r in dbContext.DbSet<Room>()
where r.FilterId == filter.Id
select r).ToList()
}).ToList();
Keep in mind that EF won't execute the query until you call a return method (ToList, Any, FirstOrDefault, etc). With this, instead of doing those ugly queries you want to avoid by not using Include(), it will simply fire two queries and properly assign the values in the object you want.
You need to use Include extension method:
var filters=dbContext.DbSet<Filter>()
.Include(f=>f.Types)
.Include(f=>f.Steps)
.Include(f=>f.Flows)
.Include(f=>f.Rooms)
.ToList()
Update
#MrSilent, Include extension method was made exactly for the purpose of loading related entities, I think the other option you have is executing a raw sql, but the way you are doing is not the way to go you have four roundtrips to your database and you need to use join instead in order to get the related entities, Include generates those joins for you and it's just one roundtrip.
This is, eg, another way I guess you could do it, but again, it is against the purpose of using EF, the idea of your model is also to represent the relationship between your tables, not just to represent them individually
var query= from f in context.DbSet<Filter>()
from s in f.Steps
from r in f.Rooms
from t in f.Types
from fl in f.Flows
select new {f, s, r, t, fl};
You can use lazy loading , how :
You first need to get the properties that are Include inclined to be virtual and then an empty constructor that is protected access type to do your job well.
public virtual ICollection<Type> Types{ get; set; }
public virtual ICollection<Step> Steps { get; set; }
public virtual ICollection<Flow> Flows { get; set; }
public virtual ICollection<Room> Rooms { get; set; }
And
//FOR EF !
protected Filter() { }
I think this solution will solve your problem.
I have two objects named Transactions and CreditorDetails . I want to be able to get the retrieve say CFirstName from my transations object. I don't want to retrieve it from CreditorDetails. for example, i want to be able to say
foreach ( Transactions item in info.transaction)
{
Console.WriteLine("My Name" + item.creditors.Select(m =>m.CFirstName));//Just an example
}
The above code isn't giving me the desired result. it's returning system.linq.Enumerable+whereSelectListIterator........... which i think i know why but i need to be able to get the CFirstName from Transaction object.
class Transactions
{
public List<CreditorsDetails> creditors { get; set; }
//Contains some other things which i didn't bother inlude since they are irrelevant
}
class CreditorsDetails
{
public string CFirstName { get; set; }
public string CAddress { get; set; }
public string CCountry { get; set; }
}
Select will give you an enumerable of strings you need to iterate that to access the information:
foreach (Transactions item in info.transaction)
{
foreach (var cfName in item.creditors.Select(m => m.CFirstName))
{
Console.WriteLine("My Name" + cfName);
}
}
If you just want the first item in the list you can also use FirstOrDefault
foreach (Transactions item in info.transaction)
{
Console.WriteLine("My Name" + item.creditors.Select(m => m.CFirstName).FirstOrDefault());
}
You can use SelectMany to retrieve items from sub-lists:
var result = info.transaction.SelectMany(x => x.creditors.Select(y => y.CFirstName));
The inner Select retrieves a list of the first names of the creditors of a transaction. By using SelectMany, you concatenate all lists of first names of all creditors in a single list.
If you want to return only distinct values, you can add a call to Distinct:
var result = info.transaction.SelectMany(x => x.creditors.Select(y => y.CFirstName))
.Distinct();
I used SQLite-Net Extensions
in the following code to retrieve 1000 rows with their children relationships from an Sqlite database:
var list =
SQLiteNetExtensions.Extensions.ReadOperations.GetAllWithChildren<DataModel>(connection);
The problem is that the performance is awkward. Because GetAllWithChildren() returns a List not an Enumerable. Does exist any way to load the records in to an Enumerable using Sqlite.net extensions?
I now use Table() method from Sqlite.net, loads the fetched rows in to the Enumerable but I dont want to use it because it does not understand the relationships and does not load the children entities at all.
GetAllWithChildren suffers from the N+1 problem, and in your specific scenario this performs specially bad. It's not clear in your question what you're trying, but you could try these solutions:
Use the filterparameter in GetAllWithChildren:
Instead of loading all the objects to memory and then filter, you can use the filter property, that internally performs a Table<T>().Where(filter) query, and SQLite-Net will convert to a SELECT-WHERE clause, so it's very efficient:
var list = connection.GetAllWithChildren<DataModel>(d => d.Name == "Jason");
Perform the query and then load the relationships
If you look at the GetAllWithChildren code you'll realize that it just performs the query and then loads the existing relationships. You can do that by yourself to avoid automatically loading unwanted relationships:
// Load elements from database
var list = connection.Table<DataModel>().Where(d => d.Name == "Jason").toList();
// Iterate elements and load relationships
foreach (DataModel element in list) {
connection.GetChildren(element, recursive = false);
}
Load relationships manually
To completely workaround the N+1 problem you can manually fetch relationships using a Contains filter with the foreign keys. This highly depends on you entity model, but would look like this:
// Load elements from database
var list = connection.Table<DataModel>().Where(d => d.Name == "Jason").toList();
// Get list of dependency IDs
var dependencyIds = list.Select(d => d.DependencyId).toList();
// Load all dependencies from database on a single query
var dependencies = connection.Table<Dependency>.Where(d => dependencyIds.Contains(d.Id)).ToList();
// Assign relationships back to the elements
foreach (DataModel element in list) {
element.Dependency = dependencies.FirstOrDefault(d => d.Id == element.DependencyId);
}
This solution solves the N+1 problem, because it performs only two database queries.
Another method to load relationships manually
Imagine we have these classes:
public class Parent
{
[PrimaryKey, AutoIncrement] public int Id { get; set; }
public string Name { get; set; }
public List<Child> children { get; set; }
public override bool Equals(object obj)
{
return obj != null && Id.Equals(((BaseModel) obj).Id);
}
public override int GetHashCode()
{
return Id.GetHashCode();
}
}
and
public class Child
{
[PrimaryKey, AutoIncrement] public int Id { get; set; }
public string Name { get; set; }
public int ParentId { get; set; }
}
Hint these classes have one-to-many relation. Then inner join between them would be:
var parents = databaseSync.Table<Parent>().ToList();
var children = databaseSync.Table<Child>().ToList();
List<Parent> parentsWithChildren = parents.GroupJoin(children, parent => parent.Id, child => child.ParentId,
(parent, children1) =>
{
parent.children = children1.ToList();
return parent;
}).Where(parent => parent.children.Any()).ToList();
Is it possible to replicate the following SQL query in NHibernate?
SELECT Name, COUNT(Name)
FROM Table
GROUP BY Name
Unfortunately I'm not able to get NHibernate to execute this as a raw sql query either as it is not permitted by my current employer.
I've seen examples of returning a count of linked entities but not a count of data in the same table.
I've currently got this working by using two queries. One to get a distinct list of names and one to get the count for each name. I'd like to optimise this to a single database call.
Thanks in advance for any help you can give!
We can do it like this:
// this would be our DTO for result
public class ResultDTO
{
public virtual string Name { get; set; }
public virtual int Count { get; set; }
}
This would be the query
// here we declare the DTO to be used for ALIASing
ResultDTO dto = null;
// here is our query
var result = session.QueryOver<Table>()
.SelectList(l => l
.SelectGroup(x => x.Name).WithAlias(() => dto.Name)
.SelectCount(x => x.Name).WithAlias(() => dto.Count)
)
.TransformUsing(Transformers.AliasToBean<ResultDTO>())
.List<ResultDTO>();