Linq to SQL, how to get single element without executing the query? - entity-framework

I have stumbled upon an optimization issue and maybe there is a way around it.
Consider that I have the following Entity:
public class EntityOne
{
public int Id {get; set;}
public virtual ICollection<Names> Names { get; }
}
And I want to fetch a single Name that matches a specific criteria.
Lets say I create the given model:
public class EntityView
{
public int Id {get; set;}
public string SingleName {get; set;}
}
Now I want to fetch the information for the EntityView model in a single query.
This is how I'm doing this so far:
var result = db.EntityOnes
.Include(a => a.Names)
.Select(a => new EntityView
{
Id = a.Id,
Name = a.Names.Where(b => b.Criteria == Criteria).Select(b => b.Name).FirstOrDefault()
}).ToList();
Is there any way I could go about this in a way that I don't have to execute that FirstOrDefault inside of the main query, and still only fetch a single record for my EntityView model?
Thanks everyone!

I think this is faster than yours:
var result = db.EntityOnes
.Include(a => a.Names)
.Select(a => new EntityView
{
Id = a.Id,
SingleName = a.Names.FirstOrDefault(b => b.Criteria == Criteria)?.Name ?? ""
}).ToList();

You can skip Where() and Select() inside the query.
var result = db.EntityOnes
.Include(a => a.Names)
.Select(a => new EntityView
{
Id = a.Id,
SingleName = a.Names.FirstOrDefault(b => b.Criteria == Criteria)?.Name
}).ToList();

Knowing I would only have one result from my Names, I can just join the IEnumerable result as following:
var result = db.EntityOnes
.Include(a => a.Names)
.Select(a => new EntityView
{
Id = a.Id,
Name = string.Join("", a.Names.Where(b => b.Criteria == Criteria).Select(b => b.Name))
}).ToList();
This way I would get a single string out of my query and execute a single call to the database

Related

The right way to apply GroupBy extension method with aggregate function

I have this simple model.
public class Room
{
public Guid Id { get; set; }
public Guid? postSubjectId { get; set; }
[ForeignKey("postSubjectId")]
public PostSubject postSubject { get; set; }
public string MemberId { get; set; }
[ForeignKey("MemberId")]
public AppUser Member { get; set; }
}
Basically I need to get Grouped postSubjectId along with MemberId.Count() , I know it's easy .. but it never comes with the expected result.
I made this simple GroupBy query
var mmbrs = _context.Rooms
.Select(g => new { id = g.postSubjectId, mmbrscount = g.MemberId })
.AsEnumerable()
.GroupBy(g => new { id = g.id , mmbrscount = g.mmbrscount.Count() }).ToList();
but it gives me unexpected result
However I did the same using ordinary sql query
select [postSubjectId] as postId, count([MemberId]) as mmbrsCount from [dbo].[Rooms] group by [postSubjectId]
and It gives me result as expected
I need to apply that expected result using LINQ GruoupBy extention method
The grouping key new { id = g.postSubjectId, mmbrscount = g.MemberId }) is like typing group by [postSubjectId], count([MemberId]) in SQL.
The correct statement is:
_context.Rooms
.GroupBy(r => r.postSubjectId)
.Select(g => new
{
id = g.Key,
mmbrscount = g.Count()
})
So every Room has exactly one property PostSubjectId, and one string property MemberId.
I need to get Grouped postSubjectId along with MemberId.Count()
Apparently you want to make groups of Rooms that have the same value for property PostSubjectId AND have the same value for MemberId.Count().
var result = dbContext.Rooms.GroupBy(room => new
{
PostSubjectId = room.PostSucjectId,
MemberIdLength = room.MemberId.Count(),
});
The result is a sequence of groups of Rooms. Every group has a key, which is a combination of [PostSubjectId, MemberIdLength]. The group is a sequence of Rooms. All rooms in one group have the same combination of [PostSubjectId, MemberIdLength].
If you don't want a sequence of groups of Rooms, you can use the overload of GroupBy that has a parameter resultSelector
var result = dbContext.Rooms.GroupBy(
// parameter keySelector
room => new
{ PostSubjectId = room.PostSucjectId,
MemberIdLength = room.MemberId.Count(),
},
// parameter resultSelector:
// from every combination of [PostSubjectId, MemberIdLength] (= the key) and
// all rooms that have this combination, make one new object:
(key, roomsWithThisKey) => new
{
// select the properties that you actually plan to use, for example
PostSubjectId = key.PostSubjectId,
MemberIdLength = key.MemberIdLength,
RoomInformations = roomsWithThisKey.Select(roomWithThisKey => new
{
Id = roomWithThisKey.Id,
Member = roomWithThisKey.Member,
...
})
.ToList(),
});

