How to Remove or Revert DataContext.DataLoadOptions? - c#

First let me start by explaining my use case:
Say there is a database "Cars". In that database, each row might have Make, Model, EngineType, etc. I have a page that is essentially a view of a single "Car" record, displaying its various stats. In that page, I have a user control that calculates and displys various MPG stats based on the "Car" record. The user control is passed the "Car" record via a property, then uses it internally for its calculations.
Inside that user control, something like this happens:
private void DataBindGrid()
{
gridView.DataSource = this.CarRecord.GetMPGStats();
}
Internal to "CarRecord", is a LINQ-to-SQL query, using the data context of CarRecord. For the purpose of this calculation, it would be more efficient to do this:
private void DataBindGrid()
{
DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Car>(c => c.Engine);
this.CarRecord.DataContext.LoadOptions = dlo;
gridView.DataSource = this.CarRecord.GetMPGStats();
}
For the purpose of this example, ignore whether or not this may be a bad design for a user control passing in a record & datacontext.
Here are the issues I'm seeing:
The page may have set its own load options before passing the record to the user control, resulting in an inefficient query.
The page may not want the new settings specified by the user control when it continues to use the Car record itself.
So I have two questions:
What is the best way to "clear" LoadOptions? Simply set DataContext.LoadOptions = new DataLoadOptions(); or = null;?
Is there any way to set some sort of temporary LoadOptions that only affect a certain operation before reverting to whatever they were before?
Thanks in advance.

data load options always have to be set before execution of first query. once a query executes there is nothing u can do with them.

Related

Completely exclude columns in DataGridView

