RavenDB - Index sorting for Alphanumeric property - c#

I am trying to index an alphanumeric property that should be sorted as follows:
WS1-1-14-1
WS1-1-15-1
WS1-1-15-2
WS1-1-15-3
I created an index that removes the non numeric characters of the property and set the SortOptions to Int so i can order then as above.
Here is my index definition:
Map = fixtures => from f in fixtures
select new
{
FixtureNumber = f.FixtureNumber.Replace("-", "").Replace("WS", ""),
};
Sort(x => x.FixtureNumber, SortOptions.Int);
The sorting works fine when i do and OrderBy on the Index:
session.Query<Fixture, Fixtures_All>.OrderBy(x => x.FixtureNumber).ToList()
But my problem is that when i do a dynamic Query on my Fixture document its like the query is still using my index:
session.Query<Fixture>.FirstOrDefault(x => x.FixtureNumber == "WS1-1-1");
Returns no results, which i would expect it would as im not querying my index. But the following does work, which i find strange:
var fixNo = "WS1-15-1".Replace("-", "").Replace("WS", "");
session.Query<Fixture>.FirstOrDefault(x => x.FixtureNumber == fixNo);
Why is my index affecting this dynamic index query ?
Also is there a better way to index my property without resorting to string replace to make it an integer ?
Thanks
Note:
If i just use the default string comparer on the SortOptions the results get sorted like so:
WS1-1-15-1
WS1-1-15-10
WS1-1-15-2
WS1-1-15-3
...

