I don't have a lot to say, but this is my little bit.

Showing posts with label ASP.NET LINQ C#. Show all posts
Showing posts with label ASP.NET LINQ C#. Show all posts

Wednesday, December 23, 2009

Concatenate Aggregate Field Values Using LINQ

Problem: SQL Server and other databases do not have an aggregate function for concatenation. Aggregate functions are those such as min, max, sum, count, and avg, which work on database fields from SQL queries containing a GROUP BY clause, but not grouped under that clause. A concatenation function would be very helpful when you need to quickly summarize string data from a child object when viewing the parent object.

In my example, I have a web app with a table showing Products, which have zero or more associated Events, and each Event has one Event Code. I wanted the table to display a Product with a concatenated summary of the Event Codes for each of its Events. Yesterday I blogged about how to use a LinqDataSource while also joining the Product and Event tables; this post will show how to extend that solution to concatenating one of the data fields.

There are many solutions that I didn't like or which didn't work for me:

  • The internet has many people requesting help with similar problems, and the proffered solutions usually include writing a database function, often a highly tailored one. I would have used this solution if I could have found a good, reliable, generalized function written by somebody who knew how to make tight T-SQL. I could not find such a function; I could only find functions that didn't work, wouldn't compile, or which were not generalized to my satisfaction. I did not want to tackle writing my own aggregate function in T-SQL, nor did I want to write a special CLR function.
  • I attempted many different ways of frobbing my LINQ query in such a way as to build the required concatenated list without success. This approach may be impossible.
  • I attempted to write a loop which would go over my LINQ query results, build the concatenated string, and splice it into the result set. This didn't work because I was trying to assign to a read-only member.
That last failed approach was tantalizingly close to working, and I managed to placate the compiler by moving my loop to a second function and calling that from within my LINQ query. This works because LINQ is flexible in building its lazy queries.

Here is the method which builds the concatenated string. This method will look different for different applications, but they will all select values from a database, build them into a string, and return the string.

private object GetConcatenatedEventCodes(int p)

{
var events =
from sourceEvent in Db.Events
where sourceEvent.product_id == p
select sourceEvent.event_code;

Hashtable eventCodeTable = new Hashtable();
StringBuilder eventString = new StringBuilder();

foreach (var sourceEventCode in events)
{
if(! eventCodeTable.ContainsKey(sourceEventCode))
eventCodeTable.Add(sourceEventCode, string.Empty);
}

foreach (var sourceEventCode in eventCodeTable.Keys)
{
eventString.Append(sourceEventCode);
eventString.Append(" ");
}

return eventString.ToString();
}


In this example I use a Hashtable in order to remove duplicates from my list, which was a feature I wanted.

Then, the LINQ query includes a direct call to the concatenation method. I have removed some nonessential parts of this query, but it shows how to assign the results of a method call to a named field within an anonymous class. This feature of LINQ will probably be familiar to an experienced ASP.NET programmer, but using it for string concatenation is useful and non-obvious.

var result = (

from sourceProduct in Db.Products
join sourceEvent in Db.Events
on sourceProduct.product_id equals sourceEvent.product_id

select new {
sourceProduct.product_id,
sourceProduct.xxx,
event_code = GetConcatenatedEventCodes(sourceEvent.product_id),
issue_time = sourceProduct.update_time
}
);


Because LINQ is lazy, it doesn't call GetConcatenatedEventCodes() until it actually attempts to fetch the query results.

Tuesday, December 22, 2009

Use LinqDataSource With Joined Tables


In ASP.NET with C#, the LinqDataSource is used for simple selects, updates, and deletes of rows of data in a single database table. That is fine when the programmer only needs to work with one table, but many programmers need to select from multiple tables joined together. LinqDataSource does not normally support joined tables, but there is at least one way to trick it into doing so.

The situation for which I concocted this method was when I had a single table from which I needed to select Products based on three fairly complicated parameters. The plain LinqDataSource did not support filtering based on my custom TreeView control, meaning I had to intercept LinqDataSource's OnSelecting event and write some code which would use LINQ to select the required Products, with custom WHERE clauses to handle my special TreeView parameters. Thus, only the LinqDataSource would work for me: the other DataSources do not allow the programmer to replace the results of the OnSelecting event with a LINQ query. Then, my business needs changed so that I absolutely had to join the Products table with an Events table to get access to some new required data.

