FormView ConvertEmptyStringToNull and binding - asp.net

I'm using a FormView with an ObjectDataSource and binding using <%# Bind("WhateverProp") %> - and all of my nullable columns are coming back with default values of the type in them.
It appears that the FormView object doesn't have a ConvertEmtpyStringToNull property like the other binding containers do. I've found articles suggesting that this was a bug in VS 2005 / .Net 2.0 - but don't see any saying what the resolution was.
Does anyone have any suggestions as to how I can work around this without just re-capturing all of the fields in the ODS_Inserting event? I'd rather not have to write code to re-bind all of my bound fields on the form just to test for nulls.

Struggled with it too.
For a dropdownlist, I do that:
AppendDataBoundItems="true"
<asp:ListItem Text="" Value=""></asp:ListItem>
For my ObjectDataSource, even thoug my UpdateMethod takes a single parameter, the entity, I add Update params for each Nullable Field of the Entity with convert to NULL
<UpdateParameters>
<asp:Parameter Name="No_Empl_Ferme" Type="Int32" ConvertEmptyStringToNull="true" />
</UpdateParameters>
I do the same for the Insert.
Works fine.

I ended up doing this - kind of a shotgun approach, but in this case all of my empty string values should be nulls. I've also considered using a string array in the code to specify which values should be nulled - and then could just loop thru the string array instead of over all of the values.
protected void RequestItemFormView_ItemInserting(object sender, FormViewInsertEventArgs e)
{
for (int i = 0; i < e.Values.Count - 1; i++)
{
if (e.Values[i].ToString() == string.Empty)
{
e.Values[i] = null;
}
}
}

In your Object DataSource, you need to add InsertParameters for each of your nullable type with the Attribute ConvertEmtpyStringToNull="True" :
<InsertParameters>
<asp:Parameter Name="NullableFieldName" Type="Int32" ConvertEmptyStringToNull="true" />
</InsertParameters>

Quote:
Tonio - i'm not using individual params, but DataObjectTypeName instead. My insert method takes a single param, and that's the business object that I want to have saved back to the database. – Scott Ivey May 1 at 12:57
I've fixed it like this:
protected void FormViewSettings_ItemUpdating(object sender, FormViewUpdateEventArgs e)
{
OrderedDictionary values = e.NewValues as OrderedDictionary;
var personID = values["PersonID"];
if (string.IsNullOrEmpty(personID.ToString()))
{
values.Remove("PersonID");
values.Add("PersonID", null);
}
}
It's a little hack but it works fine.
This way you can set the object property to null instead of string.empty without using the ConvertEmptyStringToNull setting.

Related

asp:SqlDataSource with ROWVERSION (TIMESTAMP)

