Can you sort Typed DataSet DataTables with Linq OrderBy? - linq

I have a Typed DataSet DataTable which inherits TypedTableBase<T>, which in turn implements IEnumerable<T>. I can't seem to get this to work.
myDataTable.OrderBy(x => x.ID).ThenBy(y => y.ID2);
Instead I have to assign this statement to an IEnumerable(or List), then refill my DataTable manually with the newly ordered IEnumerable before I commit. Is this how it is intended to be? I've thought about creating my own extension method that will empty/refill my DataTables, but would this be wise?
Note: Typically I only need to sort for viewing purposes using DataView. But in this case I have a custom routine that must create a new access database with sorting requirements, which means I need to sort the actual DataTable so that I may re-commit it.
Thank you.

In order to do what you want, you must add the following reference to your project:
System.Data.DataSetExtensions
Once you have that added, you can order your DataTable like this:
var query = myDataTable.OrderBy(x => x.ID).ThenBy(y => y.ID2);
// use the DataView generated from the LINQ query
DataView dtv = query.AsDataView();
In order to iterate through the DataView, you can do the following:
var dtv = query.AsDataView();
foreach(DataRowView rw in dtv)
{
// you can also cast back to the typed data...
MyCustomRowType typedRow = (MyCustomRowType) rw.Row;
// do something here...
}
Alternatively you can typecast via LINQ this way:
var dtv = query.AsDataView().Cast<MyCustomRowType>();
// rowItem is of type MyCustomRowType...
foreach(var rowItem in dtv)
{
// do something here...
}

Linq extension methods do not alter the source enumerable.
var numbers = new int[]{1,2,3};
var reversed = numbers.OrderByDescending(x=>x);
foreach(var number in reversed)
Console.Write(number); // 321
foreach(var number in numbers)
Console.Write(number); // 123
If you want to sort a DataTable, you should be using DataViews. You create a view on your DataTable, then apply a Sort or Filter to it, then bind against it. Keep in mind DataSets are an older technology and not quite up to date on the latest and the greatest. A "newer" approach would be to use the Entity Framework.

Related

create a bindingsource that supports advanced sorting from IOrderedQueryable

I want to create a bindingSource that supports sorting by multiple columns and attach it to a bindingNavigator.
I am using winforms and code-first EF.
I am constructing my query in the following manner
DbSet<Person> dset = DBContext.People;
string Searchstr ="Smith";
string sortby ="LastName, FirstName"
var qry = (IOrderedQueryable<Person>) dset.Where(
p => p.LastName.Contains(Searchstr) )
.OrderBy(sortby);
binding.datasource = dset.Local.ToBindingList(); // where binding has been dropped on form at design time.
binding.sort = sortby; // this is the line that screws up the sort order
I am using the extension method documented here to achieve the multi-column order by.
The problem turned out to be that I had set binding.sortby =sortby.
Using the multi column sortby was OK in the query because of the clever extension ( see link in the question) however it does not work to set the binding.sort to an expression involving multiple columns
The code works fine if I remove that line.

linq problem with distinct function

I am trying to bind distinct records to a dropdownlist. After I added distinct function of the linq query, it said "DataBinding: 'System.String' does not contain a property with the name 'Source'. " I can guarantee that that column name is 'Source'. Is that name lost when doing distinct search?
My backend code:
public IQueryable<string> GetAllSource()
{
PromotionDataContext dc = new PromotionDataContext(_connString);
var query = (from p in dc.Promotions
select p.Source).Distinct();
return query;
}
Frontend code:
PromotionDAL dal = new PromotionDAL();
ddl_Source.DataSource = dal.GetAllSource();
ddl_Source.DataTextField = "Source";
ddl_Source.DataValueField = "Source";
ddl_Source.DataBind();
Any one has a solution? Thank you in advance.
You're already selecting Source in the LINQ query, which is how the result is an IQueryable<string>. You're then also specifying Source as the property to find in each string in the databinding. Just take out the statements changing the DataTextField and DataValueField properties in databinding.
Alterantively you could remove the projection to p.Source from your query and return an IQueryable<Promotion> - but then you would get distinct promotions rather than distinct sources.
One other quick note - using query syntax isn't really helping you in your GetAllSources query. I'd just write this as:
public IQueryable<string> GetAllSource()
{
PromotionDataContext dc = new PromotionDataContext(_connString);
return dc.Promotions
.Select(p => p.Source)
.Distinct();
}
Query expressions are great for complicated queries, but when you've just got a single select or a where clause and a trivial projection, using the dot notation is simpler IMO.
You're trying to bind strings, not Promotion objects... and strings do not have Source property/field
Your method returns a set of strings, not a set of objects with properties.
If you really want to bind to a property name, you need a set of objects with properties (eg, by writing select new { Source = Source })

How do I sort on a column that isn't in the database with LINQ to entities?