I have a DataTable I want to use as a DataSource of a DataGridView, but with a twist: for reasons (below) I need to exclude a column from DataGridView (not just hide it, truly exclude), ideally preventing it from being generated alltogether. Theoretically, I can call Columns.RemoveAt at an appropriate moment (which is the DataBindingComplete event handler - docs), but that's too late for me (for reasons).
An obvious solution is to set AutoGenerateColumns = false and do it manually. Without having looked into the details of this, I fear I'd need to reinvent the wheel in this scenario (to keep the data bindings working etc).
My reasons for this whole esoteric are, there is huge legacy app, originally written in VB6, and there any byte-array column is just ignored by the MS Hierarchical Grid. I'm trying to emulate this behavior in a custom control derived from DataGridView, and most stuff works.
UPDATE/ANSWER
Just set dt.Columns[0].ColumnMapping = MappingType.Hidden; (courtesy of https://stackoverflow.com/a/31282356/5263865)
In modern programming, there is a tendency to separate your data (=Model) from how the data is shown to the operator (=View). An adapter class (=ViewModel) is needed to connect the Model to the View. Abbreviated this gives MVVM. If you are not familiar with this concept of separation, consider to do some background reading.
Your Data is in a DataTable. You didn't mention what kind of items are in the DataTable. To ease the discussion I'll assume that the DataTable contains a sequence of Products.
class Product
{
...
}
You have methods to put Products in the DataTable and to Access them. Something like:
interface IProductRepository
{
IEnumerable<Product> AllProducts {get;}
Product GetProductById(int productId);
void AddProductAtEnd(Product product);
void ReplaceProduct(Product product, int index);
...
}
etc. The exact methods are not important for the answer. What I try to explain is that when using this interface you hide that the Products are stored in a DataTable. This give you the freedom to change where your Products are stored: in a DataBase? A List? or maybe a file, or even the internet.
I use a generic term repository (warehouse) for something where you can store items, and later retrieve them, replace them with other items or remove them from the repository. This can be a DataTable, or a database, or maybe a file, a Dictionary, whatever. The nice thing is that I've hidden that the Products are in a DataTable.
The DataGridView
When accessing the data in a DataGridView, people tend to fiddle directly with the DataGridViewCells and DataGridViewRows.
Well, don't!
Use DataBinding instead.
In almost all forms that have DataGridViews I have the following properties:
BindingList<Product> DisplayedProducts
{
get => (BindingList<Product>)this.DataGridView1.DataSource;
set => this.DataGridView1.DataSource = value;
}
Product CurrentProduct => this.DataGridView1.CurrentRow as Product;
IEnumerable<Product> SelectedProducts => this.DataGridView1.SelectedRows
.Select(row => row.DataboundItem)
.Cast<Product>();
Back to your question
for reasons (below) I need to exclude a column from DataGridView (not just hide it, truly exclude), ideally preventing it from being generated
If I read your question literally: you don't want to generate the DataGridViewCells that are in columns that are excluded.
This does not influence the Product that each row represents, it only influences the display of these Products. For example: even though each Product has an Id, you might want not to Display this Id.
The most easy thing for this is to use visual studios designer for this. Instead of defining the columns with the DataBinder, just add the columns one by one, and use the properties of each column for the name of the column, the name of the property that it has to show, the format that is used to show the value.
Code will look like this:
DataGridView dataGridView1 = new DataGridView();
// Column to show Product.Id
DataGridViewColumn columnProductId = new DataGridViewColumn();
columnProductId.HeaderText = "ID";
columnProductId.DataPropertyName = nameof(Product.Id);
// Column to show Product.Name
DataGridViewColumn columnProductName = new DataGridViewColumn();
columnProductName.HeaderText = "Name";
columnProductName.DataPropertyName = nameof(Product.Name);
// etc. for all columns that you want to show
Note: in DataPropertyName you store the name of the Property that must be shown in this column. I use the keyword nameof, so if later the name of the property changes, this won't be a problem.
Of course, if you want some special formatting, for example for numbers or dates, you need to set the proper properties as well. This can also be done in visual studio designer.
Once that you have defined your columns, add them to the DataGridView.
To Display the Products is a two-liner:
IDataTableProducts ProductRepository {get;} // initialize in constructor
void ShowProducts()
{
IEnumerable<Product> productsToDisplay = this.ProductRepository.AllProducts;
this.DisplayedProducts = new BindingList<Product>(productsToDisplay.ToList());
}
I stumbled upon an answer: https://stackoverflow.com/a/31282356/5263865
Setting the column.ColumnMapping = MappingType.Hidden does exactly what I needed: the column isn't autogenerated anymore.
DataTable data;
data.Columns[0].ColumnMapping = MappingType.Hidden;

Efficiently and Eloquently Inserting/Deleting/Updating Child Records

I have page a that shows a header record along with a list of detail records. I'm struggling with making a clean and efficient way of inserting/deleting/updating detail records when the user clicks Save.
The detail records are shown in a jQuery DataTable, and the view model behind each detail record has an IsNew and IsRemoved property. When the user adds a detail record, its IsNew property is set to true. When the user removes a detail record, it is soft-deleted and its IsRemoved property is set to true.
When the user clicks Save and posts the page to my controller, my logic right now looks like this
[HttpPost]
public ActionResult EditData(ViewModel viewModel)
{
// Update the record's header details here
// ...
foreach (var childViewModel in viewModel.Children)
{
// Use AutoMapper to map the view model to a model
MyChildRecord childModel = this.mapper.Map<MyChildRecord>(childViewModel);
if (childViewModel.IsNew)
{
this.context.MyChildRecords.Add(childModel);
}
else if (childViewModel.IsRemoved)
{
this.context.MyChildRecords.Attach(childModel);
this.context.MyChildRecords.Remove(childModel);
}
else
{
this.context.Entry(childModel).State = EntityState.Modified;
}
}
this.context.SaveChanges();
return RedirectToAction("EditData", new { id = viewModel.Id } );
}
The thing I don't like about this code is that even if nothing about a child record is changed, I'm still updating it in the database. The only solutions I can come up with to prevent that are
Have each view model store a copy of its original values and then compare its current value to its original values when the user saves the page. I don't like this solution because I'll have to have a bunch of code to store the original values, and then I have to put the original values as hidden fields on my ASP page so that they get carried between web requests.
When the user saves the page, have the controller iterate through each child view model, load the original data from the database, and compare the view model's current values to see if the row needs to be updated or not. I don't like this method because it involves a lot of extra code for the field comparison, and I still have to make unncessary trips to the database.
This seems like a common scenario so there must be a commonly accepted way of doing this. How should I be going about this?
First, consider that while the extra updates do incur additional network traffic, the likelihood is that your actual database server is smart enough not to actually do anything to the database on disk if nothing changed.
Secondly, consider that your application might not be the only program working with your database table. Somebody else might have changed the record while you were looking at it. To be safe, you really need both solutions together: check whether the user changed your form, AND check whether the database row is the same as it was when you got the data to show to the user. If both have changed, usually it is considered an error and the user is notified.

Data Persistence across ASP.NET postbacks

Context:
I've often been in situations where our ASP.NET pages would have to show data to the user on a GridView, let him change it as he pleases (Textbox on cells) and only save it to the database when he actually hits the "Save Button". This data is usually a virtual state of the information on the page, meaning that the user can change everything without really saving it until he hits the "Save Button".
In those cases, there's always list of data that needs to be persisted across ASP.NET Postbacks. This data could be an instance of a DataTable or just some List<Someclass>.
I often see people implementing this and persisting the data on Session. On that cases i also usually see problems when it comes to some user navigating with multiple tabs open, some times on the same page. Where the data of two different tabs would get merged and cause problems of information being scrambled.
Example of how Session is often used:
private List<SomeClass> DataList
{
get
{
return Session["SomeKey"] as List<SomeClass>;
}
set
{
Session["SomeKey"] = value;
}
}
People often tries to solve it by doing something like this:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
DataList = null
}
else
{
FillGridView(DataList);
}
}
But what about when two tabs are already loaded and the user is changing the GridView values and for some weird reason he tries to save the data by hitting the Save button on the other page? I personally dislike this option.
Other ways to do this would be to put the data on ViewState. However, when it comes to persisting substantially big lists, it could impact the page heavily when it's stored on the page (HiddenField).
But, what's the best way to make that work? Once, i thought in using Session together with ViewState where the ViewState would hold an unique identifier which would index the Session saved data. That would prevent sharing the data between tabs on the browser:
private List<SomeClass> DataList
{
get
{
if (ViewState["SomeKey"] == null)
{
ViewState["SomeKey"] = Guid.NewGuid().ToString();
}
return Session[ViewState["SomeKey"].ToString()] as List<SomeClass>;
}
set {
if (ViewState["SomeKey"] == null)
{
ViewState["SomeKey"] = Guid.NewGuid().ToString();
}
Session[ViewState["SomeKey"].ToString()] = value;
}
}
On the other hand it would store a new list of data to the Session every time the user enters the page. Which would impact the server memory. Maybe they could be erased in some way.
Question:
What would be the best way of persisting that kind of data across Postbacks, considering the contexts of multiple tabs on the browser, with the less cost to the server and to the maintenance coding team?
Update:
As #nunespascal nicely posted, one option would be to store the ViewState in the Session using the SessionPageStatePersister. But unfortunately that's not an option on my case. And yet it is not very different from my last example, saving the data on the Session indexed by an UniqueId stored on the ViewState.
Would there be any other options?
There is a simple solution to that problem. Store the ViewState in the Session.
For that you need to use the SessionPageStatePersister
Refer: Page State Persister
All you need to do is override the PageStatePersister and make it use SessionPageStatePersister instead of the default HiddenFieldPageStatePersister
protected override PageStatePersister PageStatePersister
{
get
{
return new SessionPageStatePersister(this);
}
}
This even saves you the headache of maintaining a unique key. A hidden field will be used automatically to keep a unique key per instance of the page.
I've come across a similar situation. The idea is if you allow long sessions for each user to change the grid view, this means you'll also have a concurrency problem because eventually you will accept only one last set of modifications to your data.
So, my solution was, to allow changes on the database but make sure all the users see the same state via SignalR.
Now, the concurrency problem has disappeared but you still need to make the changes on the fly. You might not want to save the changes after all. I've solved this problem by applying the command design pattern. Now any set of changes can either be approved or discarded. Whenever you check the index you will see the last approved gridview. Go to update page and you see the live-updated gridview. Also, go to revisions to see old approved gridview -another advantages of command design pattern-.

