Entity Framework using Load with Select - is it possible? - c#

I'm trying to optimize my EF queries and I'm stuck with this one.
Let's say I have a model like this:
public class House
{
public int ID { get; set; }
public ICollection<Window> Windows { get; set; }
}
public class Window
{
public int ID { get; set; }
public string Color { get; set; }
public WindowKind Kind { get; set; }
}
public class WindowKind
{
public int ID { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
What I would like to do is to explicitly load all windows and to specify what should be populated in WindowKind property.
I know I can do it with .Include() like this:
var house = Context.Houses.Single(h => h.ID == id);
var windows = Context.Entry(house).Collection(h => h.Windows).Query().Include(w => w.Kind).Load();
However, this will create a query that will load all WindowKind properties and I need only Name, for example. I was hoping something like this would work but it does not, Windows collection is empty, although the generated query looks good.
var house = Context.Houses.Single(h => h.ID = id);
var windows = Context.Entry(house).Collection(h => h.Windows).Query().Select(w => { new w.Color, w.Kind.Name }).Load();
Is it possible to have fine grained control when loading child collections?

you can't load only a part of the scalar (int, string,...) properties of an entity by loading the entity.
In you case, something like the following should do:
from
w in Context.Windows
where
w.House.ID == id // here a navigation property is missing, but (imho) more clear for the sample
select new {
windows = w,
kName = w.Kind.Name
}
But in this case you will not get context attached entities.

Related

AutoMapper Giving Error, While Mapping A Table That Contains A List. C#

TLDR - The error is:
The query has been configured to use 'QuerySplittingBehavior.SplitQuery' and contains a collection in the 'Select' call, which could not be split into separate query. Please remove 'AsSplitQuery' if applied or add 'AsSingleQuery' to the query.
I am developing a backend with EntityFrameworkCore in C#.
My table classes are like this:
public class MainTable : BasicAggregateRoot<int>
{
public MainTable()
{
this.Operations = new HashSet<OperationTable>();
}
public long? RecId { get; set; }
public int FormStatus { get; set; }
public virtual ICollection<OperationTable> Operations { get; set; }
}
public class OperationTable : BasicAggregateRoot<int>
{
public OperationTable()
{
this.Works = new HashSet<Work>(); //Not important things
this.Materials = new HashSet<Material>(); //Not important things
}
public string ServiceType { get; set; }
}
And my DTOs are like this:
public class MainDto : EntityDto<int>
{
public long? RecId { get; set; }
public int FormStatus { get; set; }
public List<OperationDto> Operations { get; set; }
}
public class OperationDto
{
public string ServiceType { get; set; }
}
I created maps this way:
CreateMap<MainTable, MainDto>().ReverseMap();
CreateMap<OperationTable, OperationDto>().ReverseMap();
When I commit the mapping by:
class Service{
IRepository<MainTable, int> _mainTableRepository;
Service(IRepository<MainTable, int> mainTableRepository){
_mainTableRepository = mainTableRepository;
}
List<MainDto> All()
{
var result = mainTableRepository.Include(p => p.Operations)
.ProjectTo<MainDto>(ObjectMapper.GetMapper().ConfigurationProvider) //Here is the problem.
.ToList();
return result;
}
}
I get the error on the top.
When I get rid of the List from mainDto, error does not occur, but I don't have the result that I want either.
What might be the problem? I couldn't find an answer.
In that source you can find the differences between single and split query: https://learn.microsoft.com/en-us/ef/core/querying/single-split-queries
The problem is (I guess) IRepository.Include uses split query by default. But (again I guess) AutoMapper is not configured to use split query, it works with single queries.
We need to change the query type before mapping like this:
var result = mainTableRepository.Include(p => p.Operations)
.AsSingleQuery() //This solved the problem
.ProjectTo<MainDto>(ObjectMapper.GetMapper().ConfigurationProvider)
.ToList();

Select Entity property in related table using linq

here is my setup.
Base Model
public class Base
{
public int BaseID { get; set; }
[StringLength(8)]
[Index(IsUnique = true)]
public string BaseNumber { get; set; }
public ICollection<BillOfMaterial> billOfMaterials { get; set; }
}
BillOfMaterial Model
public class BillOfMaterial
{
public int BillOfMaterialID { get; set; }
[StringLength(10)]
[Index(IsUnique = true)]
public string BomNumber { get; set; }
public ICollection<Base> Bases { get; set; }
}
What I am trying to do is select all bill of material BomNumbers where the base is equal to a input base number.
What I have tried
BaseNumber = "A1C1D001";
var BOMQuery = (from Base in db.Bases.Include("BillOfMaterials")
where Base.BaseNumber == BaseNumber
select Base.billOfMaterials.ToList());
When trying to create this query I can't see the BomNumber property when I do => select Base.BillOfMaterials.(Can't Find Property)
I tried using the .Include() extension to try and bring in the related table in hopes it would give me the property. Not sure how to word this question exactly to do a good google search for the answer. What am I doing wrong here? Any help would be appreciated.
Thank you,
When you only need a list of BOMs use the following:
var BOMQuery = db.Bases
.Where(x => x.BaseNumber == BaseNumber)
.SelectMany(a => a.billOfMaterials.Select(b => b.BomNumber)).ToList();
You can then add it to an ObservableCollection like this:
BomList = new ObservableCollection<string>(BOMQuery);

Combine workings of Query / QueryOver and DetachedCriteria in NHibernate

In my current project, I started using NHibernate and basically everything works nicely, up to a point, where I need to send complete object tree for some entity to the consumer of my service = everything eagerly loaded and sent from the server.
And I want my queries to be as fast as possible.
The search criteria is rather simple: the complete newest result object for the specified product and the specified department.
If i call my query like this, I am getting the correct result:
(however this produces 3 queries, still, at least I am getting the correct result)
Result res = session.QueryOver<Result>()
.Where(x => x.Product == product)
.Where(x => x.Department == department)
.OrderBy(x => x.Date).Desc
.Take(1).SingleOrDefault();
However, such result is not eagerly loaded with all the child collections.
(and yes, it still creates 3 queries).
For I definitelly need all the items of type ResultContent in Content populated. ResultContent has some collections of elements, that have their collections as well.
I desperately need all those collections fully populated with all the child elements and child elements of its child elements and so on.
I played with NHibernate queries nearly whole day now and am nearly achieving correct behavior with query like this:
var resultCriteria = DetachedCriteria.For<Result>();
resultCriteria.SetFetchMode("Product", FetchMode.Eager);
resultCriteria.SetFetchMode("Department", FetchMode.Eager);
resultCriteria.SetFetchMode("Content", FetchMode.Join);
resultCriteria.SetFetchMode("Source", FetchMode.Join);
resultCriteria.SetFetchMode("Content.Numerics", FetchMode.Eager);
resultCriteria.SetFetchMode("Content.Numerics.DataCollection", FetchMode.Join);
resultCriteria.SetFetchMode("Content.Numerics.DataDescription", FetchMode.Join);
resultCriteria.SetFetchMode("Content.Numerics.DataDescription.DescriptionSource", FetchMode.Eager);
resultCriteria.SetMaxResults(5);
var relevantResults = resultCriteria.GetExecutableCriteria(session).List<Result>();
With this approach, I am getting 5 Results, but the Content collection is not populated further than the first element. And for the DataCollection in Numerics, there only are 5 elements queried, even though it should be somewhere near 500 items pulled.
I Played with the FetchModes quite a lot and am starting to think that I will not achieve what I need nowhere near in the future.
Could you please hint me in the proper direction how I could combine those 2 approaches properly?
Should I use HQL (I never did, so I think I would struggle even more)?
Note: I googled quite a lot and tried a lot of things, but as far as NHibernate and sql goes, I consider myself quite a noob. I will try improving my skills, but the time pressure now tends towards asking a question.
The other relevant classes look like this (so that you can see which class holds a collection of what, lots of simplifications applied of course)
public class Result
{
public virtual long Id { get; protected set; }
public virtual Product Product { get; set; }
public virtual Department Department { get; set; }
public virtual DateTime Date { get; set; }
public virtual ISet<ResultContent> Content { // getters and setters }
public virtual ISet<ResultSource> Source { // getters and setters }
}
public class Product
{
public virtual short Id { get; protected set; }
public virtual string Name { get; set; }
public virtual string Code { get; set; }
public virtual string Description { get; set; }
}
public class Department
{
public virtual short Id { get; protected set; }
public virtual string Name { get; set; }
public virtual string Code { get; set; }
public virtual string Description { get; set; }
}
public class ResultContent
{
public virtual Result Result { get; set; }
public virtual Numerics Numerics { get; set; }
public virtual long Revision { get; set; }
}
public class Numerics
{
public virtual long Id { get; protected set; }
public virtual string Name { get; set; }
public virtual ISet<DataEntry> DataCollection { //getters and setters }
public virtual ISet<DataDescription> DataDescription { //getters and setters}
}
DataEntry and DataDescription Classes are just holding Id, Date and either string or boolean value.
hi can you try it ,
Result ra = null;
Product pa = null;
Department da = null;
Result res = session.QueryOver<Result>(() => ra)
.JoinAlias(() => ra.Product ,() => pa)
.JoinAlias(() => ra.Department ,() => da)
.Where(() => pa == product)
.Where(() => da == department)
.OrderBy(() => ra.Date).Desc
.Take(1).SingleOrDefault();

ravendb index across multiple nested properties

I am asking how to create an index based upon two different nested properties on an document. I am executing these queries through C#.
public class LocationCode
{
public string Code {get;set;}
public string SeqId {get;set;}
}
public class ColorCode
{
public string Code {get;set;}
public string SeqId {get;set;}
}
public class TestDocument
{
public int Id {get;set;}
public List<LocationCode> Locations { get; set; }
public List<ColorCode> Colors { get; set; }
}
I have experimented with various AbstractIndexCreationTask, Map, and Map+Reduce, but to no avail.
I would like to be able to do a query such as:
Get all documents where any Locations.Code property is "USA", AND/OR Colors.Code="RED", or on the SeqId property. I dont know whether this would mean I need multiple indexes. Normally I would either be comparing the Code property on both nested classes, or the Seq, but never mixed.
Please could someone point me in the right direction.
Many thanks
Phil
Create your index like this:
public class TestIndex : AbstractIndexCreationTask<TestDocument, TestIndex.IndexEntry>
{
public class IndexEntry
{
public IList<string> LocationCodes { get; set; }
public IList<string> ColorCodes { get; set; }
}
public TestIndex()
{
Map = testDocs =>
from testDoc in testDocs
select new
{
LocationCodes = testDoc.Locations.Select(x=> x.Code),
ColorCodes = testDoc.Colors.Select(x=> x.Code)
};
}
}
Then query it like this:
var q = session.Query<TestIndex.IndexEntry, TestIndex>()
.Where(x => x.LocationCodes.Any(y => y == "USA") &&
x.ColorCodes.Any(y => y == "RED"))
.OfType<TestDocument>();
Full unit test here.

Entity Framework (5.0) Code First - Insert into Collection within Collection

I've got three classes.
Event > Workshop > Workshop Times
I'm currently looking for best way of inserting records into the Workshop Times, this is running through code first using ICollections.
Looking for something along the lines of this, but I know it doesn't work:
//Create connection
var db = new Context();
var Event = db.Events
.Include("Workshops")
.Include("Workshops.Times")
.Where(ev => ev.GUID == EventGUID).FirstOrDefault();
Event.Workshops.Add(new Workshop
{
Name = tbWorkshopName.Text,
Description = tbWorkshopDescription.Text,
Times.Add(new WorkshopTime{
//Information for times
})
});
db.SaveChanges();
Chopped down classes:
public class Workshops{
public int id { get; set; }
public string name { get; set; }
public ICollection<WorkshopTimes> Times{get;set;}
}
public class Events {
public int id { get; set; }
public string name { get; set; }
public ICollection<Workshops> WorkShops { get; set; }
}
public class WorkshopTimes {
public int id { get; set; }
public DateTime time { get; set; }
}
You are definitely on the right track with your query, however your include statements appear incorrect. From your model I would expect:
var Event = db.Events
.Include("WorkShops")
.Include("WorkShops.events")
.Where(ev => ev.GUID == EventGUID).FirstOrDefault();
Note this uses the property names not the types. This will ensure that the entities in the listed nav properties will be included in the result.
In addition you can use a lambda to do the same thing (but its typesafe)
Check out here for how to do a very similar scenario to yours:
EF Code First - Include(x => x.Properties.Entity) a 1 : Many association
or from rowan miller (from EF team)
http://romiller.com/2010/07/14/ef-ctp4-tips-tricks-include-with-lambda/
And make sure you are using System.Data.Entities for lambda based includes ( Where did the overload of DbQuery.Include() go that takes a lambda? )

Categories