I have the following code:
var linqResults = (from rst in QBModel.ResultsTable
group rst by GetGroupRepresentation(rst.CallerZipCode, rst.CallerState) into newGroup
select newGroup
).ToList();
With the grouping method:
private string[] GetGroupRepresentation(string ZipCode, string State)
{
string ZipResult;
if (string.IsNullOrEmpty(ZipCode) || ZipCode.Trim().Length < 3)
ZipResult = string.Empty;
else
ZipResult = ZipCode.Substring(0, 3);
return new string[]{ ZipResult, State };
}
This runs just fine but it does not group at all. The QBModel.ResultsTable has 427 records and after the linq has run linqResults still has 427. In debug I can see double-ups of the same truncated zip code and state name. I'm guessing it has to do with the array I'm returning from the grouping method.
What am I doing wrong here?
If I concatenate the return value of the truncated zip code and state name without using an array I get 84 groupings.
If I strip out the rst.CallerState argument and change the grouping method to:
private string GetGroupRepresentation(string ZipCode)
{
if (string.IsNullOrEmpty(ZipCode) || ZipCode.Trim().Length < 3)
return string.Empty;
return ZipCode.Substring(0, 3);
}
It will return me 66 groups
I don't really want to concatenate the group values as I want to use them seperately later, this is wrong as it is based on if the array worked, however, kind of like the following:
List<DataSourceRecord> linqResults = (from rst in QBModel.ResultsTable
group rst by GetGroupRepresentation(rst.CallerZipCode, rst.CallerState) into newGroup
select new MapDataSourceRecord()
{
State = ToTitleCase(newGroup.Key[1]),
ZipCode = newGroup.Key[0],
Population = GetZipCode3Population(newGroup.Key[0])
}).ToList();
Array is reference type, so when the grouping method compare two arrays with same values it can not determine they are the same, because the references are different. you can read more here
One solution would be considering a class instead of using an array for results of function, and use another class to compare your results implementing the IEqualityComparer Interface, and pass it to GroupBy method, so that the grouping method can find which combinations of ZipCode and State are really equatable. read more
Not sure if this will work because I can not replicate your code.
but maybe it will be easier to add a group key and your string[] in seperate variables before you go forth your grouping. like this.
var linqdatacleanup = QBModel.ResultsTable.Select(x=>
new {
value=x,
Representation = GetGroupRepresentation(rst.CallerZipCode, rst.CallerState),
GroupKey= GetGroupRepresentationKey(rst.CallerZipCode, rst.CallerState)
}).ToList();
so GetGroupRepresentationKey returns a single string and your GetGroupRepresentation returns your string[]
this will allow you to do your grouping on this dataset and access your data as you wanted.
but before you spend to much time on this check this stack overflow question. maybe it will help
GroupBy on complex object (e.g. List<T>)
Related
I have a list of objects with parameters ID, and Code. I want to return the Objects that contain specific letters within the Code parameter.
ID Code
---------
0 ABCD
1 LMNO
2 ARDQ
4 PQRD
List of string -> Letters = ('A','M','DQ')
For this example, it should return any objects that contain 'A' or 'M' within the Code parameter.
Results -> Object ID's 0,1,2
I tried something like this
var Results = MyObjects.FindAll(x => Letters.Contains(x.Code))
But this isn't what I want, I can't wrap my head around how to do this.
The works given the update of the question from what was originally asked.
var Results = MyObjects.Where(x => letters.Any(y => x.Code.Contains(y))).Select(x => x.ID);
for each object, letters is checked to see if it is contained in the code.
After looking at this for a while, I think the best solution is a nested foreach.
var Results = New Object();
foreach(Object row in myObject)
{
foreach(string letter in letters)
{
if(row.column2.contains(letter))
{
Results.add(row);
};
};
};
I am having a nightmare in building an efficient search query. I am using LINQ. The requirement is as follows:
In the application there is one text fox field which is used as a Quick Search.
The table I will be searching holds three fields Make, Model and Extension.
A typical keyword user can enter is like Honda Accord XL
Based on the keywords database should return me the matching rows and here the problem starts. There is no restriction on the order which keywords will be entered to prepare the phrase, i.e. one can enter Accord XL Honda or it could be like XL Accord or could just be Honda. In the example Honda is Make, Accord is the Model and XL is the extension.
Ideally the search result should only pull up perfect matches like if Honda Accord is entered it will not bring up other models from Honda. But the major problem is I don't know what they will enter and I have to look into three different columns of the table using Contains operator.
Here is what I tried:
I spit the phrase into words and place them in an array
var arr = keyWord.Split(new [] {' '}). Next step I build the query inside a loop of those array elements:
foreach (var k in arr)
{
var item = new Vehicle();
var arrayItem = k;
var query = DataContext.Vehicles.Where(v =>v.RealName.Contains(arrayItem)
|| v.Model.Contains(arrayItem)
|| v.Style.Contains(arrayItem)).ToList();
foreach (var v in query)
{
if(!result.Contains(v))
result.Add(v);
}
}
return result;
Now when the loop is executing and matching records for Make it already fills the list with say 250 items. But how can I remove unwanted items like when a record has CT as Model or TYL as Extension? If I knew the order of the words in which the keyword was created then I will have the option to remove unmatched Make, Model or Extension from the list by using one line of code for each and return the final result. But in this case if I have to do it I again have to use loop and remove unmatched items, even that will not probably give me the correct data. And definitely this is not the efficient way to do this.
--- Try to use Below way your code first retrieve respective make to database ------then filter gettting previous result.
List<Vehicle> lsvehicle= new List<Vehicle()>;
foreach (var k in arr)
{
var arrayItem = k;
lsvehicle = DataContext.Vehicles.Where(v =>v.RealName.Contains(arrayItem) ).ToList();
}
foreach (var k in arr)
{
lsvehicle = lsvehicle.Where(v =>v.Model.Contains(arrayItem) || v.Style.Contains(arrayItem)).tolist();
}
return lsvehicle ;
This can be achieved by concatenating the Make + Model + Extension string and then comparing that the whole array is contained by this string
var query = DataContext.Vehicles;
foreach (var k in arr)
{
var item = new Vehicle();
var arrayItem = k;
query = query.Where(v => (v.RealName + v.Model + v.Style).Contains(arrayItem)).ToList();
}
return query;
NOTE: Logical answer, may require syntax error correction if any
I would suggest following approach, assuming you have the option to create view in database
and you are searching through three columns
1)Create a view With all combination
2)Use linq to get the record from the view
sql
create view [Vw_VehicleSearch]
AS
Select
M+V+E [MVE],
M+E+V [MEV],
V+M+E [VME],
v+E+M [VEM],
E+M+V [EMV],
E+V+M [EVM]
from
vehicletable
c#
public List<string> Search(string quickSearchText)
{
using(var ctx=new model()))
{
var result=ctx
.Vw_VehicleSearch
.Where(v=>v.MVE.Contains(quickSearchText)
|| v=>v.MEV.Contains(quickSearchText)
.|| v=>v.VME.Contains(quickSearchText)
.|| v=>v.VEM.Contains(quickSearchText)
.|| v=>v.EMV.Contains(quickSearchText)
.|| v=>v.EVM.Contains(quickSearchText)
.ToList();
return result.Select(r=>r.MVE).ToList();
}
}
What you want is: all vehicles of which either RealName, or Model, or Style contains all keywords. This can be achieved by:
var query = DataContext.Vehicles.Where(v =>
arr.All(s => v.RealName.Contains(s))
|| arr.All(s => v.Model.Contains(s))
|| arr.All(s => v.Style.Contains(s)))
.ToList();
Entity Framework is able to translate this query into SQL because it has a trick to convert the array arr into a table (of sorts) that can be used in the SQL statement (you should take a look at the generated SQL).
This is not the most efficient way to run a query. It will become considerably slower when the number of keywords becomes "large". I don't think that will be an issue here though.
Edit: I changed my inputs from a
List<string>
to a List<int>. They should always be valid integers now, no empty/null etc.
I currently have a 6 item list, the items consistent of integers.
I also have an object that contains a coma delimited string of integers. I'm trying to match the inputs to this list.
For example
Inputs could be, 1000,2000,3000
The object contains 1000,2000,3000,4000
I would want this to match.
If my input is 1000,2000,3000,4000 and my object only contains 1000,2000,3000 -> it should not match.
Here's my current code to create the object using linq to xml. "TaxUnitIdList" is the comma delimited string. (This is when my inputs were strings)
var obj = (from tug in tugElem.Descendants("CodeData")
select new TaxUnitGroups
{
Code = (string)tug.Attribute("code"),
CodeID = (string)tug.Attribute("codeid"),
Desc = (string)tug.Attribute("desc"),
TaxUnitIdList = (string)tug.Attribute("taxunits")
});
I'm thinking something along the following or a join (which I could not get to work) this is failing me due to the where wanting a boolean (which makes sense)
var matchedTugs = (from tug in tugElem.Descendants("CodeData")
let TaxUnitIdList = (string)tug.Attribute("taxunits")
let taxArr = TaxUnitIdList.Split(',').Select(int.Parse)
where taxArr.Contains(inputTaxUnits.All()) //This is where I screw up
select new TaxUnitGroups
{
Code = (string)tug.Attribute("code"),
CodeID = (string)tug.Attribute("codeid"),
Desc = (string)tug.Attribute("desc"),
TaxUnitIdList = (string)tug.Attribute("taxunits")
}).ToList();
Try this in the Where clause:
TaxUnitIdList.Split(',')
.Select(s => int.Parse(s))
.Except(inputTaxUnits.Where(s=>int.TryParse(s, out tempInteger))
.Select(s=>int.Parse(s)))
.Any();
Basically, we want to covert the TaxUnitId list into an integer array, convert the input list into a good (tryparse) input array as well, find the difference and verify that the result is 0.
I have a string:
strCheckedCategories = "2;"
an EntityList representing a SharePoint list, with item IDs from 1 to 21:
EntityList<VendorSearchesItem> vendorSearches =
dataContext.GetList<VendorSearchesItem>("Vendor Searches");
a LINQ query returning fields from two SharePoint lists that are joined to the "Vendor Searches" list:
var vendorSearchesQuery = (from s in vendorSearches
orderby s.VendorID.Title
select new
{
Vendor = s.VendorID.Title,
Website = s.VendorID.VendorWebsite,
VendorID = s.VendorID.Id,
SearchType = s.SearchTypeID.Title,
SearchTypeId = s.SearchTypeID.Id
});
and another LINQ query returning only the items where the item ID is in the list:
var q2 = from m2 in vendorSearchesQuery
where strCheckedCategories.Contains(m2.SearchTypeId.ToString())
select m2
The problem is that, in addition to returning the item with ID 2 (desired result) the query also returns items with ID 12, 20, and 21. How can I fix that?
So, fundamentally, what you want to do here is have an IN clause in which you specify a bunch of values for a field and you want rows who's value for that column is in that set.
CAML does actually have an IN clause which you could use, but sadly LINQ to Sharepoint doesn't provide any means of generating an IN clause; it's simply not supported by the query provider.
You're trying to use a bit of a hack to get around that problem by trying to do a string comparison rather than using the proper operators, and you're running into the pitfals of stringifying all of your operations. It's simply not well suited to the task.
Since, as I said, you cannot get LINQ to SharePoint to use an IN, one option would simply be to not use LINQ, build the CAML manually, and execute it using the standard server object model. But that's no fun.
What we can do is have a series of OR checks. We'll see if that column value is the first value, or the second, or the third, etc. for all values in your set. This is effectively identical to an IN clause, it's just a lot more verbose.
Now this brings us to the problem of how to OR together an unknown number of comparisons. If it were ANDs it'd be easy, we'd just call Where inside of a loop and it would AND those N clauses.
Instead we'll need to use expressions. We can manually build the expression tree ourselves of a dynamic number of OR clauses and then the query provider will be able to parse it just fine.
Our new method, WhereIn, which will filter the query to all items where a given property value is in a set of values, will need to accept a query, a property selector of what property we're using, and a set of values of the same type to compare it to. After we have that it's a simple matter of creating the comparison expression of the property access along with each key value and then ORing all of those expressions.
public static IQueryable<TSource> WhereIn<TSource, TKey>(
this IQueryable<TSource> query,
Expression<Func<TSource, TKey>> propertySelector,
IEnumerable<TKey> values)
{
var t = Expression.Parameter(typeof(TSource));
Expression body = Expression.Constant(false);
var propertyName = ((MemberExpression)propertySelector.Body).Member.Name;
foreach (var value in values)
{
body = Expression.OrElse(body,
Expression.Equal(Expression.Property(t, propertyName),
Expression.Constant(value)));
}
return query.Where(Expression.Lambda<Func<TSource, bool>>(body, t));
}
Now to call it we just need the query, the property we're filtering on, and the collection of values:
var q2 = vendorSearchesQuery.WhereIn(vendor => vendor.SearchTypeId
, strCheckedCategories.Split(';'));
And voila.
While I'd expect that to work as is, you may need to call the WhereIn before the Select. It may not work quite right with the already mapped SearchTypeId.
You should probably use a Regex, but if you want a simpler solution then I would avoid string searching and split those strings to an array:
string strCheckedCategories = "2;4;5;7;12;16;17;19;20;21;";
string[] split = strCheckedCategories.Split(';');
It will create an empty entry in the array for the trailing semicolon delimiter. I would check for that and remove it if this is a problem:
strCheckedCategories.TrimEnd(';');
Finally now you can change your where clause:
where split.Contains(m2.SearchTypeId.ToString())
If you have a very large list it is probably worth comparing integers instead of strings by parsing strCheckedCategories into a list of integers instead:
int[] split = strCheckedCategories.Split(';').Select(x => Convert.ToInt32(x)).ToArray();
Then you can do a quicker equality expression:
where split.Contains(m2.SearchTypeId)
try:
strCheckedCategories.Split(new []{';'}).Any(x => x == m2.SearchTypeId.ToString())
Contains will do a substring match. And "20" has a substring "2".
var q2 = from m2 in vendorSearchesQuery
where strCheckedCategories.Split(';').Contains(m2.SearchTypeId.ToString())
select m2
var q2 = from m2 in vendorSearchesQuery
where strCheckedCategories.Contains(";" + m2.SearchTypeId + ";")
select m2
And your strCheckedCategories should always end with ; and start with ;, for example ;2;, ;2;3;, ...
NOTE: This trick works only when your SearchTypeId should always not contain ;. I think you should use another kind of separator like \n or simply store your checked categories in a list or some array. That's the more standard way to do.
I'm trying to get the n-th element out of a list of anonymous types returned by a LINQ query where n is a random number from 0 to 100. Messed around with it a while now and I'm not getting anywhere. My code (with names changed to protect IP):
var query = from Table1 t1 in theContext.Table1
join Table2 t2 in theContext.Table2
on ...
where ...
select new
{
partNum = t1.part_number,
partSource = t2.part_source
}
int num = new Random().Next(0, 100);
// here's where the code I've tried fails
Can I somehow do a Take<T>(100).ToList<T>()[num] to get a single anonymous type with partNum and partSource? I ended up solving this by explicitly defining a type, but it seemed like I was missing a more elegant solution here. All I want to do is return a Dictionary<string, string> to the caller so I'd prefer not to have to define a type outside of this method.
Update: ElementAt doesn't work for this. I tried adding:
// get a random part from the parts list
int num = new Random().Next(0, query.Count() - 1 );
var nthElement = query.ElementAt(num);
And I got an exception: The query operator 'ElementAt' is not supported.
You should be able to use:
var item = query.Take(100).ToList()[num];
Of course, it would be more efficient to do:
var item = query.Skip(num).First();
I believe you just want the ElementAt extension method:
var nthElement = query.ElementAt(num);
No need to mess with Take queries or such, and certainly not ToList.