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.
Related
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();
Is this possible? I am trying to avoid a lot of copying and pasting from area to area. I have a search function (I have reduced the code for simplicity).
if (!String.IsNullOrEmpty(filterVM.searchString))
{
var nameSearch = filterVM.searchString.ToLower();
guests = guests.Where(g => g.FirstName.ToLower().StartsWith(nameSearch)
|| g.LastName.ToLower().StartsWith(nameSearch)
)
}
filterVM.FilteredResultsCount = guests.CountAsync();
Guests can change from area to area, but it always has the same base things, like FirstName and LastName,
ex:
public class GuestBasicBase
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public string GuestGuid { get; set; } = Guid.NewGuid().ToString();
public string FirstName { get; set; }
public string LastName { get; set; }
}
Then I can have a bigger class for a particular area like
public class AreaOneGuest : GuestBasicBase
{
public int ID {get; set;}
public string ExtraFieldOne { get; set; }
public string ExtraFieldTwo { get; set; }
//Etc
}
I would like to have a function which will return a viewmodel and part of that viewmodel is PaginatedList and the other part is the Filter parameters, like this:
public class GuestBasicBaseIndexVM
{
public PaginatedList<T:GuestBasicBase> Guests { get; set; }
public GuestIndexFilterVM FilterVM { get; set; }
}
And I want a function to return this but take in a larger field, like
public async Task<GuestBasicBaseIndexVM>(T:GuestBasicBase, GuestIndexFilterVM filterVM){
//do search function
return (T where T: GuestBasicBase)
}
Does this question make sense and is it possible? Currently trying on my own and seeing what happens...I feel like it is sort of like the PaginatedList class but I am not certain
Not exactly what I wanted, but here is what I did. changed my BaseViewModel like this:
public class GuestBasicBaseIndexVM
{
public IEnumerable<GuestBasicBase> Guests { get; set; }
//Changed from a PaginatedList
public GuestIndexFilterVM FilterVM { get; set; }
}
and this function:
public static async Task<GuestBasicBaseIndexVM> CreateUpdatedGuestList(GuestIndexFilterVM filterVM, IQueryable<GuestBasicBase> guests)
{
//Code to search through guests and return filtered list and filters viewmodel
}
Then after the Ienumaerable of basic guests is returned I did this to connect them back to the AreaOne Guests
var x = await Helpers.CreateUpdatedGuestList(filterVM, guests);
var hsIDs = x.Guests.Select(v => v.GuestGuid).ToHashSet(); //Filtered GuestGuids to hashset
areaOneGuests = guests.Where(g => hsIDs.Contains(g.GuestGuid)) //This matches up the filtered list of base guests to the actual full guests.
//Then whatever code to do what I want with the AreaOne Guests....
It wasn't exactly what I was trying to do, but still saves me a lot of copying and pasting from area to area with similar base Guest classes. Have not been able to measure any noticeable performance loss/gain doing it this way.
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);
I have a simple object like such:
public class Foo {
public int One { get; set; }
public int Two { get; set; }
....
public int Eleven { get; set; }
}
Given an IEnumerable, what I want is a LINQ method to transform as such:
myFooEnumerable.Select(n => transformMagicGoesHere);
Where my return object looks like this:
public class Bar {
public string DurationDescription {get;set;} //Value would be "One" or "Two" or ...
public int Value {get;set;} //Holds value in the property One or Two or ...
}
So for every item N in myFooEnumerable in the example above I'd get 11(N) items in my resultant select statement.
This should do it:
var bars = myFooEnumerable.SelectMany(
x => x.GetType().GetProperties().Select(p => new Bar {
DurationDescription = p.Name,
Value = (int)p.GetValue(x)
}));
Not a great thing to be doing in the first place, IMO, but it will at least work.
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.