LINQ to Entities 3.5 doesn't support String.Join so I'm binding my gridview to a property I define outside the Select statement. Obviously it's not letting me sort on RecipientNames because it's just an IEnumerable and that doesn't make sense. How can I use LINQ to Entities to sort on my new column? If possible I'd like to get rid of RecipientNamesList altogether and create something that LINQ will be able to handle for sorting.
IQueryable<NotificationDetail> resultsFlattened = results.Select(n => new NotificationDetail()
{
..
RecipientNames = n.NotificationRecipients.Select(nr => nr.Recipient.RecipientNameFirst + " " + nr.Recipient.RecipientNameLast).Where(s => s.Trim().Length > 0)});
});
IQueryable<NotificationDetail> resultsPaged = ApplySortingPaging(resultsFlattened,SortPageOptions);
return resultsPaged.ToEntityList(results.Count()); //blows up here, obviously
public string RecipientNamesList
{
get
{
return String.Join(", ", RecipientNames.ToArray());
}
}
It blows up because the sort cannot be translated into SQL. The easy solution is to make sure you perform the sort locally and not in SQL Server. Just add a ToList or AsEnumerable after the first query.
resultsFlattened = resultsFlattened.ToList();
You'll want to ensure that you do your paging before this, otherwise you could be pulling down a large number of rows from the database.
Or you use the Linq.Translations library developed by by Damien Guard and others. It enables you to use local, calculated properties just as you require.

Entity Framework - LinQ projection problem

I want to create an Entity Object from a LinQ statement, but I don't want to load all its columns.
My ORDERS object has a lot of columns, but I just want to retrieve the REFERENCE and OPERATION columns so the SQL statement and result will be smaller.
This LinQ statement works properly and loads all my object attributes:
var orders = (from order in context.ORDERS
select order);
However the following statement fails to load only two properties of my object
var orders = (from order in context.ORDERS
select new ORDERS
{
REFERENCE = order.REFERENCE,
OPERATION = order.OPERATION
});
The error thrown is:
The entity or complex type
'ModelContextName.ORDERS' cannot be
constructed in a LINQ to Entities
query.
What is the problem? Isn't it possible to partially load an object this way?
Thank you in advance for your answers.
ANSWER
Ok I should thank you both Yakimych and Dean because I use both of your answers, and now I have:
var orders = (from order in context.ORDERS
select new
{
REFERENCE = order.REFERENCE,
OPERATION = order.OPERATION,
})
.AsEnumerable()
.Select(o =>
(ORDERS)new ORDERS
{
REFERENCE = o.REFERENCE,
OPERATION = o.OPERATION
}
).ToList().AsQueryable();
And I get exactly what I want, the SQL Statement is not perfect but it returns only the 2 columns I need (and another column which contains for every row "1" but I don't know why for the moment) –
I also tried to construct sub objects with this method and it works well.
No, you can't project onto a mapped object. You can use an anonymous type instead:
var orders = (from order in context.ORDERS
select new
{
REFERENCE = order.REFERENCE,
OPERATION = order.OPERATION
});
The problem with the above solution is that from the moment you call AsEnumerable(), the query will get executed on the database. In most of the cases, it will be fine. But if you work with some large database, fetching the whole table(or view) is probably not what you want. So, if we remove the AsEnumerable, we are back to square 1 with the following error:
The entity or complex type 'ModelContextName.ORDERS' cannot be constructed in a LINQ to Entities query.
I have been struggling with this problem for a whole day and here is what I found. I created an empty class inheriting from my entity class and performed the projection using this class.
public sealed class ProjectedORDERS : ORDERS {}
The projected query (using covariance feature):
IQueryable<ORDERS> orders = (from order in context.ORDERS
select new ProjectedORDERS
{
REFERENCE = order.REFERENCE,
OPERATION = order.OPERATION,
});
Voilà! You now have a projected query that will map to an entity and that will get executed only when you want to.
I think the issue is creating new entities within the query itself, so how about trying this:
context.ORDERS.ToList().Select(o => new ORDERS
{
REFERENCE = o.REFERENCE,
OPERATION = o.OPERATION
});

Why does DataGrid call Linq query when scrolling?

I am just getting started with Linq, WPF and Silverlight. I am trying to display data which originates from a XML document in a DataGrid. I use a Linq query to select the objects I want and link the result to the DataGrid.
XDocument doc = GedView.GedcomConverter.ConvertToXml(new StreamReader(e.Result));
var query = from person in doc.Descendants("INDI")
select new PersonInfo()
{
Id = (string)person.Attribute("Value"),
GedcomName = (string)person.Descendants("NAME").SingleOrDefault().Attribute("Value"),
Sex = (string)person.Descendants("SEX").SingleOrDefault().Attribute("Value"),
BirthDate = GedcomConverter.ConvertDate(person.Descendants("BIRT").SingleOrDefault()),
DeathDate = GedcomConverter.ConvertDate(person.Descendants("DEAT").SingleOrDefault()),
BurialDate = GedcomConverter.ConvertDate(person.Descendants("BURI").SingleOrDefault()),
};
DataGrid.ItemsSource = query;
DataGrid.SelectedIndex = -1;
However, when the grid is scrolled, the performance is bad. I notice that the ConvertDate method is called many times. (The ConvertDate method converts a human-readable date string into a DateTime? object.)
Why is this? I had assumed that the 'query' would be executed once and not continuously.
What would be the right way to do this? I am using a query because I will want to add some kind of filter to restrict the items in the list at a later date.
Thanks
Try:-
DataGrid.ItemsSource = query.ToList();
The DataGrid is not expecting the IEnumerable that it is accessing to cause something very expensive to happen when it gets an enumerator to find items. However by passing the query itself to the DataGrid you cause the query to execute every time data grid calls GetEnumerator.
Since you want to filter at a later date you could simply re-assign the ItemsSource when you change your filter settings.

Resources