I ran into .NET (Framework, w/ WinForm and WebForms) / MS SQL project where significant tables in the database contain a TIMESTAMP (aka ROWVERSION) column (called tsModified) to prevent concurrency issues. Additionally, this project does not allow the application layer to interact directly with the tables (instead all CRUD and business logic must be done through Stored Procedures).
One of the things that has been driving me crazy is how to use an SqlDataSource that can account for the TIMESTAMP column during UPDATE.
The basic form of the CRUD procs are like this:
CREATE PROC Customer_List
#pk_Customer INT = null
SELECT id, name, tsModified
FROM Customer
WHERE #pk_Customer IS NULL OR #pk_Customer = id;
CREATE PROC Customer_Update
#id INT,
#name varchar,
#tsMod TIMESTAMP
IF NOT EXISTS (SELECT TOP 1 1 FROM Customer where id=#id and tsModified=#tsModified)
Return; --actually RAISEERROR w/ a concurrency alert telling the user to refresh & try again
UPDATE Customer SET [param/value pairs] WHERE id = #id;
Sure, you could manually define partial classes and methods to account for tsModified, and then use asp:ObjectDataSource, but that is extra work. I just want the convenience of dropping an asp:SqlDataSource on the form, and get on with my day.
BUT... the SqlDataSource does not like the TIMESTAMP as a parameter. In fact, I've literally spent days researching how to make this work, and ran into plenty of others having the same issue.
I finally figured it out. See answer below.
Here's how you can use a MS SQL ROWVERSION (TIMESTAMP) column with an asp:SqlDataSource while using Stored Procedures, to handle concurrency.
Set up your SqlDataSource like this:
<asp:SqlDataSource ID="dsRegs" runat="server" OnUpdating="dsRegs_Updating" ConnectionString="[your connstr]" InsertCommand="RegulatoryAgency_Insert" InsertCommandType="StoredProcedure" SelectCommand="RegulatoryAgency_List" SelectCommandType="StoredProcedure" UpdateCommand="RegulatoryAgency_Update" UpdateCommandType="StoredProcedure">
<InsertParameters>
<asp:Parameter Name="RegulatoryCode" Type="String" />
<asp:Parameter Name="RegulatoryName" Type="String" />
<asp:Parameter Name="RegulatoryState" Type="String" />
</InsertParameters>
<SelectParameters>
<asp:Parameter Name="pk_RegulatoryAgency" Type="DBNull" />
</SelectParameters>
<UpdateParameters>
<asp:Parameter Name="pk_RegulatoryAgency" Type="Int32" />
<asp:Parameter Name="RegulatoryCode" Type="String" />
<asp:Parameter Name="RegulatoryName" Type="String" />
<asp:Parameter Name="RegulatoryState" Type="String" />
<asp:Parameter Direction="InputOutput" Name="tsModified" Type="Empty" />
</UpdateParameters>
</asp:SqlDataSource>
The important things to notice are:
In UpdateParameters, tsModified is the TIMESTAMP value and the Type="Empty".
OnUpdating is set to the dsRegs_Updating event.
Now the code behind:
/// <summary>
/// When editing for this record/row begins in the grid, we need to get the primary key from the row,
/// and then stuff the TIMESTAMP (tsModified) into a Session variable so it persists
/// </summary>
protected void gvRegs_StartRowEditing(object sender, DevExpress.Web.Data.ASPxStartRowEditingEventArgs e)
{
int pk = (int)e.EditingKeyValue;
var db = new myDataContext();
var ra = db.RegulatoryAgency_List(pk).First();
Session["tsModified"] = ra.tsModified;
}
/// <summary>
/// Before we call the database, convert the Session var back the original Linq-to-SQL type (System.Data.Linq.Binary), then
/// convert it to a (byte) array, and update the SqlDataSource parameter with the correct value.
/// </summary>
protected void dsRegs_Updating(object sender, SqlDataSourceCommandEventArgs e)
{
DbParameter dp = e.Command.Parameters["#tsModified"];
dp.Value = ((System.Data.Linq.Binary)Session["tsModified"]).ToArray();
}
In this example, the front is using a DevExpress ASPxGridView, but the databinding and events should be similar on other databound controls. When the row editing begins, we pull the tsModified value for the record from the database and place it into a Session variable. Then the SqlDataSource fires its Updating event, we grab the Session variable, convert it back to it's original format (in my case a System.Data.Linq.Binary because this example is using Linq-to-SQL), and finally the last trick is that you can't pass the TIMESTAMP value as binary, varbinary, or byte -- is must be sent as btye[], which the .ToArray() is taking care.
With the code like this, I'm able to successfully SELECT, INSERT, UPDATE and DELETE through the SqlDataSource, and the tsModified (TIMESTAMP) value in the database increments as expected.

ASP Master pages: How to pass a value from a child page to a SqlDataSource parameter on a master page

Here's a puzzle. A datasource on a master page refers for its SelectParameter to a label control containing some text on a (grand)child page:
<asp:SqlDataSource ... SelectCommand="SELECT * FROM [tblMyTable] WHERE (([strField] = ?) ">
<SelectParameters>
<asp:ControlParameter Name="strField" ControlID="cphMaster$cphChild$lblGrandchild" propertyname="Text" DbType="String"/>
</SelectParameters>
</asp:SqlDataSource>
but this generates an error ("System.Data.OleDb.OleDbException: Data type mismatch in criteria expression.")
I've checked the obvious (strField really is a string, the ContentPlaceHolder (cph) controls are correctly identified by their IDs). Any ideas?
And is my basic approach of 'ControlParameter reading a control placed in a ContentPlaceholder(s)' a reasonable way of passing a value into a SelectParameter?
I will suggest you to create a public property with type SQLDataSource on your master page class that can be accessed to your child page.
YourMasterPage.vb (class name of master page is YourMasterPage)
Private _mastersqldatasource as SqlDataSource
Public ReadOnly Property MasterSQLDataSource() As SqlDataSource
Get
Return SqlDataSource1
End Get
End Property
Then you can access it to your child page
dim myMasterPage as YourMasterPage
myMasterPage = DirectCast(Me.Page.Master, YourMasterPage)
myMasterPage.MasterSQLDataSource.SelectParameters("strField").DefaultValue = lblGrandChild.Text
Hope this helps, I used to use this way for your case
You can use following code in your child page .cs file .
SqlDataSource ds = this.MasterPage.FindControl("datasourceid");
// now you can custimize this ds according to your problem
It turns out that my method is fine. In my real application I had several criteria, and the problem was that the SelectParameters were not in the same order as the criteria in the WHERE clause. When the order is the same, the method works.
So:
SELECT * FROM [myTable] WHERE [FieldA] = ? AND [FieldB] = ? AND [FieldC] = ?
needs:
<SelectParameters>
<asp:ControlParameter Name="FieldA" ControlID="cphMaster$cphChild$lblGrandChildA .../>
<asp:ControlParameter Name="FieldB" ControlID="cphMaster$cphChild$lblGrandChildB .../>
<asp:ControlParameter Name="FieldC" ControlID="cphMaster$cphChild$lblGrandChildC .../>
</SelectParameters>
ie, not ACB, etc. I've never before come across this behaviour, which may arise from the master/child(/grandchild) structure. Hope this helps others.
Congratulation it seems you able to fix the issue. If you allow your users to use some various browsers (e.g: IE, Firefox, Chrome, Safari).
You might need to check whether cphMaster$cphChild$lblGrandChildA is the same generate ID for your control, in some browser it will rendered as cphMaster_cphChild_lblGrandChildA with underscore, but if you only allow your user to use only one browser, then it will not be an issue.

How to avoid empty date getting converted to DateTime.Min value when saving to database? Entity Frameworks

I have a form with text box for date input. If I Provide date everything is fine. If I don't provide date (I leave it empty), DateTime.min (1/1/0001) value is sent. And this causes error: The conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value
My date property is defined as nullable:
public Nullable<System.DateTime> InstallDate { get; set; }
What I want is null value to be written to database if I don't provide date.
I know I could in my update method check for 1/1/0001 and send null, but, do I have to do that? Is there a way around this problem? Thanks
EDIT:
I solved the problem adding code below into my update method:
tank.InstallDate = tank.InstallDate == DateTime.MinValue ? null : tank.InstallDate;
So my code looks like this:
public void UpdateTank(Tank tank)
{
using (RetailFuelEntities ctx = new RetailFuelEntities())
{
tank.InstallDate = tank.InstallDate == DateTime.MinValue ? null : tank.InstallDate;
ctx.Tanks.Attach(tank);
ctx.Entry(tank).State = EntityState.Modified;
ctx.SaveChanges();
}
}
Here is my ObjectDataSource:
<asp:ObjectDataSource ID="ObjectDataSource1" runat="server" SelectMethod="GetTank"
TypeName="DataAccess.Fuel.EF4.Tanks" DataObjectTypeName="DataAccess.Fuel.EF4.Tank"
DeleteMethod="DeleteTank" InsertMethod="InsertTank" UpdateMethod="UpdateTank">
<SelectParameters>
<asp:ControlParameter ControlID="TextBoxTankId" Name="TankId" PropertyName="Text"
Type="String" />
</SelectParameters>
</asp:ObjectDataSource>
I wonder if null checking can be avoided.
The problem is that your UI control (FormView) doesn't convert an empty string to null, but instead to DateTime.MinValue. Your property happily accepts this value, because it's a valid date after all.
The solution can be found here. Basically, there's a parameter attribute specifically for this case and it's called ConvertEmptyStringToNull.

ASP.NET SqlDataSource set SessionParameter programmatically

I have a StoredProcedure called "usp_posts_getall" and it has 1 parameter called "#thisCategoryID"
in my "thisCategoryID", any values other than 0(zero) will return all the records in my Posts table.
Now I have a category menu items and each time I select, I set the value in my Session name called "SelectedCID".
So, How do I ...
Create a SessionParameter Programmatically in SqlDataSource?
UPDATE:
ok. I got it working now.
If it's a session parameter that's used by the SqlDataSource, then you can set the value in the session, e.g in Page_Load():
Session["thisCategoryID"] = theCategoryId;
(am I misunderstanding the question?)
Ok, update:
I think you can create an event handler for the SqlDataSource.OnSelecting event. In that handler, you can access the Parameters collection of the datasource and can add another Parameter to it. I currently cannot test the following code, so it might not be fully correct, but I hope you see the idea:
SqlDataSource1_OnSelecting(SqlDataSourceSelectingEventArgs args)
{
var param = new Parameter("#thisCatagoryID");
param.DefaultValue = Session["SelectedCID"];
SqlDataSource1.SelectParameters.Add(param);
}
Alternatively, you can set the parameter declaratively in the markup, e.g:
<asp:SqlDataSource ...>
<SelectParameters>
<asp:SessionParameter Name="thisCategoryID" SessionField="SelectedCID"
DefaultValue="0" />
...
</SelectParameters>
</asp:SqlDataSource>

Sorting an asp:ListView bound to an EntityDataSource

I have an asp:ListView control on an ASP.NET page. It is bound to an EntityDataSource which is setup this way:
<asp:EntityDataSource ID="EntityDataSourceOrders" runat="server"
ConnectionString="name=EntitiesContext"
DefaultContainerName="EntitiesContext" EntitySetName="SOrder"
Include="Address"
EnableDelete="True" EnableInsert="True"
EnableUpdate="True">
</asp:EntityDataSource>
In SQL Server there are two tables, SOrder and Address. SOrder has a foreign key AddressID to the Address table ("an order has one address"). The address has an alphanumeric field "Name1".
In the LayoutTemplate of the ListView is a link button to sort the orders in the list by Name1 of the order's address:
<asp:LinkButton runat="server" ID="SortButtonName" Text="Name"
CommandName="Sort" CommandArgument="Address.Name1" />
If I click this button I get an EntitySqlException telling me that "'Address.Name1' could not be resolved in the current context".
Sorting by a "flat" field of the order table - for instance "OrderCode" - works:
<asp:LinkButton runat="server" ID="SortButtonOrderCode" Text="Order number"
CommandName="Sort" CommandArgument="OrderCode" />
So the exception occurs only when I try to sort by an related field in another table. I was expecting that with the Include="Address" property of the EntityDataSource sorting by fields of the related address should be possible, but it seems not.
I've made a test hack to check the query I expect the EntityDataSource to create internally:
With Linq to Entities:
using (EntitiesContext ctx = new EntitiesContext())
{
var result = from order in ctx.SOrder.Include("Address")
orderby order.Address.Name1
select order;
foreach (SOrder x in result)
{
string test=x.Address.Name1;
}
}
Or with Entity SQL:
string queryString = #"SELECT VALUE x FROM SOrder AS x
Order By x.Address.Name1";
using (EntitiesContext ctx = new EntitiesContext())
{
ObjectQuery<SOrder> query =
new ObjectQuery<SOrder>(queryString, ctx).Include("Address");
foreach (SOrder x in query.Execute(MergeOption.AppendOnly))
{
string test=x.Address.Name1;
}
}
Both works! I get a sorted result.
Now I am a bit lost how I get this sort operation working in the ListView. Does somebody have an idea what I am doing wrong here?
Thank you in advance!
I found the solution myself. It's all a matter of three missing characters: In my code above, this ...
<asp:LinkButton runat="server" ID="SortButtonName" Text="Name"
CommandName="Sort" CommandArgument="Address.Name1" />
...is WRONG and has to be replaced by:
<asp:LinkButton runat="server" ID="SortButtonName" Text="Name"
CommandName="Sort" CommandArgument="it.Address.Name1" />
Using "it." for properties of related objects seems to be necessary in contrast to flat fields. Therefore in the second example above both ways are possible:
CommandArgument="it.OrderCode" // works
CommandArgument="OrderCode" // works as well
The same for "object identities" (primary key fields) of related objects:
CommandArgument="it.Address.AddressID" // works
CommandArgument="Address.AddressID" // works as well
But again for related properties which are not identities:
CommandArgument="it.Address.Name1" // works
CommandArgument="Address.Name1" // does NOT work
Crazy, the only place where I could find (accidentally) an indication to this solution, is this video:
How Do I Use the Entity Data Source?
...especially at around 9:40 min of the video.
Associated classes may wont work CommandArguments or eg DropDownList's DataTextField value. You may use Data Transfer Objects for listing and sorting
public class OrderDTO
{
public string AddressName1 {get;set;}
}
using (EntitiesContext ctx = new EntitiesContext())
{
var result = from order in ctx.SOrder
let dto = new OrderDTO
{
AddressName1 = order.Address.Name1
//bla bla
};
orderby dto.AddressName1
select dto;
}

Resources