C# lambda extract a single row string value - c#

Want to extract the text value from a lookup table's column in a db. EL is the entity for my db. Current code :
var QTypes = EL.ElogQueryType.Where<ElogQueryType>( eqt=> eqt.ID == queryTypeID);
string qType = QTypes.First().QueryType;
I get a list when I just pull .Select(... so something is wrong.

You should be able to just do if you know you will be just getting one item:
var QTypes = EL.ElogQueryType.Where(eqt=> eqt.ID == queryTypeID).Single().QueryType;
If you are not sure if you will get one or nothing use SingleOrDefault().
If you just want the first since you are expecting many records do:
var QTypes = EL.ElogQueryType.First(eqt=> eqt.ID == queryTypeID).QueryType;
Same applies if you don't know if you will get anything, use FirstOrDefault.

It's not clear what's wrong, as your current query should give you what you're after. However, you can also use the overload of First which takes a predicate:
string qType = EL.ElogQueryType.First(eqt => eqt.ID == queryTypeID)
.QueryType;
You say you "get a list when [you] pull .Select(" but it's not really clear what you mean. You haven't said what's wrong with the code you've already specified.
(As Kelsey says, there are alternatives to First: FirstOrDefault, SingleOrDefault, Single and even Last should you wish.)

Related

Custom Method in LINQ Query

I sum myself to the hapless lot that fumbles with custom methods in LINQ to EF queries. I've skimmed the web trying to detect a pattern to what makes a custom method LINQ-friendly, and while every source says that the method must be translatable into a T-SQL query, the applications seem very diverse. So, I'll post my code here and hopefully a generous SO denizen can tell me what I'm doing wrong and why.
The Code
public IEnumerable<WordIndexModel> GetWordIndex(int transid)
{
return (from trindex in context.transIndexes
let trueWord = IsWord(trindex)
join trans in context.Transcripts on trindex.transLineUID equals trans.UID
group new { trindex, trans } by new { TrueWord = trueWord, trindex.transID } into grouped
orderby grouped.Key.word
where grouped.Key.transID == transid
select new WordIndexModel
{
Word = TrueWord,
Instances = grouped.Select(test => test.trans).Distinct()
});
}
public string IsWord(transIndex trindex)
{
Match m = Regex.Match(trindex.word, #"^[a-z]+(\w*[-]*)*",
RegexOptions.IgnoreCase);
return m.Value;
}
With the above code I access a table, transIndex that is essentially a word index of culled from various user documents. The problem is that not all entries are actually words. Nubers, and even underscore lines, such as, ___________,, are saved as well.
The Problem
I'd like to keep only the words that my custom method IsWord returns (at the present time I have not actually developed the parsing mechanism). But as the IsWord function shows it will return a string.
So, using let I introduce my custom method into the query and use it as a grouping parameter, the is selectable into my object. Upon execution I get the omninous:
LINQ to Entities does not recognize the method
'System.String IsWord(transIndex)' method, and this
method cannot be translated into a store expression."
I also need to make sure that only records that match the IsWord condition are returned.
Any ideas?
It is saying it does not understand your IsWord method in terms of how to translate it to SQL.
Frankly it does not do much anyway, why not replace it with
return (from trindex in context.transIndexes
let trueWord = trindex.word
join trans in context.Transcripts on trindex.transLineUID equals trans.UID
group new { trindex, trans } by new { TrueWord = trueWord, trindex.transID } into grouped
orderby grouped.Key.word
where grouped.Key.transID == transid
select new WordIndexModel
{
Word = TrueWord,
Instances = grouped.Select(test => test.trans).Distinct()
});
What methods can EF translate into SQL, i can't give you a list, but it can never translate a straight forward method you have written. But their are some built in ones that it understands, like MyArray.Contains(x) for example, it can turn this into something like
...
WHERE Field IN (ArrItem1,ArrItem2,ArrItem3)
If you want to write a linq compatible method then you need to create an expresion tree that EF can understand and turn into SQL.
This is where things star to bend my mind a little but this article may help http://blogs.msdn.com/b/csharpfaq/archive/2009/09/14/generating-dynamic-methods-with-expression-trees-in-visual-studio-2010.aspx.
If the percentage of bad records in return is not large, you could consider enumerate the result set first, and then apply the processing / filtering?
var query = (from trindex in context.transIndexes
...
select new WordIndexModel
{
Word,
Instances = grouped.Select(test => test.trans).Distinct()
});
var result = query.ToList().Where(word => IsTrueWord(word));
return result;
If the number of records is too high to enumerate, consider doing the check in a view or stored procedure. That will help with speed and keep the code clean.
But of course, using stored procedures has disadvatages of reusability and maintainbility (because of no refactoring tools).
Also, check out another answer which seems to be similar to this one: https://stackoverflow.com/a/10485624/3481183

Converting an array and performing a contains

I am reading some data from a database table. One of the fields in the database "VendorList" returns a comma seperated list of Vendors or just one id.
Ex: "1256,553,674" or "346"
There are a couple things I need to do:
Convert this string to an int[]
Perform a "Contains" against an IEnumerable collection.
Return that collection and assign it to a property.
This code is being called inside of a .Select when creating a new object and "Vendor" is a property on that new object.
Here is my code that I am currently using:
Vendors = (m.VendorList.Contains(","))
? (from v in vendors
where m.VendorList.Split(',')
.Select(n => Convert.ToInt32(n))
.ToArray()
.Contains(v.VendorID)
select v).ToList()
: (string.IsNullOrEmpty(m.VendorList))
? null
: (from s in vendors
where s.VendorID == int.Parse(m.VendorList)
select s).ToList()
The code works but it looks very messy and it will be hard to maintain if another developer were to try and refactor this.
I am sort of new to linq, can you provide any tips to clean up this mess?
As you can see I am using two ternary operators. The first one is to detect if its a comma separated list. The second is to detect if the comma separated list even have values.
Try this. I believe it's equivalent to what you're trying to do.. correct me if I'm wrong.
You could do the following in a single line of code, but I think it's more readable (maintainable) this way.
var Vendors = new List<int>();
if (m.VendorList != null)
Vendors.AddRange(vendors.Where(v => m.VendorList
.Split(',')
.Select(y => Convert.ToInt32(y))
.Contains(v))
.Select(v => v));
Vendors = from v in vendors
let vendorList = from idString in m.Split(',')
select int.Parse(idString)
where vendorList.Contains(v.VendorID)
select v;
There is no need to check for the presence of ",".
This is a case where I'd suggest pulling part of this out of your LINQ statement:
var vendorIds = m.VendorList
.Split(new[]{','}, StringSplitOptions.RemoveEmptyEntries)
.Select(n => Convert.ToInt32(n))
.ToArray();
someObj.Vendors = vendors.Where(v => vendorIds.Contains(v.VendorID));
This is more readable. By assigning a variable to vendorIds, you indicate to future programmers what this variable means. They don't have to fully grok all your LINQ code before they can understand the general intent.
This will perform better. In your original code, you are re-parsing the entire vendor list twice for each value in vendors. This code parses it once, and reuses the data structure for all of your ID checks. (If you have large lists of vendor IDs, you can further improve performance by making vendorIds a HashSet<>.)
If your input is an empty string, the RemoveEmptyEntries part will ensure you end up with an empty list of vendor IDs, and hence no matching Vendors. If your input has only one value without commas, you'll end up with a single ID in the list.
Note that this will not behave exactly like your original code, in that it won't set the value to null if given a null or empty m.VendorList. I'm guessing that if you take time to think about it, having a null m.VendorList is not actually something you expect to happen, and it'd be better to "fail fast" if it ever did happen, rather than be left wondering why your .Vendors property ended up null. I'm also guessing that if you have an empty .Vendors property, it will be easier for consuming code to deal with correctly than if they have to check for null values.
You can try this:
string str = "356"; //"1256,553,674";
string[] arr = str.Split(',');
List<int> lst = new List<int>();
foreach (string s in arr)
{
lst.Add(Convert.ToInt32(s));
}
List will contain all numbers in your string
string str = "1256,553,674";
IEnumerable<int> array = str.Split(',').Select(n => Convert.ToInt32(n)).ToArray();

LINQ to SQL Any() throws ArgumentException when called on array of db fields

I tried to refactor the following code (which filters some database records by category and search term):
from entry in _DB.TheTable
join otherEntry in _DB.AnotherTable
on entry.PrimaryKey equals otherEntry.Field
where category.Any(String.IsNullOrWhiteSpace)
|| category.Contains(entry.ProductGroup)
where String.IsNullOrWhiteSpace(searchTerm)
|| entry.ProductNumber.Contains(searchTerm)
|| otherEntry.Name1.Contains(searchTerm)
|| otherEntry.Name2.Contains(searchTerm)
|| otherEntry.Name3.Contains(searchTerm)
select new {entry.Something, entry.SomethingElse};
to look like this:
from entry in _DB.TheTable
join otherEntry in _DB.AnotherTable on entry.PrimaryKey equals otherEntry.Field
let searchFields = new[]{entry.ProductNumber, otherEntry.Name1, otherEntry.Name2,/*...*/}
where category.Any(String.IsNullOrWhiteSpace)
|| category.Contains(entry.ProductGroup)
where String.IsNullOrWhiteSpace(searchTerm)
|| searchFields.Any(field=>field.Contains(searchTerm))
select new {entry.Something, entry.SomethingElse};
Unfortunately, LINQ to SQL doesn't seem to be capable of doing this: I get an ArgumentException from the code searchFields.Any(field=>field.Contains(searchTerm)) stating that
The argument value was of the wrong type. Expected: System.String. Actual: System.String[].
How can I adjust this to work?
EDIT: sorry, forgot to mention searchTerm is a string, category is a string[].
EDIT: The solution given here is not correct
The problem you have is that Contains, can only get String values, and you are passing String[].
I'm thinking that maybe you could do searchTerm.Contains(field) to set correctly the condition for the Any function, because the field variable is a String and you're looking for values that are into searchTerm
I hope this can help you.
See you!
The only workaround I found was to concatenate all the fields to be searched into one string and to check if the search term is contained within. Of course, this may return false positives, and a seperator would have to be inserted in between the fields to prevent that.
from entry in _DB.TheTable
join otherEntry in _DB.AnotherTable on entry.PrimaryKey equals otherEntry.Field
let searchFields = entry.ProductNumber + otherEntry.Name1 + otherEntry.Name2
+ otherEntry.Name3
where category.Any(String.IsNullOrWhiteSpace)
|| category.Contains(entry.ProductGroup)
where String.IsNullOrWhiteSpace(searchTerm)
|| searchFields.Contains(searchTerm)
select new {entry.Something, entry.SomethingElse};
I'm still open for better solutions and will accept other answers if they provide one. For the meantime, I've gone back to the original version shown in the question.

LINQ, select with only the first element of child item

With LINQ, how to do a select and only return the first associated child item?
I have:
[table: Report]
GeneratedDate
PerformanceMetric 1 -----> N [table PerformanceMetric]
Bios Timestamp
... X
Y
..... (there is a good dozen other fields)
Currently, I do:
Report = _ctx.Reports.SingleorDefault(rpt => rpt.bios == mySerial);
This returns the report with all the PerformanceMetric associated. I would like to only have the first occurrence of PerformanceMetric returned.
Does LINQ provide a built-in way of doing so?
EDIT: In fact, I am looking for the Report, but the PerformanceMetric should only contain the first element. (Report.PerformanceMetrics.Count <= 1 )
sry for not being clear..
Hard to tell exactly the full structure based on your XML, but something like:
var firstMetric = _ctx.Reports
.Where(rpt => rpt.bios == mySerial)
.Select(rpt => rpt.PerformanceMetrics) // assuming name here...
.FirstOrDefault();
This returns the first performance metric of the report matching mySerial, or default (null for classes) if the where clause didn't find any reports or no performance metrics existed.
Update: based on the update in the question, you want something different. You want the report, but with only 1 of the performance metrics. This involves mutating the original report (not sure of all the members), which can be kinda hairy because you don't want to manipulate your original data in LINQ.
So you have two options:
You can create a new instance of Report and copy all the properties and just one metric.
You can create an anonymous type with the report and the property together.
I'd probably prefer #2 since returning a modified Report might be confusing. So you could do:
var firstMetric = _ctx.Reports
.Where(rpt => rpt.bios == mySerial)
.Select(new { Report = rpt, Metric = rpt.PerformanceMetrics.FirstOrDefault() })
.FirstOrDefault();
This will scan the Reports for the one where bios == mySerial, create a new anonymous type with a Report member == to the original report, and a Metric member == the first metric in the Report list.
If no metrics exist but report exists, you'll get an anonymous type with Report = your report and Metric = null. If no report exists matching condition, returns null.
If you do really want #1, you could do this:
var firstMetric = _ctx.Reports
.Where(rpt => rpt.bios == mySerial)
.Select(new Report
{
PerformanceMetrics = rpt.PerformanceMetrics.Take(1),
// copy all other Report fields here...
})
.FirstOrDefault();
Are you looking for something like this?
var reportMetric = (from r in _ctx.Reports select r.PerformanceMetric where r.Bios == mySerial).FirstOrDefault();

Search in a List<DataRow>?

I have a List which I create from a DataTabe which only has one column in it. Lets say the column is called MyColumn. Each element in the list is an object array containing my columns, in this case, only one (MyColumn). Whats the most elegant way to check if that object array contains a certain value?
var searchValue = SOME_VALUE;
var result = list.Where(row => row["MyColumn"].Equals(searchValue)); // returns collection of DataRows containing needed value
var resultBool = list.Any(row => row["MyColumn"].Equals(searchValue)); // checks, if any DataRows containing needed value exists
If you should make this search often, I think it's not convenient to write LINQ-expression each time. I'd write extension-method like this:
private static bool ContainsValue(this List<DataRow> list, object value)
{
return list.Any(dataRow => dataRow["MyColumn"].Equals(value));
}
And after that make search:
if (list.ContainsValue("Value"))
http://dotnetperls.com/list-find-methods has something about exists & find.
Well, it depends on what version C# and .NET you are on, for 3.5 you could do it with LINQ:
var qualiyfyingRows =
from row in rows
where Equals(row["MyColumn"], value)
select row;
// We can see if we found any at all through.
bool valueFound = qualifyingRows.FirstOrDefault() != null;
That will give you both the rows that match and a bool that tells you if you found any at all.
However if you don't have LINQ or the extension methods that come with it you will have to search the list "old skool":
DataRow matchingRow = null;
foreach (DataRow row in rows)
{
if (Equals(row["MyColumn"], value))
{
matchingRow = row;
break;
}
}
bool valueFound = matchingRow != null;
Which will give you the first row that matches, it can obviously be altered to find all the rows that match, which would make the two examples more or less equal.
The LINQ version has a major difference though, the IEnumerable you get from it is deferred, so the computation will not be done until you actually enumerate it's members. I do not know enough about DataRow or your application to know if this can be a problem or not, but it was a problem in a piece of my code that dealt with NHibernate. Basically I was enumerating a sequence which members where no longer valid.
You can create your own deferred IEnumerables easily through the iterators in C# 2.0 and higher.
I may have misread this but it seems like the data is currently in a List<object[]> and not in a datatable so to get the items that match a certain criteria you could do something like:
var matched = items.Where(objArray => objArray.Contains(value));
items would be your list of object[]:s and matched would be an IEnumerable[]> with the object[]:s with the value in.

Categories