EF core - parent.InverseParent returns null for some rows

I have a Category table and it has a Parent Category, I try to iterate over all the categories and get the parents categories with it's Inverse Parent but some of them returns without the inverse parents from unknown reason.
Categories.cs
public partial class Categories
{
public Categories()
{
InverseParent = new HashSet<Categories>();
}
public int Id { get; set; }
public int? ParentId { get; set; }
public DateTime CreateDate { get; set; }
public bool? Status { get; set; }
public virtual Categories Parent { get; set; }
public virtual ICollection<Categories> InverseParent { get; set; }
}
This is how I try to iterate them to create a select list items:
var parentCategories = await _context.Categories.
Include(x => x.Parent).
Where(x => x.Status == true).
Where(x => x.Parent != null).
Select(x => x.Parent).
Distinct().
ToListAsync();
foreach (var parent in parentCategories)
{
SelectListGroup group = new SelectListGroup() { Name = parent.Id.ToString() };
foreach (var category in parent.InverseParent)
{
categories.Add(new SelectListItem { Text = category.Id.ToString(), Value = category.Id.ToString(), Group = group });
}
}
So the problem is that some of my parent categories returns all their children categories and some don't and I don't why.
There are several issues with that code, all having some explaination in the Loading Related Data section of the documentation.
First, you didn't ask EF Core to include InverseParent, so it's more logically to expect it to be always null.
What you get is a result of the following Eager Loading behavior:
Tip
Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.
Second, since the query is changing it's initial shape (Select, Disctinct), it's falling into Ignored Includes category.
With that being said, you should build the query other way around - starting directly with parent categories and including InverseParent:
var parentCategories = await _context.Categories
.Include(x => x.InverseParent)
.Where(x => x.InverseParent.Any(c => c.Status == true)) // to match your query filter
.ToListAsync();
While you are including Include(x => x.Parent), you don't seem to do the same for InverseParent. This might affect your results exactly the way you describe. Would including it fix it?
parentCategories = await _context.Categories.
Include(x => x.Parent).
Include(x => x.InverseParent).
Where(x => x.Status == true).
Where(x => x.Parent != null).
Select(x => x.Parent).
Distinct().
ToListAsync();
foreach (var parent in parentCategories)
{
SelectListGroup group = new SelectListGroup() { Name = parent.Id.ToString() };
foreach (var category in parent.InverseParent)
{
categories.Add(new SelectListItem { Text = category.Id.ToString(), Value = category.Id.ToString(), Group = group });
}
}
UPD: Since you are selecting x => x.Parent anyway it might be necessary to use ThenInclude() method instead.

Get nested data and sharp into DTO with nested DTO

