I have an object with two different integer properties in it, and I'm trying to get a a new object in Linq to Entities, combining two integer properties from the same object as concatenated strings, as follows
List<DateRange> collection = (from d in context.dates
select new DateRange
{
DateString = from s in context.Seasons
where s.SeasonID = d.DateID
select string.Format("{0} - {1}", s.StartYear, s.EndYear) }
).ToList<DateRange>();
The string concatenation of the years will not compile.
This will work in LINQ to Objects, provided that each object in objects is a class or struct containing "Number1" and "Number2" fields or properties:
var results = from o in objects
select string.Format("{0} - {1}", o.Number1, o.Number2);
(However, your original should work, as well....)
Assuming you are connecting to a database via LINQ to SQL/Entities, then the String.Format call will likely fail, as with those providers, the select clause is executed within the database. Not everything can be translated from C# into SQL.
To convert your database results into a string like you want to, the following should work:
var temp = (
from d in context.dates
from s in context.Seasons
where s.SeasonID == d.DateID
select new { s.StartYear, s.EndYear }
).ToList(); // Execute query against database now, before converting date parts to a string
var temp2 =
from t in temp
select new DateRange
{
DateString = t.StartYear + " - " + t.EndYear
};
List<DateRange> collection = temp2.ToList();
EDIT:
I had an additional thought. The String.Format call is most likely the problem. I am not sure if it would work or not, but what about a plain-jane concat:
List<DateRange> collection =
(from d in context.dates
select new DateRange
{
DateString = from s in context.Seasons
where s.SeasonID = d.DateID
select s.StartYear + " - " + s.EndYear
}
).ToList<DateRange>();
Your original code works if you really want what you wrote. However, if your really want to get from
var objects = new MyObject[]{
new MyObject {Int1 = 1, Int2 = 2},
new MyObject {Int1 = 3, Int2 = 4}};
something like 1 - 2 - 3 - 4 you can write
var strings = objects.Select(o = > string.Format("{0} - {1}", o.Int1, o.Int2).ToArray();
var output = string.Join(" - ", strings);
using System.Data.Objects.SqlClient;
:
:
List<DateRange> collection = (from d in context.dates
select new DateRange
{
DateString = from s in context.Seasons
where s.SeasonID = d.DateID
select SqlFunctions.StringConvert((double)s.StartYear) + " - " +
SqlFunctions.StringConvert((double)s.EndYear)
}).ToList<DateRange>();
The StringConvert method gets converted into the proper conversion function when the LINQ statement is converted to SQL for execution on the server.
Related
I'm trying to the the value of a lookup field in SharePoint using Linq and a collection of SPListItem - something like this:
int totalDepts = (from SPListItem itm in hourEntries select ((SPFieldLookupValue)itm["Level1"]).LookupValue).Distinct().Count();
But that doesn't seem to work (and it strikes me as missing some steps)
Has anyone done this before?
I wasn't able to figure out to do it directly in the Linq query, so I ended up creating WorkHoursEntries object, and populating it with all my SPListItems
List<WorkHourEntry> workHourEntries = new List<WorkHourEntry>();
foreach (SPListItem hourEntry in hourItems)
{
//Collect entries that are in the current Fiscal Year reporting period
if (fiscalYearMonths.Contains(hourEntry["Reporting_x0020_Month"].ToString()))
{
WorkHourEntry curEntry = new WorkHourEntry();
string Level1 = (string)hourEntry["Level1"];
SPFieldLookupValue val = new SPFieldLookupValue(Level1);
curEntry.organization = val.LookupValue;
SPFieldCalculated cf = (SPFieldCalculated)hourEntry.Fields["WECSCHours"];
curEntry.WECSCHours = cf.GetFieldValueForEdit(hourEntry["WECSCHours"]);
workHourEntries.Add(curEntry);
}
}
This allowed me to run Linq queries directly on the WorkHourEntry collection
var uniqueDeptNames = (from itm in workHourEntries select itm.organization).Distinct().ToArray();
I'm using LINQ's Union method to combine two or more collections. After that I'm trying to apply sorting to the combined collection by calling OrderBy on a field that is common to the collections. Here is how I am applying sorting:
combinedCollection.OrderBy(row => row["common_field"]);
combinedCollection is defined as:
Enumerable<DataRow> combinedCollection;
I need the sorting to be applied to the entire combined collection. For some reason, that is not happening. Instead I see there is sorting applied on some other field separately within each 'collection' block within the combined collection
And idea why??
First Edit
foreach (....)
{
if (combinedCollection != null)
{
combinedCollection = combinedCollection.Union(aCollection);
}
else
{
combinedCollection = aCollection;
}
}
Second Edit
_Cmd.CommandText = "SELECT Person.Contact.FirstName, Person.Contact.LastName, Person.Address.City, DATEDIFF(YY, HumanResources.Employee.BirthDate, GETDATE()) AS Age"
+ " FROM HumanResources.EmployeeAddress INNER JOIN"
+ " HumanResources.Employee ON HumanResources.EmployeeAddress.EmployeeID = HumanResources.Employee.EmployeeID INNER JOIN"
+ " Person.Address ON HumanResources.EmployeeAddress.AddressID = Person.Address.AddressID INNER JOIN"
+ " Person.Contact ON HumanResources.Employee.ContactID = Person.Contact.ContactID AND HumanResources.Employee.ContactID = Person.Contact.ContactID AND "
+ " HumanResources.Employee.ContactID = Person.Contact.ContactID AND HumanResources.Employee.ContactID = Person.Contact.ContactID";
DataTable employeeTable = new DataTable();
_Adpt.Fill(employeeTable);
DataRow[] allRows = null;
allRows = employeeTable.Select("");
IEnumerable<DataRow> filteredEmployeeRows;
filteredEmployeeRows = from row in allRows select row;
// Declare a variable to hold the city-filtered rows and set it to null for now
IEnumerable<DataRow> cityFilteredEmployeeRows = null;
//Copy filtered rows into a data table
DataTable filteredEmployeeTable = filteredEmployeeRows.CopyToDataTable<DataRow>();
foreach (DataRowView city in CityListBox.SelectedItems)
{
// create an exact copy of the data table
DataTable filteredEmployeeCopyTable = filteredEmployeeTable.Copy();
// Enumerate it
IEnumerable<DataRow> filteredEmployeeRowsInSingleCity = filteredEmployeeCopyTable.AsEnumerable();
// Apply the city filter
filteredEmployeeRowsInSingleCity = _ApplyCityFilter(filteredEmployeeRowsInSingleCity, city["City"].ToString());
if (cityFilteredEmployeeRows != null)
{
// Combine the filtered rows for this city with the overall collection of rows
cityFilteredEmployeeRows = cityFilteredEmployeeRows.Union(filteredEmployeeRowsInSingleCity);
}
else
{
cityFilteredEmployeeRows = filteredEmployeeRowsInSingleCity;
}
}
//apply ordering
cityFilteredEmployeeRows.OrderBy(row => row["Age"]);
//cityFilteredEmployeeRows.OrderByDescending(row => row["Age"]);
EmployeeGridView.DataSource = cityFilteredEmployeeRows.CopyToDataTable<DataRow>();
.......
private IEnumerable<DataRow> _ApplyCityFilter(IEnumerable<DataRow> filteredEmployeeRows, string city)
{
IEnumerable<DataRow> temp = filteredEmployeeRows;
filteredEmployeeRows = from row in temp
where row["City"].ToString() == city
select row;
return filteredEmployeeRows;
}
I think you have a problem with the LINQ lazy evaluation, I would have to investigate to find out wich part causes the problem.
Using the foreach(var item...) in lazy functions has already bitten me (because when executed later they all reference the last iterated item), but in your case it doesn't look like this is the problem.
To check it is the really the issue you can just use a DataRow[] in place of the IEnumerable<DataRow> and call .ToArray() after every LINQ function.
Edit: I'm not sure I got your code right but can't you just use:
var cities = CityListBox.SelectedItems.Cast<DataRowView>()
.Select(city => city["City"].ToString())
.ToArray();
var rows = allRows
.Where(r => cities.Contains((string)r["City"]))
.OrderBy(r => (int?)r["Age"])
.ToArray(); // if you want to evaluate directly, not mandatory
I have a linq query like this:
var accounts =
from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new AccountsReport
{
recordIndex = ?
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
}
I want to populate recordIndex with the value of current row number in collection returned by the LINQ. How can I get row number ?
Row number is not supported in linq-to-entities. You must first retrieve records from database without row number and then add row number by linq-to-objects. Something like:
var accounts =
(from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new
{
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
})
.AsEnumerable() // Moving to linq-to-objects
.Select((r, i) => new AccountReport
{
RecordIndex = i,
CreditRegistryId = r.CreditRegistryId,
AccountNumber = r.AccountNo,
});
LINQ to objects has this builtin for any enumerator:
http://weblogs.asp.net/fmarguerie/archive/2008/11/10/using-the-select-linq-query-operator-with-indexes.aspx
Edit: Although IQueryable supports it too (here and here) it has been mentioned that this does unfortunately not work for LINQ to SQL/Entities.
new []{"aap", "noot", "mies"}
.Select( (element, index) => new { element, index });
Will result in:
{ { element = aap, index = 0 },
{ element = noot, index = 1 },
{ element = mies, index = 2 } }
There are other LINQ Extension methods (like .Where) with the extra index parameter overload
Try using let like this:
int[] ints = new[] { 1, 2, 3, 4, 5 };
int counter = 0;
var result = from i in ints
where i % 2 == 0
let number = ++counter
select new { I = i, Number = number };
foreach (var r in result)
{
Console.WriteLine(r.Number + ": " + r.I);
}
I cannot test it with actual LINQ to SQL or Entity Framework right now. Note that the above code will retain the value of the counter between multiple executions of the query.
If this is not supported with your specific provider you can always foreach (thus forcing the execution of the query) and assign the number manually in code.
Because the query inside the question filters by a single id, I think the answers given wont help out. Ofcourse you can do it all in memory client side, but depending how large the dataset is, and whether network is involved, this could be an issue.
If you need a SQL ROW_NUMBER [..] OVER [..] equivalent, the only way I know is to create a view in your SQL server and query against that.
This Tested and Works:
Amend your code as follows:
int counter = 0;
var accounts =
from account in context.Accounts
from guranteer in account.Gurantors
where guranteer.GuarantorRegistryId == guranteerRegistryId
select new AccountsReport
{
recordIndex = counter++
CreditRegistryId = account.CreditRegistryId,
AccountNumber = account.AccountNo,
}
Hope this helps.. Though its late:)
I have created following function to get dates difference:
public static double MonthsDifference(DateTime dtStart, DateTime dtNow)
{
DateTime start = dtStart;
DateTime end = dtNow;
int compMonth = (end.Month + end.Year * 12) - (start.Month + start.Year * 12);
double daysInEndMonth = (end - end.AddMonths(1)).Days;
double months = compMonth + (start.Day - end.Day) / daysInEndMonth;
return months;
}
I am using it in my LINQ query
var query = from c in context.AccountOwners.Where( MonthsDifference(p.Account.StateChangeDate,DateTime.Now) < 4 )
select c;
return query.Count();
but it is giving error:
LINQ to Entities does not recognize the method 'Double MonthsDifference(System.DateTime, System.DateTime)' method, and this method cannot be translated into a store expression.
Please suggest solution
The MonthsDifference function cannot be mapped to SQL, which is why you are getting this error. You need to either rewrite the query expression without any calls to your own methods to do what you want (which may be impossible -- I don't know exactly which native database functions LINQ to Sql supports), or fetch the result set and do the filtering locally.
The latter approach would go like this:
var count = context.AccountOwners.ToArray()
.Count(o => MonthsDifference(p.Account.StateChangeDate,DateTime.Now) < 4);
If you want to do this in the Linq then you need to inline this method so that Linq2Sql can translate it into sql.
So I think you'll need something like:
var start = DateTime.Now;
var query = from c in context.AccountOwners
let accountDate = c.Account.StateChangeDate
let diff = (start.Month + start.Year * 12) - (accountDate.Month + accountDate.Year * 12) + ...
where diff < 4
select c;
return query.Count();
Linked to Months difference between dates
In LINQ to Entities you can use Entity functions:
using System.Data.Objects;
var query = from c in context.AccountOwners.Where(EntityFunctions.DiffMonths(
p.Account.StateChangeDate,DateTime.Now) < 4 )
select c;
return query.Count();
I'm very new to Linq, I can find multi-line data reading examples everywhere (by using foreach()), but what is the correct way of reading a single line of data? Like a classic Product Detail page.
Below is what I tried:
var q = from c in db.Products
where c.ProductId == ProductId
select new { c.ProductName, c.ProductDescription, c.ProductPrice, c.ProductDate };
string strProductName = q.First().ProductName.ToString();
string strProductDescription = q.First().ProductDescription.ToString();
string strProductPrice = q.First().ProductPrice.ToString();
string strProductDate = q.First().ProductDate.ToString();
The code looks good to me, but when I see the actual SQL expressions generated by using SQL Profiler, it makes me scared! The program executed four Sql expressions and they are exactly the same!
Because I'm reading four columns from a single line. I think I must did something wrong, so I was wondering what is the right way of doing this?
Thanks!
Using the First() extension method would throw the System.InvalidOperationException when no element in a sequence satisfies a specified condition.
If you use the FirstOrDefault() extension method, you can test against the returned object to see if it's null or not.
FirstOrDefault returns the first element of a sequence, or a default value if the sequence contains no elements; in this case the default value of a Product should be null. Attempting to access the properties on this null object will throw ArgumentNullException
var q = (from c in db.Products
where c.ProductId == ProductId
select new { c.ProductName, c.ProductDescription, c.ProductPrice, c.ProductDate }).FirstOrDefault();
if (q != null)
{
string strProductName = q.ProductName;
string strProductDescription = q.ProductDescription;
string strProductPrice = q.ProductPrice;
string strProductDate = q.ProductDate;
}
Also, you shouldn't have to cast each Property ToString() if you're object model is setup correctly. ProductName, ProductDescription, etc.. should already be a string.
The reason you're getting 4 separate sql queries, is because each time you call q.First().<PropertyHere> linq is generating a new Query.
var q = (from c in db.Products
where c.ProductId == ProductId
select new { c.ProductName, c.ProductDescription, c.ProductPrice, c.ProductDate }
).First ();
string strProductName = q.ProductName.ToString();
string strProductDescription = q.ProductDescription.ToString();
string strProductPrice = q.ProductPrice.ToString();
string strProductDate = q.ProductDate.ToString();