How to force DataGridView to re-populate its rows using IQueryable<TEntity>

BlaEntities TestContext = new BlaEntities();
IQueryable<TestEntity> Entities = TestContext.TestEntity;
TestDataGridView.DataSource = Entities;
When I assign Entities to TestDataGridView's DataSource directly; I don't have to do anything to reflect my changes to the grid.
TestEntity entity = Entities.First();
entity.Title = "What up!?";
This is more than enough to see the change in the TestDataGridView. One exception I encountered was that if I add another row to the TestContext using TestContext.AddToTestEntity(...) , it doesn't show up in the grid (contrary to deleting it) but I got it working using the BindingSource's Add method.
BindingSource source = new BindingSource{DataSource = Entities};
TestDataGridView.DataSource = source;
source.Add(CreateNewTestEntity());
Now the only obstacle left in my way is this:
If I use filtering - like TestContext.Where(t => t.Active) - use it as DataSource to my grid, then change the first record's Active property to false, how do I refresh/reload the grid to reflect this without creating another instance of BlaEntities?
IQueryable<TestEntity> FilteredEntities =
TestContext.TestEntity.Where(t => t.Active);
TestDataGridView.DataSource = FilteredEntities;
TestEntity temp = FilteredEntities.First();
temp.Active = false;
I see it is not active anymore in grid but since the grid should show only the active records, how can I remove it from the grid without removing it from the source?
When I iterate over FilteredEntities, I can see that the temp isn't there anymore but I still can see and edit it in the grid. So what I need is something forces grid to iterate its DataSource (which is FilteredEntities) as well and populate itself again
I tried calling TestContext's Refresh method and BindingSource's reset methods.
I tried changing TestDataGrid.DataSource to null, then changing it back to FilteredEntities hoping to re-populate the rows, didn't work either.
It works if I save my changes using TestContext.SaveChanges() and use another instance of BlaEntities like TestDataGridView.DataSource = new BlaEntities().TestEntity.Where(t => t.Active) but I need to use my current instance.
So the question is, how can I make the TestGridView to reload its contents using FilteredEntities.
Any advice would be greatly appreciated. Thank you.
since you already have a BindingSource have a look at the ResetBindings method
//edit:
as from the comments below:
i would approach this problem with a factory for "Entities" ... if that factory would hold a ref to the last created IQueryable, it could implement the interface IQueryable itself, by forwarding all interface methods to that created object ... so it could act as a wrapper for your datasource that can replace that datasource by recreating it based on the predicate function and the actual state of all objects