I'm newbie to EF, Linq and C# in general, I'm stuck with developing following.
I cannot map data into structure like this:
Id,
Actions [
Action1,
Action2,
Action3
]
I have 2 DTO classes like this:
public class TestDTO
{
public int TestId { get; set; }
public TestDTO2[] Actions { get; set; }
}
and
public class TestDTO2
{
public int TestActionId { get; set; }
public DateTime? StartDate { get; set; }
...
}
I've separated calls to DB into file called BusinessLogic, I'm doing it like this:
public IQueryable<TestDTO> GetNested(Filter filter)
{
var query =
from a in db.Table1.AsQueryable()
select new TestDTO
{
TestId = a.Id,
Actions = (
from b in db.Table2.AsQueryable()
where a.Id == b.TestId
select new TestDTO2
{
TestActionId = b.TestActionId,
StartDate = b.StartDate
}
).ToArray()
};
return query;
}
I'm getting following error:
LINQ to Entities does not recognize the method 'Project.Core.Models.TestDTO2[] ToArrayTestDTO2' method, and this method cannot be translated into a store expression.
You can't perform exactly this query, it is better to make two simple queries and then process their results on client side:
var main = db.Table1.Select(x => new { x.Id, x.Title }).ToList();
var mainIds = main.Select(x => x.Id).ToList();
var actions = db.Table2.Where(x => mainIds.Contains(x.TestId)).Select(x => new
{
x.TestId,
x.TestActionId,
x.StartDate
}).ToList();
var result = main.Select(x => {
var actions = actions.Where(y => y.TestId == x.Id).Select(y => new TestDTO2
{
TestActionId = y.TestActionId,
StartDate = y.StartDate
}).ToArray();
return new TestDTO
{
TestId = x.Id,
Title = x.Title,
Actions = actions.Length == 0 ? null : actions
};
}).ToList();
yes, you can't use any c# method that can't translate a sql in EF.
actually, you need get a list,then covert it to your DTO
db.Table1
.Join(db.Table2,
a => a.Id,
b => b.TestId,
(a, b) => new
{
a.Id,
b
})
.GroupBy(k => k.Id, v => v).ToList()
.Select(a=>new TestDTO
{
TestId = a.Id,
Actions = a.Select(b=>
new TestDTO2
{
TestActionId = b.TestActionId,
StartDate = b.StartDate
}.ToArray()
}).ToList()

ASP.NET Core cannot sort grouped items

I have the following model in my ASP.NET Core application:
public class LocationTypeGroup {
public string Name { get; set; }
public IEnumerable<LocationType> LocationTypes { get; set; }
}
public class LocationType
{
[Key]
public int LocationTypeID { get; set; }
public string Name { get; set; }
public string IntExt { get; set; }
}
I am trying to run a query that groups them by IntExt, and sorts by Name within each group.
The following works, but doesn't sort:
public async Task<List<LocationTypeGroup>> GetGroupedLocationTypes()
{
return await _context.LocationTypes
.GroupBy(p => p.IntExt)
.Select(g => new LocationTypeGroup
{
Name = g.Key,
LocationTypes = g.Select(x => x)
})
.OrderBy(x=>x.Name)
.ToListAsync();
}
If I change to this:
LocationTypes = g.Select(x => x).OrderBy(x => x)
Then I still do not get a sorted result.
What am I doing wrong?
It's possible that EF can't build SQL query.
So you need simplify it manually. and split to 2 queries:
var groups = await context.LocationTypes
.GroupBy(p => p.IntExt)
.ToListAsync();
return groups.Select(g => new LocationTypeGroup
{
Name = g.Key,
LocationTypes = g.Select(x => x)
})
.OrderBy(x=>x.Name);
The first query loads simply groups, and the second sorts them and converts to LocationTypeGroup.
May be it caused by too old version of Entity Framework Core. Try this approach, moreover it will be less expensive:
//data is loaded into memory
var data = await _context.LocationTypes.ToListAsync();
//data's transform
var answer = data.GroupBy(x => x.IntExt)
.Select(x => new LocationTypeGroup
{
Name = x.Key,
LocationTypes = x.AsEnumerable()
}).OrderBy(x => x.Name).ToList();

Load multiple entities eagerly with .Include()

I want to load eagerly a class A instance with a certain Id. Then I want to .Include every class B instance related to A instance and .Include every C instance related to B instance.
This does not work:
var schoolyear = _context.Schoolyears
.Include(s => s.SchoolclassCodes)
.Include(s => s.TimeTableEntries)
.SingleOrDefault(s => s.Id == schoolyearId);
The last .Include is on the wrong level. It must relate to the .SchoolclassCodes.
Is this not possible with the stronly typed .Include() or do I have to use the weaked type style:
var schoolyear = _context.Schoolyears
.Include("SchoolclassCode.TimeTableEntries")
.SingleOrDefault(s => s.Id == schoolyearId);
class Schoolyear
{
public int Id { get; set;}
public ICollection<SchoolclassCode> {get;set;}
}
class SchoolclassCode
{
public ICollection<TimeTableEntry> {get;set;}
}
class TimeTableEntry
{
}
You can do it with the stongly typed method as well.
Just make a select query in the Include until you reach the last entity in the hierarchy that should be loaded:
var schoolyear = _context.Schoolyears
.Include(s => s.SchoolclassCodes
.Select(t => t.TimeTableEntries));