RIA-Services - how to WhereOr or use an IN style construct - silverlight

I am using SL 4, WCF RIA Services against Entity Framework 4.0. I have an Entity, Visit, that has a string Status field. I have a search screen where I need to display results that have StatusA or StatusB. I am struggling to find a way to specify a client-side query that specifies a collection of statuses that should be matched. If I was to write what I want in SQL it would look something like:
select * from Visit where Status in ('StatusA', 'StatusB');
Client side, it appears to be straightforward to chain Where methods for a WhereAnd effect:
var query = this.PqContext.GetVisitsQuery();
if (!string.IsNullOrEmpty(this.PracticeName))
{
query = query.Where(v => v.PracticeName.ToUpper().Contains(this.PracticeName.ToUpper()));
}
if (this.VisitDateAfter.HasValue)
{
query = query.Where(v => v.VisitDate > this.VisitDateAfter);
}
if (this.VisitDateBefore.HasValue)
{
query = query.Where(v => v.VisitDate < this.VisitDateBefore);
}
However, I can't seem to find a straightforward way to do a WhereOr style operation. I have tried this:
var statuses = new List<string>();
if (this.ShowStatusA)
{
statuses.Add("StatusA");
}
if (this.ShowStatusB)
{
statuses.Add("StatusB");
}
if (statuses.Any())
{
query = query.Where(BuildContainsExpression<Visit, string>(e => e.Status, statuses));
}
Where BuildContainsExpression looks like:
private static Expression<Func<TElement, bool>> BuildContainsExpression<TElement, TValue>(Expression<Func<TElement, TValue>> valueSelector, IEnumerable<TValue> values)
{
if (null == valueSelector)
{
throw new ArgumentNullException("valueSelector");
}
if (null == values)
{
throw new ArgumentNullException("values");
}
ParameterExpression p = valueSelector.Parameters.Single();
if (!values.Any())
{
return e => false;
}
var equals =
values.Select(
value =>
(Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));
var body = equals.Aggregate<Expression>(Expression.Or);
return Expression.Lambda<Func<TElement, bool>>(body, p);
}
But this throws a "Bitwise operators are not supported in queries." exception. Any clues? Is there an alternative way to build an expression tree that works here or do I need to pass all the parameters over to the server and use the BuildContainsExpression there?
Your time and your guidance are much appreciated.

You can create a query method such as the following in your domain service:
GetVisitsByStatus(string[] statusList) {
// create the LINQ where clause here
}
And then from the client, call context.GetVistsByStatusQuery(string[]).
Not all of LINQ is (or even can) be exposed over the URL, so there are always cases where you need to use simple parameters, and have the middle tier construct the LINQ expressions that eventually define the query that goes to the back-end data store.
Hope that helps.

Related

Dapper One to Many Mapping Logic

The dapper tutorial gives this example to help a user with Multi Mapping (One to Many)
While this works I am curious why they have you store the orders in the dictionary but then in the end they use a linq.Distinct() and return from the list. It seems like it would be cleaner to just return the ordersDictionary.Values as the dictionary logic ensures no duplicates.
//Tutorial
using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{
Dictionary<int,Order> orderDictionary = new Dictionary<int, Order>();
List<Order> list = connection.Query<Order, OrderDetail, Order>(sql, (order, orderDetail) =>
{
if (!orderDictionary.TryGetValue(order.OrderID, out Order orderEntry))
{
orderEntry = order;
orderEntry.OrderDetails = new List<OrderDetail>();
orderDictionary.Add(orderEntry.OrderID, orderEntry);
}
orderEntry.OrderDetails.Add(orderDetail);
return orderEntry;
}, splitOn: "OrderID")
.Distinct()
.ToList();
return list;
}
//my suggestion
using (var connection = new SqlConnection(FiddleHelper.GetConnectionStringSqlServerW3Schools()))
{
Dictionary<int,Order> orderDictionary = new Dictionary<int, Order>();
//change 1 no need to store into list here
connection.Query<Order, OrderDetail, Order>(sql, (order, orderDetail) =>
{
if (!orderDictionary.TryGetValue(order.OrderID, out Order orderEntry))
{
orderEntry = order;
orderEntry.OrderDetails = new List<OrderDetail>();
orderDictionary.Add(orderEntry.OrderID, orderEntry);
}
orderEntry.OrderDetails.Add(orderDetail);
return orderEntry;
}, splitOn: "OrderID"); //change 2 remove .Distinct().ToList()
return orderDictionary.Values.ToList(); //change 3 return dictionaryValues
}
I'm the author of this tutorial: https://dapper-tutorial.net/query#example-query-multi-mapping-one-to-many
why they have you store the orders in the dictionary
A row is returned for every OrderDetail. So you want to make sure to add the OrderDetail to the existing Order and not create a new one for every OrderDetail. The dictionary is used for performance to check if the Order has been already created or not.
it would be cleaner to just return the ordersDictionary.Values
How will your query return dictionary values?
Of course, if you are in a method such as yours, you can do
var list = orderDictionary.Values;
return list;
But how to make this Connection.Query return dictionary values? An order is returned for every row/OrderDetail, so the order will be returned multiple times.
Outside the Query, your dictionary solution works great and is even a better solution for performance, but if you want to make your Query return the distinct list of orders without using Distinct or some similar method, it's impossible.
EDIT: Answer comment
my suggestion return orderDictionary.Values.ToList(); //change 3 return dictionaryValues
Thank you for your great feedback, it's always appreciated ;)
It would be weird in a tutorial to use what the query returns when there is no relationship but use the dictionary for one to many relationships
// no relationship
var orders = conn.Query<Order>("", ...).Distinct();
// one to many relationship
conn.Query<Order, OrderDetail>("", ...);
var orders = orderDictionary.Values.ToList();
Your solution is better for performance the way you use it, there is no doubt about this. But this is how people usually use the Query method:
var orders = conn.Query("", ...).Distinct();
var activeOrders = orders.Where(x => x.IsActive).ToList();
var inactiveOrders = orders.Where(x => !x.IsActive).ToList();
They use what the Query method returns.
But again, there is nothing wrong with the way you do it, this is even better if you can do it.

