So, I think I have a problem with restrictions, or at least missing something.
Basically I have a query that I execute with the following result set:
As you can see, there are 6 records.
Now inside my application it will return 6 records but it will add the order OR00221 multiple times, which is expected but what isn't expected is that it will only add the first one, with freightnr 6 multiple times.
As you can see, it add the 3rd record from the resultset for all items with this OrdNr OR00221.
I believe the problem is somewhere here:
restriction.Add(Restrictions.In(Projections.Property<ItemToPlan>(x => x.TrailerType), TrailerType));
The source of the class:
public static IList<ItemToPlan> GetItems(string[] TrailerType, Dictionary<string, string> filter, string orderBy, bool ascending, Dictionary<string, bool> groupingColumns)
{
if (TrailerType == null || TrailerType.Length == 0)
{
return new List<ItemToPlan>();
}
using (ISession session = NHibernateHelper.OpenSession())
{
var query = session.QueryOver<ItemToPlan>();
var restriction = Restrictions.Conjunction();
restriction.Add(Restrictions.In(Projections.Property<ItemToPlan>(x => x.TrailerType), TrailerType));
if (filter != null)
{
Type type = typeof(ItemToPlan);
IClassMetadata meta = session.SessionFactory.GetClassMetadata(type);
foreach (var item in filter)
{
if (!string.IsNullOrWhiteSpace(item.Value))
{
IType propertyType = meta.GetPropertyType(item.Key);
if (propertyType == NHibernateUtil.String)
{
restriction.Add(Restrictions.InsensitiveLike(Projections.Property(item.Key), item.Value, MatchMode.Anywhere));
}
else
{
restriction.Add(Restrictions.InsensitiveLike(Projections.Cast(NHibernateUtil.String, Projections.Property(item.Key)), item.Value, MatchMode.Anywhere));
}
}
}
}
if (restriction.ToString() != "()")
{
query.Where(restriction);
}
if (groupingColumns != null)
{
foreach (var item in groupingColumns)
{
if (item.Value)
{
query = query.OrderBy(Projections.Property(item.Key)).Asc;
}
else
{
query = query.OrderBy(Projections.Property(item.Key)).Desc;
}
}
}
else
{
groupingColumns = new Dictionary<string, bool>();
}
if (!string.IsNullOrWhiteSpace(orderBy) && !groupingColumns.ContainsKey(orderBy))
{
if (ascending)
{
query = query.OrderBy(Projections.Property(orderBy)).Asc;
}
else
{
query = query.OrderBy(Projections.Property(orderBy)).Desc;
}
}
var result = query.List<ItemToPlan>();
return result;
}
Source as pastebin: https://pastebin.com/aiTTBKBQ
You can use GroupBy to avoid duplication of records if this problem really exist in the query. I suppose that you have OrdNr property in your model.
Here is a sample of how to do this.
result = result.GroupBy(e => e.OrdNr).Select(e => e.FirstOrDefault());
Related
I have a ProductVersions model which has a multi part key, a int ProductId and a int VersionNum field.
The function below takes a list of simple Dto classes that are just these 2 fields with goal of returning a set full ProductVersion objects from the database that match.
Below is a less than efficient solution. I am expecting the incoming list to only between 2 to 4 items so its not too bad but I'd like to do better.
private async Task<List<ProductVersion>?> GetProductVersionsFromDto(IList<ProductVersionDto>? productVersionDtos)
{
List<ProductVersion>? productVersions = null;
if (productVersionDtos != null)
{
foreach (ProductVersionDto dto in productVersionDtos)
{
ProductVersion? productVersion = await myPortalDBContext.ProductVersions
.Where(pv => pv.ProductId == dto.ProductId && pv.VersionNum == dto.VersionNum)
.FirstOrDefaultAsync();
if (productVersion != null)
{
if (productVersions == null) productVersions = new List<ProductVersion>();
productVersions.Add(productVersion);
}
}
}
return productVersions;
}
I had consider something like this:
private async Task<List<ProductVersion>?> GetProductVersionsFromDto(IList<ProductVersionDto>? productVersionDtos)
{
List<ProductVersion>? productVersions = null;
if (productVersionDtos != null)
{
productVersions = await myPortalDBContext.ProductVersions
.Join(productVersionDtos,
pv => new { pv.ProductId, pv.VersionNum },
pvd => new { pvd.ProductId, pvd.VersionNum },
(pv, pvd) => pv)
.ToListAsync();
}
return productVersions;
}
but at runtime fails because the Join doesn't make sense. Anyone know of way to do this more efficiently with just a single round-trip into the dbContext?
Use FilterByItems extension and then you can generate desired query:
private async Task<List<ProductVersion>?> GetProductVersionsFromDto(IList<ProductVersionDto>? productVersionDtos)
{
if (productVersionDtos == null)
return null;
var productVersions = await myPortalDBContext.ProductVersions
.FilterByItems(productVersionDtos,
(pv, dto) => pv.ProductId == dto.ProductId && pv.VersionNum == dto.VersionNum, true)
.ToListAsync();
return productVersions;
}
I have a ViewModel that contains different elements inside different tables that I tend to assign to it by query.
My problem is that I can't do this with IEnumerable (in GetAll() below), it keeps returning me null for RoomCode but for a single item (in GetDeviceId() below) then it works fine.
public IEnumerable<DeviceViewModel> GetAll()
{
var result = deviceRepository.GetAll().Select(x => x.ToViewModel<DeviceViewModel>());
for(int i = 0; i < result.Count(); i++)
{
int? deviceID = result.ElementAt(i).DeviceId;
result.ElementAt(i).RoomCode = deviceRepository.GetRoomCode(deviceID);
}
return result;
}
public DeviceViewModel GetDeviceID(int deviceID)
{
var result = new DeviceViewModel();
var device = deviceRepository.Find(deviceID);
if (device != null)
{
result = device.ToViewModel<DeviceViewModel>();
result.RoomCode = deviceRepository.GetRoomCode(deviceID);
}
else
{
throw new BaseException(ErrorMessages.DEVICE_LIST_EMPTY);
}
return result;
}
public string GetRoomCode(int? deviceID)
{
string roomCode;
var roomDevice = dbContext.Set<RoomDevice>().FirstOrDefault(x => x.DeviceId == deviceID && x.IsActive == true);
if (roomDevice != null)
{
var room = dbContext.Set<Room>().Find(roomDevice.RoomId);
roomCode = room.RoomCode;
}
else
{
roomCode = "";
}
return roomCode;
}
First, you need to materialize the query to a collection in local memory. Otherwise, the ElementAt(i) will query the db and give back some kind of temporary object each time it is used, discarding any change you do.
var result = deviceRepository.GetAll()
.Select(x => x.ToViewModel<DeviceViewModel>())
.ToList(); // this will materialize the query to a list in memory
// Now modifications of elements in the result IEnumerable will be persisted.
You can then go on with the rest of the code.
Second (and probably optional), I also recommend for clarity to use foreach to enumerate the elements. That's the C# idiomatic way to loop through an IEnumerable:
foreach (var element in result)
{
int? deviceID = element.DeviceId;
element.RoomCode = deviceRepository.GetRoomCode(deviceID);
}
I have the following code when iam searching it searches with all the key words for 1.example : London
2.example : London Bridge
but when i search by
example3 : in London Bridge
it does not return any results. and also my scores are not sorted. here my am making search only in title and trying to sort by scores (highest to lowest)
following is my code to making search only in title and trying to sort by scores (highest to lowest)
public SearchResultCollection Search(string searchString, string ProgramCampusStage, out Hits hitScores)
{
var searchIndex=Sitecore.Search.SearchManager.GetIndex(SearchIndexName);
using (IndexSearchContext context = searchIndex.CreateSearchContext())
{
// get the search term
string searchterm = searchString.ToLower().Trim();
PhraseQuery completeQuery = new PhraseQuery();
completeQuery.SetSlop(4);
foreach (var s in searchterm.Split(' '))
{
completeQuery.Add(new Term("title", s));
}
hitScores = context.Searcher.Search(completeQuery, new Sort(new SortField[1] { SortField.FIELD_SCORE }));
SearchHits hits = new SearchHits(hitScores);
var results = hits.FetchResults(0, Int32.MaxValue);
foreach (var result in results)
{
try
{
Item item = result.GetObject<Item>();
bool isAuthorized = UserUtility.IsUserAuthorized(item, ProgramCampusStage);
if (isAuthorized)
{
if (item != null)
{
string categoryName = GetCategoryName(item);
if (item.Language.Name != Context.Language.Name || categoryName == string.Empty)
{
continue;
}
results.AddResultToCategory(result, categoryName);
}
else
{
results.AddResultToCategory(result, OtherCategory);
}
}
}
catch
{
continue;
}
}
return _searchResults = results;
}
}
}
Instead of adding the stop words as terms in your index (which could cause you other headaches like 'too many clauses' exceptions), you could try filtering out the stop words when you construct your query:
foreach (var s in searchterm.Split(' '))
{
if (!Lucene.Net.Analysis.Standard.StandardAnalyzer.STOP_WORDS_SET.Contains(s))
{
completeQuery.Add(new Term("title", s));
}
}
I'm working on a project and implemented a search by multiple fields in MVC, using LINQ like so:
public ActionResult SearchResult(SearchViewModel model)
{
List<Requisition> emptyList = new List<Requisition>();
if (model.RequisitionID > 0
|| model.Department > 0
|| model.Status > 0
|| model.RequisitionedBy != null)
{
var results = db.Requisitions.Where(x => x.RequisitionId > 0);
results = ProcessSearchInput(model, results);
return PartialView(results.ToList());
}
return PartialView(emptyList);
}
Helper:
private static IQueryable<Requisition> ProcessSearchInput(SearchViewModel model, IQueryable<Requisition> results)
{
if (model.Department > 0)
results = results.Where(x => x.Department == model.Department);
if (model.RequisitionedBy != null)
results = results.Where(x => x.Requisitioned_By.Contains(model.RequisitionedBy));
if (model.Status > 0)
results = results.Where(x => x.Status.Contains(model.Status.ToString()));
return results;
}
This code works fine.
However, if I add an extra search field to the form, I would also need to add a separate if statement in the controller.
With the current approach, the ProcessSearchInput method will contain too many if statements.
Is there a better way to handle a search with multiple fields?
Your current approach violates the open closed principle. The solution is to create a dynamic filter like in this example. However that is a complicated solution that is worth only if you are going to add more and more filters along the way. If not then don't bother.
I agree with previous comments: your current solution is probably the way to go.
In the real world, you'll soon have to implement filters like 'all customers having either a billing- or a shipping-address in New York', and more complicated stuff. By then, all clever generic stuff will just be in the way.
However, if you promise never to use this in production code:
you can save a lot of typing by using a query by example, where you specify the filter as an instance of the type your source contains:
var example = new Requisition { Department = 8, Requisitioned_By ="john" };
var result = db.Requisitions.FilterByExample(example);
This is a simple implementation:
public static class FilterByExampleHelper
{
public static IQueryable<T> FilterByExample<T>(this IQueryable<T> source, T example) where T : class
{
foreach (var property in typeof(T).GetProperties(BindingFlags.Public|BindingFlags.Instance).Where(p => p.CanRead))
{
ConstantExpression valueEx = null;
var propertyType = property.PropertyType;
if (propertyType.IsValueType)
{
var value = property.GetValue(example);
if (value != null &&
!value.Equals(Activator.CreateInstance(propertyType)))
{
valueEx = Expression.Constant(value, propertyType);
}
}
if (propertyType == typeof(string))
{
var value = property.GetValue(example) as string;
if (!string.IsNullOrEmpty(value))
{
valueEx = Expression.Constant(value);
}
}
if (valueEx == null)
{
continue;
}
var parameterEx = Expression.Parameter(typeof(T));
var propertyEx = Expression.Property(parameterEx, property);
var equalsEx = Expression.Equal(propertyEx, valueEx);
var lambdaEx = Expression.Lambda(equalsEx, parameterEx) as Expression<Func<T, bool>>;
source = source.Where(lambdaEx);
}
return source;
}
}
There is a sample application called MVCCrud. This example is quite good and I would like to use it as the framework on a project that I am working on.
The problem is that MVCCrud uses LingToSQL and I would like to use LinqToEntities. I got most everything to work correctly once I converted over to LinqToEntities except one place.
In the following code on the lines i = typeof(TModel).GetProperty(primaryKey).GetValue(p, null),
cell = getCells(p)
it gives a Linq to Entities does not recognize GetValue.
Can someone help me refactor the following code?
items = items.OrderBy(string.Format("{0} {1}", sidx, sord)).Skip(pageIndex * pageSize).Take(pageSize).AsQueryable();
// Generate JSON
var jsonData =
new
{
total = totalPages,
page,
records = totalRecords,
rows = items.Select(
p => new
{
// id column from repository
i = typeof(TModel).GetProperty(primaryKey).GetValue(p, null),
cell = getCells(p)
}).ToArray()
};
return Json(jsonData);
and here is the getCell method:
private string[] getCells(TModel p)
{
List<string> result = new List<string>();
string a = actionCell(p);
if (a != null)
{
result.Add(a);
}
foreach (string column in data_rows.Select(r => r.value))
{
try
{
// hack for tblcategory.name
string[] parts = column.Split('.');
// Set first part
PropertyInfo c = typeof(TModel).GetProperty(parts[0]);
object tmp = c.GetValue(p, null);
// loop through if there is more than one depth to the . eg tblCategory.name
for (int j = 1; j < parts.Length; j++)
{
c = tmp.GetType().GetProperty(parts[j]);
tmp = c.GetValue(tmp, null);
}
if (tmp.GetType() == typeof(DateTime))
{
result.Add(((DateTime)tmp).ToString(dateTimeFormat));
}
else if (tmp.GetType() == typeof(float))
{
result.Add(((float)tmp).ToString(decimalFormat));
}
else if (tmp.GetType() == typeof(double))
{
result.Add(((double)tmp).ToString(decimalFormat));
}
else if (tmp.GetType() == typeof(decimal))
{
result.Add(((decimal)tmp).ToString(decimalFormat));
}
else
{
result.Add(tmp.ToString());
}
}
catch (Exception)
{
result.Add(string.Empty);
}
}
return result.ToArray();
}
Do this ToList() instead of AsQueryable():
items = items.OrderBy(string.Format("{0} {1}", sidx, sord)).Skip(pageIndex * pageSize).Take(pageSize).ToList();
You can't execute any external method "within" linq query.
And may you say that was working in Linq2Sql then you should know when you call any external method "Like ToString()" Linq2Sql will fetch all data from database then handle your query in the memory and that maybe a serious harming if you have a lot of records.
For more information look at this