That left me stuck between having to use a LinqDataSource for one essential feature, and also LinqDataSource missing the join-table feature which was also essential. I worked around this problem by tricking LinqDataSource into working with joined tables.

The crux of the hack is to intercept the LinqDataSource's OnSelecting event (which I was already doing), and also to add the data fields from the join table to the DBML business object. So in my case, I have a Product object in my DBML file, and I added two data fields to it, which would later hold the data from the join tables. Then, in the OnSelecting method, I fill in those data fields by selecting from the other tables.

var result = (

from sourceProduct in Db.Products
join sourceEvent in Db.Events
on sourceProduct.product_id equals sourceEvent.product_id

where _selectedCodes.Contains(sourceEvent.event_code)
&& sourceEvent.issue_time >= beginDate
&& sourceEvent.issue_time <= endDate

select new { sourceProduct.product_id, sourceProduct.xxx, sourceEvent.event_code, sourceEvent.issue_time }


Notice the custom WHERE clause which filters based on a list called _selectedCodes, which is built from my custom TreeView; that feature is not supported by a plain LinqDataSource. Then notice how I choose two data fields from the sourceProduct, but then choose two others from sourceEvent, which is the join table. Those latter two, event_code and issue_time, are the fields which I added to the Product object in my DBML file.

If you do not add the extra fields to you DBML object, then ASP.NET will throw an error when you try to access those fields. (Strangely, it will not throw an error when, in your LINQ query, you assign values into those fields.) For me, this happened when a GridView tried to bind to the selected data in the LinqDataSource.

I hope this helps other programmers who need to use join tables to populate DBML objects gotten via a LinqDataSource.

Friday, December 11, 2009

Use Multiple Database Instances With LINQ

If an ASP.NET web application uses LINQ to persist business objects in a SQL Server database, LINQ may have been configured without the programmer ever seeing or setting any connection strings. It can therefore be non-obvious how to point the business objects at a different instance of the same (same-schema) database, because it is not obvious where the connection string is set.

It is set in the web.config file (for desktop applications, in app.config). So if a web application needs to use different DEV, TEST, and PROD instances of a business database, web.config can be modified to point the LINQ classes to a different instance.

This is pretty simple, and fundamental to using LINQ in a business setting, but it is a piece of trivia which, if not known, can lead to much head-scratching.

Tuesday, December 8, 2009

Select-All Checkbox For GridView in ASP.NET

Sometimes when your boss asks you to make an ASP.NET web application with a GridView containing business objects, one of the required features is a checkbox on each row so that the objects can be selected individually, so that an operation can be executed on only the selected objects.

When that happens, if you don't already know how to make that happen, you will search the internet, and will find the solution:

  • Click on the smart tag for the GridView
  • Choose Edit Columns...
  • Add a new TemplateColumn to your GridView
  • Click OK
  • Click on the smart tag for the GridView again
  • Choose Edit Templates...
  • Click on the smart tag for the GridView yet again
  • Select Display: Item Template
  • Drag a CheckBox from the Toolbox panel to the item template
  • ...do whatever else you need to do with the CheckBox
Then, sometimes your boss will come back and commend you on a job well done, but notice that he wants a little bit more. In this case, your boss may ask for a button to select or deselect all the listed objects. If he does this, you will find, much to your horror as an ASP.NET programmer, that you may actually have to (brace yourself) write some code (but not much). Verily, Microsoft hasn't pre-implemented a solution to this problem, so you can't respond to your boss's request by merely clicking with your mouse. Nevertheless, there are many straightforward solutions, and here is one of them.

This solution puts a CheckBox in the header row of the GridView, and when the user selects or deselects that CheckBox, all the CheckBoxes in all the rows are selected or deselected. Begin by adding the CheckBox to your ASPX page:

  • Click on the smart tag for the GridView
  • If you aren't still editing templates, choose Edit Templates, then click the smart tag again
  • Select Display: Header Template
  • Drag a CheckBox from the Toolbox panel to the header template
  • Right-click the CheckBox and choose Properties (the Properties panel will appear)
  • Set the AutoPostBack property to true
  • Choose the Events button of the Properties panel
  • Add a method for the CheckedChanged event

That's all it takes for the UI: you have added a CheckBox to the header row, set it to post back events to the server, and set an event handler for CheckedChanged events. The last, simple thing to do is to write the code to select or deselect all the object-row CheckBoxes. Here is an example; the name of the method and the names of the CheckBoxes will be different in your application.

protected void GridViewHeaderCheckBox_CheckedChanged(object sender, EventArgs e)

{
foreach (GridViewRow row in SourceEventGridView.Rows)
{
CheckBox rowCheckBox = row.Cells[0].FindControl("GridViewRowCheckBox") as CheckBox;
rowCheckBox.Checked = (sender as CheckBox).Checked;
}
}

Other solutions may be more appropriate in other situations. This problem could certainly be solved with Select All and Select None buttons; or with JavaScript; or surely in many other ways. Whichever way you make it happen, your boss should be satisfied.

Thursday, November 12, 2009

Custom Delete For LinqDataSource and DetailsView or GridView

Recently I have been working on an ASP.NET web application. It reads data out of a database, does stuff with it, and writes out some files. One of the required features is the ability to delete records from the database. For this application, I have a DropDownList for selecting an object (where the Value on the DropDownList is the primary key of the object, and the Text is the name of the object) and a DetailsView where the fields can be displayed and edited. The data source for the DetailsView is a LinqDataSource, based on a data context defined in a .dbml file.

To enable deleting, click the quick tag (the little ">" icon on the corner of the widget on the aspx page) on the LinqDataSource and choose Enable Delete. Then click the quick tag of the DetailsView and choose Enable Deleting. A new row will be added to the DetailsView containing a link for deleting.

That is all well and good, and for a very simple database schema, that will be sufficient for deletes to work correctly. The LinqDataSource will use the primary key of the record to delete the record from the database: behind the scenes it generates SQL that is something like "DELETE FROM recordtable WHERE id=@id". This will fail, however, if the record has child records in another table, because the foreign key constraint on the other table will prevent the original record from being deleted. For better or worse, Linq swallows the error and fails silently.

The LinqDataSource does not have a straightforward way to customize the DELETE command, although other data sources such as SqlDataSource do have that. I wanted to use LinqDataSource, and after trying many solutions, I found one that worked: write a simple stored procedure to do the delete, and call that.

  1. Create a stored procedure in the database for deleting the record. As an example, it might look like this:
    CREATE PROCEDURE [dbo].[DELETE_RECORD]
    
    @RECORD_ID INT
    AS
    BEGIN
    DELETE FROM RECORDCHILDTABLE WHERE RECORDID = @RECORD_ID
    DELETE FROM RECORDTABLE WHERE ID = @RECORD_ID
    END


    You can see that I simply delete the child records first, then delete the master record. My schema is still pretty simple, having only this one relationship to worry about. A more complicated schema would require a more complicated stored procedure.


  2. Open the .dbml data context file, and find the stored procedure in the Server Explorer pane. Drag the stored procedure from the Server Explorer to the data context pane (the main part of the window displaying the tables in your .dbml file) and it appears in that pane, in a split pane. The stored procedure is now part of the data context.


  3. In the properties for the main record table (not the child record table), change the Delete property from Use Runtime to use the new stored procedure.
    • In the properties pane, click on the "..." button next to the value for the Delete property, and a Configure Behavior window will open.
    • Select your object class and choose "Delete" from the Behavior dropdown menu.
    • Select the Customize radio button, select the stored procedure from the dropdown menu, and choose the primary key of the record as a method parameter.


  4. The LINQ TO SQL data context now knows to use the stored procedure when trying to delete that record. The LinqDataSource in the .aspx page will follow this behavior, and should now safely and successfully delete the records.



I have a personal bias against stored procedures because I prefer to implement data layers as part of the application, not as part of the database. Thus, this is a less optimal solution than to teach LINQ itself how to delete the record. Nevertheless, it's pretty good, and it works.