'504 - Gateway Timeout' when Indexing the items in Episerver Find

When Indexing the items, it fails sometimes and it gives,
The remote server returned an error: (504) Gateway Timeout. [The remote server returned an error: (504) Gateway Timeout.]
The Indexing logic is here as below,
var client = EPiServer.Find.Framework.SearchClient.Instance;
List<ItemModel> items = getItems(); // Get more than 1000 items
List<ItemModel> tempItems = new List<ItemModel>();
//Index 50 items at a time
foreach(var item in items)
{
tempItems.Add(item);
if (tempItems.Count == 50)
{
client.Index(tempItems);
tempItems.Clear();
}
}
What causes this to happen ?
Note: The above mentioned ItemModel is a custom model which is not implemented interfaces (such as IContent). And the items is a list of ItemModel objects.
Additional info:
EPiServer.Find.Framework version 13.0.1
EPiServer.CMS.Core version 11.9.2
I always figured the SearchClient to be a bit sketchy when manipulating data in Find, as far as I figured (but I have to check this) the SearchClient obey under the request limitation of Episerver Find and when doing bigger operations in loops it tends to time out.
Instead, use the ContentIndexer, i.e.
// Use this or injected parameter
var loader = ServiceLocator.Current.GetInstance<IContentLoader>();
// Remove all children or not
var cascade = true;
ContentReference entryPoint = ...where you want to start
// Get all indexable languages from Find
Languages languages = SearchClient.Instance.Settings.Languages;
// Remove all current instances of all languages below the selected content node
//languages.ForEach(x => ContentIndexer.Instance.RemoveFromIndex(entryPoint, cascade.Checked, x.FieldSuffix));
foreach (var lang in languages)
{
if (cascade)
{
var descendents = loader.GetDescendents(entryPoint);
foreach (ContentReference descendent in descendents)
{
ContentIndexer.Instance.RemoveFromIndex(descendent, false, lang.FieldSuffix);
}
}
// Try delete the entrypoint
var entryTest = loader.Get<IContent>(entryPoint, new CultureInfo(lang.FieldSuffix));
if (entryTest != null)
{
var delRes = ContentIndexer.Instance.Delete(entryTest);
}
}
This is the most bulletproof way to delete stuff from the index as far as I figured.

Circular references and stack overflow exceptions

I have this many to many association between KundeInfo and HovedKategori, which I have mapped in my MS SQL database like this:
I have implemented the methods KundeInfo.HovedKategoris:
public IEnumerable<KundeInfo> KundeInfos
{
get
{
using (var dc = new DataClassesBSMAKSDataContext())
{
dc.DeferredLoadingEnabled = false;
var kundeInfoHovedKategoris = dc.KundeInfoHovedKategoris.Where(x => x.HovedKategori_Id == Id);
var kundeInfos = dc.KundeInfos.Where(x => kundeInfoHovedKategoris.Any(y => y.KundeInfo_Id == x.Id));
return kundeInfos.ToList();
}
}
}
... and HovedKategori.KundeInfos:
public IEnumerable<HovedKategori> HovedKategoris
{
get
{
using (var dc = new DataClassesBSMAKSDataContext())
{
var kundeInfoHovedKategoris = dc.KundeInfoHovedKategoris.Where(x => x.KundeInfo_Id == Id);
var hovedKategoris = dc.HovedKategoris.Where(x => kundeInfoHovedKategoris.Any(y => y.HovedKategori_Id == x.Id));
return hovedKategoris.ToList();
}
}
}
This retrieves the associated KundeInfos from a specific HovedKategori and opposite. The problemhowever lies in the serialization. When I call ToList(), or serialize these objects to JSON, linq will try to first follow all references returned by HovedKategori.KundeInfos, if it were that method I were to call first, and then it would for each returned object, try to follow all references returned by KundeInfo.HovedKategoris and so on, until it would cast a stack overflow exception.
If I could somehow prevent linq from following certain properties with an [Ignore] attribute or something, it would work, but I haven't been able to find anything like that.
What can I do in this situation?
This is in part a design issue. What you should really ask yourself is if you need navigation properties in every possible direction. For example if you just add a Kategori ID instead of a navigation property you could still query your context (by using the ID) but do you really need to always get all the Kategori's with all underlying data?
also if you make your properties virtual you have lazy loading and will only get the information if you .Include it or explicitly reference it.
Ok - So I solved this problem by just making it into methods on the respective classes, which it should be in the first place, since it retrieved these entities from the database. So yes, partially design issue.
Virtual did not work, I considered using projection and an abstract class, but it would be such a haystack of inheritance, and class casts, that it would not even be worth considering.
public IEnumerable<KundeInfo> KundeInfos()
{
using (var dc = new DataClassesBSMAKSDataContext())
{
var kundeInfoHovedKategoris = dc.KundeInfoHovedKategoris.Where(x => x.HovedKategori_Id == Id);
var kundeInfos = dc.KundeInfos.Where(x => kundeInfoHovedKategoris.Any(y => y.KundeInfo_Id == x.Id));
return kundeInfos.ToList();
}
}