How To Set ComboBox's SelectedItem Programmatically via Silverlight?

Here's my scenario - I am working with SL3 (and WCF Data Services) and I have a custom form that manages Employees. On the form, I have some simple TextBox(es) and some ComboBox(es) for entering basic information for an Employee. In my architecture, I have some base classes, one to handle the Collections of objects (ObservableCollection and INotifyPropertyChanged implemented) and one that is for the Entity (catches and calls OnPropertyChanged("prop"). In my code behind file (.cs), I have an event handler that handles the _SelectedItemChanged() for the ComboBox(es), for example, EmployeeType, where in the database, values might be like Director, Staff, Reporter, Manager, etc. for the Code column (other columns exist in the EmployeeType table like Description, ModifiedDate, and ID). I have some constructors on my form control, and when I load the form with an empty constructor and thus nothing is loaded (which is the way it should load and correct), everything works perfectly, i.e. I can enter data like FirstName (TextBox), HireData (TextBox), and EmployeeType (ComboBox). The issue I am having is when, I am loading this form, and I know the EmployeeType before-hand, so I don't know how to set the ComboBox's SelectedItem programmatically?
I tried something like this, say I want the form to load the EmployeeType as Manager, so I have in my Load() method:
private SetEmployeeType()
{
var employeeType = new EmployeeType { Code = "Manager" };
this.EmployeeTypeComboBox.SelectedItem = employeeType;
}
But as I'm tracing through my code (F5), I see employeeType as an EmployeeType, but it's properties not fully populated (all blank except for Code as I explicitly called "Manager" above), so when my _SelectedItemChanged() event is called, the .SelectedItem = null, and thus the ComboBox is loaded with nothing picked (the ItemSource is bound to a list and it does come through properly).
FYI, I have other methods where I load my list of EmployeeTypes, e.g. GetEmployeeTypes() and that loads fine. If the ComboBox is blank and then I pick a value, the correct value is submitted to the database, but like I noted, sometimes I want to pre-define the SelectedItem and thus disable the ComboBox to disallow the User from entering invalid data.
I even tried some LINQ like so and it seems not to work:
var type = from e in employeeTypeList // collection loads fine with 10 items
where e.Code = "Manager"
select e;
When I trace through the above query, the 'type' does come back with the correct EntityType object with all of the properties populated (count=1 which is correct), but it doesn't seem to bind to the ComboBox since the ComboBox's SelectedItemChanged() is expecting something like this:
var employeeType = this.EmployeeType.SelectedItem as EmployeeType; // i.e. expecting EmployeeType
but instead, my LINQ query brings back a value of something like:
IEnumerable<EmployeeType> // with some extra stuff...
PS. I am working off from memory since I am currently at home and this is from my code at work, so please excuse me if I am missing something obvious. I have tried different scenarios, and just can't seem to figure it out. Thanks in advance for any assistance!
It sounds like you want to set the selected item to be the manager.
If so, you want something like this:
var type = (from e in employeeTypeList
where e.Code = "Manager"
select e).FirstOrDefault();
EmployeeType.SelectedItem = type;
Your code is creating a list of managers, which even though it has only one item, does not match the data type expected by SelectedItem. The solution to this is to just extract the one item from the list using FirstOrDefault which will give the first item in the list or null if the list is empty.
Try to set the mode to TwoWay instead of Oneway if you have set so.

Categories

Resources