Raven will always look for an index on the given type and for an indexed property (by name) on that type when doing an dynamic index. If it can't find one, it will auto-generate one first.
When you issue the following query:
session.Query<Fixture>.FirstOrDefault(x => x.FixtureNumber == "WS1-1-1");
Raven will find that there is indeed an index on Fixture on the property FixtureNumber defined by the Fixtures_All index. The problem here is that the stored FixtureNumber inside the index is altered (stripped dash and letters) so Raven will use that to map your query (it doesn't know that it's been altered).
If you look at the index in the Raven studio you can select "Index Entries" to show what the actual index looks like on disk:
This is why you don't get any results when issue the query.
If you want to use an altered property in the index you can do the following:
Map = fixtures => from f in fixtures
select new
{
ShortenFixtureNumber = f.FixtureNumber.Replace("-", "").Replace("WS", ""),
FixtureNumber = f.FixtureNumber
};
Sort("ShortenFixtureNumber", SortOptions.Int);
Then you can create something I like to call a "Query Model" (not sure if that's the right name):
public class FixtureQueryModel
{
public string FixtureNumber { get; set; }
public int ShortenFixtureNumber { get; set; }
}
With this, I can query both by the index using the "Query model" and also query directly on the type:
var fixture = session.Query<FixtureQueryModel, Fixtures_All>()
.Where(x => x.FixtureNumber.StartsWith("WS1"))
.OrderBy(x => x.ShortenFixtureNumber)
.As<Fixture>()
.ToList();
var fixture2 = session.Query<Fixture>()
.FirstOrDefault(x => x.FixtureNumber == "WS1-1-14-1");
However, this might seem rather complicated and in your case I think the default string sort comparer would work directly on an un-altered FixtureNumber, but hopefully this post provides some help into what's going on with indexes.

Related

Asp.Net Mvc delete columns from database table

I am trying to delete all table data where username = current user name. But there is some error on lambda expression and conversion."Cannot convert source type to target type. Any help?
public JsonResult DeleteChatMessages(int id)
{
string toId = Session["SessionUserName"].ToString();
tblChat tblchats = _obj.tblChat.Where(p => p.ToId == toId);
_obj.tblChat.Remove(tblchats);
_obj.SaveChanges();
return this.Json(true, JsonRequestBehavior.AllowGet);
}
I am assuming you are expecting only one object to return from the Where filter.
Change your line as,because as per this . The Where clause returns an IEnumerable<TSource>. And you are assigning that to the non IEnumerable object.
tblChat tblchats = _obj.tblChat.FirstOrDefault(p => p.ToId == toId)
and you also might want to check for the Null, before removing it from the tblChat.
If you are expecting more than one object to match the filter and want to remove all of them then use #tmg approach.
_obj.tblChat.RemoveRange(_obj.tblChat.Where(p => p.ToId == toId).ToList());
Filter the "chats" you want.
var tblchats = _obj.tblChat.Where(p => p.ToId == toId).ToList();
After iterate throught them and remove.
foeach(chat in tblchats){
_obj.tblChat.Remove(chat);
}
_obj.SaveChanges();

Assign a column value if it throws an exception in Linq query

I have a query where one property is a path "/Primary/secondary/tertiary/.../.../"
My task is to split this path by the slashes, so every sub-path can be assigned as a property in the query result.
The problem is, that the length varies. Some paths have a post-split array length of 1, some of 7. So I need to have 7 different category columns:
var result = MySource.Where(ms => ...)
.Select(ms => new {
ID = ms.ID,
Name = ms.Name,
Category1 = ms.Path.Split('/')[0],
Category2 = ms.Path.Split('/')[1] //exception
.... //exception
Category7 = ms.Path.Split('/')[6] //exception
});
After the path gets split, the resulting array is of various length (1 - 7) leading into an ArgumentOutOfRangeException. How can I circumvent this exceptions?
I have tried using the nullcoalescence operator ms.Path.Split('/')[1] ?? "N/A", which did not help, because there is no result but an exception thrown. Because of this every shorthand-if statement will fail as well.
Is there a way to catch the exception (wrap in try catch block?) so I can assign a default value if the array is out of bounds?
Your modeling seems a little broken. Instead of a flattened set of properties, populate a single collection. Something like this:
Select(ms => new {
ID = ms.ID,
Name = ms.Name,
Categories = ms.Path.Split('/')
})
Going a step further, you can create an actual (non-anonymous) model to hold this information, encapsulating the logic of category range checking. Something like:
Select(ms => new SomeObject(
ms.ID,
ms.Name,
ms.Path.Split('/')
))
Then in SomeObject you can have all sorts of logic, for example:
In the constructor you can perform input checking on the values, including the count of categories supplied, to ensure the object is valid.
You can keep the collection of categories private and expose properties for 1-7 if you really need to, which internally perform this check. (Though I really don't recommend that. It creates an unnecessary point of change for something that's already handled by a collection, indexing values.) Something like:
public string Category1
{
get
{
if (categories.Length < 1)
return string.Empty;
return categories[0];
}
}
Maybe throw an exception instead of returning an empty string? Maybe do something else? The point is to encapsulate this logic within an object instead of in a LINQ query or in consuming code.
you can do
Category7 = ms.Path.Split('/').ElementAtOrDefault(6) ?? "N/A",
see demo: https://dotnetfiddle.net/4nTBhq
ElementAtOrDefault return the element at index (for example 6, like [6]) but if out of bound return null.
optimized, without calling Split multiple times:
.Select(ms => {
var categories = ms.Path.Split('/');
return new {
ID = ms.ID,
Name = ms.Name,
...
Category7 = categories.ElementAtOrDefault(6),
};
})

Filter Generic Collection Down to Records Containing Any Item in Collection of String

string rep = "Joe Shmoe"
ObjectSet<StoreData> storeData = edmContext.StoreData;
ObjectSet<CallData> callData = edmContext.CallData;
IEnumerable<string> repStoreData = storeData.Where(r => r.RepName == rep).Select(s => s.Location);
IEnumerable<CallData> repCallData = Here is where I want to filter down the callData collection down to just the records that have a location that is contained in the repStoreData collection
I've tried using some form of Join and Any but don't really understand the arguments those are asking for.
This was my best attempt and it is a no go.
... = callData.Join(d => d.LOCATION.Any(repStoreData));
Well you don't have to use a join. You could just use:
callData.Where(d => repStoreData.Contains(d.LOCATION))
That's assuming d.LOCATION is a single string.
However, you probably don't want to do that with your current declaration of repStoreData as IEnumerable<string> - LINQ won't be able to turn that into a query to be executed at the database.
If you're able to declare repStoreData as IQueryable<string>, however, that would be more likely to work well. I don't know whether that will work with ObjectSet<T>, but I'd hope so.

How to create a custom property in a Linq-to-SQL entity class?

I have two tables Studies and Series. Series are FK'd back to Studies so one Study contains a variable number of Series.
Each Series item has a Deleted column indicating it has been logically deleted from the database.
I am trying to implement a Deleted property in the Study class that returns true only if all the contained Series are deleted.
I am using O/R Designer generated classes, so I added the following to the user modifiable partial class for the Study type:
public bool Deleted
{
get
{
var nonDeletedSeries = from s in Series
where !s.Deleted
select s;
return nonDeletedSeries.Count() == 0;
}
set
{
foreach (var series in Series)
{
series.Deleted = value;
}
}
}
This gives an exception "The member 'PiccoloDatabase.Study.Deleted' has no supported translation to SQL." when this simple query is executed that invokes get:
IQueryable<Study> dataQuery = dbCtxt.Studies;
dataQuery = dataQuery.Where((s) => !s.Deleted);
foreach (var study in dataQuery)
{
...
}
Based on this http://www.foliotek.com/devblog/using-custom-properties-inside-linq-to-sql-queries/, I tried the following approach:
static Expression<Func<Study, bool>> DeletedExpr = t => false;
public bool Deleted
{
get
{
var nameFunc = DeletedExpr.Compile();
return nameFunc(this);
}
set
{ ... same as before
}
}
I get the same exception when a query is run that there is no supported translation to SQL. (
The logic of the lambda expression is irrelevant yet - just trying to get past the exception.)
Am I missing some fundamental property or something to allow translation to SQL? I've read most of the posts on SO about this exception, but nothing seems to fit my case exactly.
I believe the point of LINQ-to-SQL is that your entities are mapped for you and must have correlations in the database. It appears that you are trying to mix the LINQ-to-Objects and LINQ-to-SQL.
If the Series table has a Deleted field in the database, and the Study table does not but you would like to translate logical Study.Deleted into SQL, then extension would be a way to go.
public static class StudyExtensions
{
public static IQueryable<study> AllDeleted(this IQueryable<study> studies)
{
return studies.Where(study => !study.series.Any(series => !series.deleted));
}
}
class Program
{
public static void Main()
{
DBDataContext db = new DBDataContext();
db.Log = Console.Out;
var deletedStudies =
from study in db.studies.AllDeleted()
select study;
foreach (var study in deletedStudies)
{
Console.WriteLine(study.name);
}
}
}
This maps your "deleted study" expression into SQL:
SELECT t0.study_id, t0.name
FROM study AS t0
WHERE NOT EXISTS(
SELECT NULL AS EMPTY
FROM series AS t1
WHERE (NOT (t1.deleted = 1)) AND (t1.fk_study_id = t0.study_id)
)
Alternatively you could build actual expressions and inject them into your query, but that is an overkill.
If however, neither Series nor Study has the Deleted field in the database, but only in memory, then you need to first convert your query to IEnumerable and only then access the Deleted property. However doing so would transfer records into memory before applying the predicate and could potentially be expensive. I.e.
var deletedStudies =
from study in db.studies.ToList()
where study.Deleted
select study;
foreach (var study in deletedStudies)
{
Console.WriteLine(study.name);
}
When you make your query, you will want to use the statically defined Expression, not the property.
Effectively, instead of:
dataQuery = dataQuery.Where((s) => !s.Deleted);
Whenever you are making a Linq to SQL query, you will instead want to use:
dataQuery = dataQuery.Where(DeletedExpr);
Note that this will require that you can see DeletedExpr from dataQuery, so you will either need to move it out of your class, or expose it (i.e. make it public, in which case you would access it via the class definition: Series.DeletedExpr).
Also, an Expression is limited in that it cannot have a function body. So, DeletedExpr might look something like:
public static Expression<Func<Study, bool>> DeletedExpr = s => s.Series.Any(se => se.Deleted);
The property is added simply for convenience, so that you can also use it as a part of your code objects without needing to duplicate the code, i.e.
var s = new Study();
if (s.Deleted)
...

How to group then select elements into new class in LINQ (C# preferably)

Hi I am trying to get my head around grouping, and then building my own class in the result. I know the result of a group by is an IGrouping collection but can I access the rows as they are being built to add a couple of flags to them with a custom class?
I have a class called FlightTimes with some data, but I'd like to append some data to the rows, like a FlagRedEye. So I created a class called FlightTimeResult with the original FlightTime class data plus the flag.
Can I do this? I can't seem to figure out how to get it to work. I like to use strong types until I understand what is going on. I had to change a few things to protect my client so I apologize for any syntax errors.
IGrouping<string, FlightTimeResult> FlightTimes =
( from flighttimes in schedules.FlightTimes
group flighttimes by flighttimes.FlightType.ToString()
into groupedFlights
select new FlightTimeResult( )
{
FlightTimeData = FlightTime, // Original class data
FlagRedEye = (FlightTime.departureTime.Hour >= 0 &&
FlightTime.departureTime.Hour < 6) // Extra flag
} )
The goal is to have a collection of FlightTimesResult (FlightTime + extra flag) grouped by FlightType. Not sure how to access the individual FlightTime rows in the query 'select new FlightTimeResult()'
Do i need to use a nested query on the groupedFlights?
Thank you very much.
It is easiest achieved by calling Linq functions explicitly in following way:
IQueryable<IGrouping<string, FlightTimeResult>> query
= schedules.FlightTimes.GroupBy(
ft => ft.FlightType.ToString(), // key
ft => new FlightTimeResult() { // your constructed objects for key
FlightTimeData = ft,
FlagRedEye = (ft.departureTime.Hour >= 0 && ft.departureTime.Hour < 6)
}
);
The two-argument GroupBy operator function takes two lambdas as arguments - one for extracting keys, second for extracting values for it.
Also keep in mind that group by operation (be it group itm by key construction or GroupBy call) returns a collection of IGrouping<,>s - not a single one.
Thus it will be IEnumerable<IGrouping<,>> or IQueryable<IGrouping<,>>.
I think you're on the right track. Instead of grouping FlightTimes by FlightType, try building FlightTimeResults and grouping those by FlightType instead:
var results =
from ft in schedules.FlightTimes
group new FlightTimeResult
{
FlightTimeData = ft,
FlagRedeye = ft.DepartureTime.Hour >= 0 && ft.DepartureTime.Hour < 6
}
by ft.FlightType.ToString()
into groupedFlights
select groupedFlights;

Categories

Resources