SqlCacheDependecy command notification not working

I been trying to get sqlcachedependecy working, but it doesn't appear to work
I got the proper settings in my web.config and also global.asa, however when I run this query and the changes are made to the database from either with in or outside the web site the cached objects are not updated please someone help? I know its not because this query is querying a view, because I tested this using straight SqlDependecy and the notification works fine.
public IQueryable<VictoryList> GetVictoryList()
{
string cacheKey = HttpContext.Current.User.Identity.Name + "victoryCacheKey";
IQueryable<VictoryList> cachednews = (IQueryable<VictoryList>)HttpContext.Current.Cache.Get(cacheKey);
if (cachednews == null)
{
var results = from v in _datacontext.ViewVictoryLists
orderby _datacontext.GetNewId()
select new VictoryList
{
MemberID = v.MemberID,
Username = v.Aspnetusername,
Location = v.Location,
DaimokuGoal = v.DaimokuGoal,
PreviewImageID = v.PreviewImageID,
TotalDaimoku = v.TotalDaimoku,
TotalDeterminations = v.TotalDeterminations,
DeterminationID = v.DeterminationID,
DeterminationName = v.DeterminationName
};
results = results.ToList().AsQueryable();
SqlCacheDependencyAdmin.EnableNotifications(_datacontext.Connection.ConnectionString);
SqlCacheDependency dependency =
new SqlCacheDependency(_datacontext.GetCommand(results) as SqlCommand);
HttpContext.Current.Cache.Insert(cacheKey, results, dependency);
return results;
}
return cachednews;
}
According to the stated Limitations for creating a query for notification, listed at msdn...
The statement must not reference a view.

Should I still see the query hit in SQL Profiler?

I am currently building a web site and I just implemented SqlCacheDependency using LinqToSQL like so.
public IQueryable<VictoryList> GetVictoryList()
{
string cacheKey = HttpContext.Current.User.Identity.Name + "victoryCacheKey";
IQueryable<VictoryList> cachednews = (IQueryable<VictoryList>)HttpContext.Current.Cache.Get(cacheKey);
if (cachednews == null)
{
var results = from v in _datacontext.ViewVictoryLists
orderby _datacontext.GetNewId()
select new VictoryList
{
MemberID = v.MemberID,
Username = v.Aspnetusername,
Location = v.Location,
DaimokuGoal = v.DaimokuGoal,
PreviewImageID = v.PreviewImageID,
TotalDaimoku = v.TotalDaimoku,
TotalDeterminations = v.TotalDeterminations,
DeterminationID = v.DeterminationID,
DeterminationName = v.DeterminationName
};
SqlCacheDependency dependency =
new SqlCacheDependency(_datacontext.GetCommand(results) as SqlCommand);
HttpContext.Current.Cache.Add(cacheKey, results, dependency, DateTime.MaxValue,
TimeSpan.Zero, CacheItemPriority.Normal, null);
return results.ToList().AsQueryable();
}
return cachednews;
}
It appears to be working as things are noticbly faster especially on some complex queries, however while looking at things in SQLProfiler I still see the query run through, I'm using the CommandBroker mode of SqlCacheDependency. Should I still see the query even though the data is obviously coming from a cached object?
I think that the problem is that you are storing IQueryable's in your cache, and then cachednews contains an IQueryable that hits the database.
Try the following changes.
public IQueryable<VictoryList> GetVictoryList() {
// ...
if (cachednews == null)
{
var results = from // ...
results = results.ToList().AsQueryable(); // force query execution
SqlCacheDependency dependency = // ...;
HttpContext.Current.Cache.Add(cacheKey,
results, // now just the result are stored
dependency,
DateTime.MaxValue,
TimeSpan.Zero,
CacheItemPriority.Normal,
null);
return results;
}
return cachednews;
}

Resources