EntityDataSource Null Update Pameters not getting marked Modified - asp.net

I am using an EntityDataSource with a FormView on VB.NET application. The FormView contains an AjaxControlToolKit TabContains with multiple tabs. Due to the fact that each tab is a naming container, Bind doesn't work properly for updating values (as discovered from reading other posts on stackoverflow). I instead have to declare UpdateParameters on my EntityDataSource. Example markup is as follows:
<asp:FormView ID="fv" runat="server" DataSourceID="eds" DataKeyNames="ID">
<EditItemTemplate>
<asp:TabContainer ID="tc" runat="server">
<asp:TabPanel ID="tp" runat="server" HeaderText="Tab 1">
<ContentTemplate>
<asp:TextBox ID="tbName" runat="server" Text='<%#Eval("Name") %>'></asp:TextBox>
</ContentTemplate>
</asp:TabPanel>
</asp:TabContainer>
</EditItemTemplate>
</asp:FormView>
<asp:EntityDataSource ID="eds" runat="server" ConnectionString="name=NDSEntities"
DefaultContainerName="NDSEntities" EnableFlattening="False" EntitySetName="Customers"
Where="it.ID = #ID" EnableUpdate="true" EnableInsert="true">
<WhereParameters>
<asp:QueryStringParameter Name="ID" QueryStringField="ID" DbType="Guid" />
</WhereParameters>
<UpdateParameters>
<asp:ControlParameter Name="Name" ControlID="fv$tc$tp$tbName" DbType="String" />
</UpdateParameters>
<InsertParameters>
<asp:ControlParameter Name="Name" ControlID="fv$tc$tp$tbName" DbType="String" />
</InsertParameters>
</EntityDataSource>
This works great, until a customer is edited and their name is set to nothing (assuming in this case, a null name is allowed). The Name UpdateParameter is set to Null but the ObjectStateEntry is not set to modified for Null properties, even if previously the Entity had a value specified. As long as the name is changed to something other than Null, everything is updated correctly.
I found a workaround by putting the following code in the Updating event of the EntityDataSource.
Dim ose As ObjectStateEntry = context.ObjectStateManager.GetObjectStateEntry(action)
For Each p As Parameter In eds.UpdateParameters
ose.SetModifiedProperty(p.Name)
Next
This makes sure that each property in the UpdateParameters has its state set to modified. It works, but it seems like a hack and I can see it causing problems down the road. Is there anything else I could do?

Do you have an "Concurrency Mode" set for the entity in question? Depending on how you actually update the entity (I haven't used the EntityDataSource, but I'm guessing it internally uses the ObjectContext.Attach method), the code that creates the SQL statement, will try to update only those columns that are actually changed.
Consider the following:
void UpdatePersonEntity(int id, string firstName, string lastName)
{
Person p = new Person { Id = id };
this.Context.People.Attach(p);
p.FirstName = firstName;
p.LastName = lastName;
this.Context.SaveChanges();
}
If firstName or lastName is null, the ObjectContext would assume that it's original value hasn't been touched. This might be something you should look into. I apologize if this isn't helpful, but it might push you in the right direction.

Related

Gridview's UPDATE does not like the space in my parameter

Unfortunately, the database I'm dealing with has a space in the column name. I have a DropDownList in a GridView and I'm trying to update the column with whatever the user selects in the DropDownList. Here's how I have the DropDownList:
<asp:TemplateField HeaderText="Phase (edit)" SortExpression="EditedPhase">
<EditItemTemplate>
<asp:DropDownList ID="PhaseDropDownList" runat="server" DataSourceID="PhaseDropDown" DataTextField="Current Project Phase" DataValueField="Current Project Phase" SelectedValue='<%# Bind("Phase") %>'>
</asp:DropDownList>
</EditItemTemplate>
<asp:TemplateField>
Here's the data source:
<asp:SqlDataSource ID="PhaseDropDown" runat="server" ConnectionString="<%$ ConnectionStrings:ODSConnectionString %>" SelectCommand="select distinct [Current Project Phase] from [Phase_Table]">
</asp:SqlDataSource>
Here's how I have my update command and the parameter:
UpdateCommand="UPDATE [Pipeline] SET EditedPhase = #[Current Project Phase]"
<UpdateParameters>
<asp:Parameter Name="[Current Project Phase]" Type="String"/>
</UpdateParameters>
After doing a little bit of research, I discovered that you can not have spaces in the parameter, but most of the solutions used the code behind. I have no code behind because introducing code behind could potentially break it (needing to deal with page loads and such). How do I fix my current issue?
If there's a typo, sorry.
Yes, do not use spaces in your parameter name; the value passed to the parameter can have spaces though, so I'm not sure if that is a point of confusion. For instance, update your SQL query like:
UpdateCommand="UPDATE [Pipeline] SET EditedPhase = #CurrentProjectPhase">
<UpdateParameters>
<asp:Parameter Name="CurrentProjectPhase" Type="String" DefaultValue="Current Project Phase"/>
</UpdateParameters>
Here the value (the DefaultValue is a default to supply to the update when no value is provided) can have spaces just fine. But the name cannot. You can set the DefaultValue of the parameter in code or use a more capable parameter (like session, control parameters) to grab values from something...

Select Method with ControlParameter defaultvalue property behavior

I'm using an objectdatasource with my gridview, the ODS's SelectMethod is working but not the way I want, I'm sure it's something to do with the DefaultValue property but I've already tried to set it with no luck.
here's the code:
<asp:ObjectDataSource ID="AppointmentsDataSource" runat="server"
TypeName="DAL"
DataObjectTypeName="DatabaseModel.Appointment"
SelectMethod="getAppointmentsfiltered">
<SelectParameters>
<asp:ControlParameter ControlID="txtCal" PropertyName="Text" Type="DateTime" Name="chosenDate" DefaultValue="" />
</SelectParameters>
</asp:ObjectDataSource>
Public Function getAppointmentsfiltered(chosenDate As DateTime) As IEnumerable(Of Appointment)
If String.IsNullOrEmpty(chosenDate) Then
Return GetAllAppointments()
Else
Dim appts = (From a In context.Appointments.Include("Client")
Where a.AppointmentDate.Equals(chosenDate)
Select a).ToList()
Return appts
End If
End Function
Public Function GetAllAppointments() As IEnumerable(Of DatabaseModel.Appointment)
Dim AllAppointments = (From a In context.Appointments.Include("Client")
Order By a.AppointmentDate Descending
Select a).ToList()
Return AllAppointments
End Function
my textbox:
<asp:TextBox ID="txtCal" runat="server" AutoPostBack="true" OnTextChanged="txtCal_TextChanged" Text=""></asp:TextBox>
<img id="caltrigger" alt="Click this icon to display the calendar" src="../Images/calendar-icon.png" width="18" height="20" />
<asp:CalendarExtender ID="CalendarExtender1" runat="server" TargetControlID="txtCal" PopupButtonID="caltrigger">
</asp:CalendarExtender>
As you can see, I want to see all the appointments in the gridview initially since the value of my date textbox is empty. The filtering works fine.
Any help would be appreciated, and forgive my ignorance but I'm fairly new to asp.net
EDIT: I ended up declaring two ODS in markup and switching at runtime, which takes care of this issue but if anybody can tell me why the above code didn't work i'd appreciate it.

GridView and DropDownlist

I have a gridview that when enter to edit mode one of the column change to dropdownlist:
<EditItemTemplate>
<asp:DropDownList ID="DropDownList2" runat="server"
DataSourceID="SqlDataSource1" DataTextField="name" DataValueField="name">
</asp:DropDownList>
</EditItemTemplate>
Now i have a SqlDataSource with update method that define in this aspx file:
<UpdateParameters>
<asp:Parameter Name="name" Type="String" />
<asp:ControlParameter ControlID="DropDownList2" Type="string"
PropertyName="SelectedValue" Name="genre" />
</UpdateParameters>
now i want to get the selected value and insert it but when i press the Update button in the row in the gridview i get this error:
Could not find control 'DropDownList2' in ControlParameter 'genre'
any idea why it happen?
Yes. ControlParameters only work when the DOM can find the control you're referring to that's within the same branch of the GridView. The problem is that Gridviews are a poor way of handling DOM controls because as soon as you go into a "mode" of the GridView like the EDIT mode, the entire DOM structure changes. The DOM by the way, is the Document Object Model. Because it's changed, therefore ASP cannot find the control you're referring to.
I've overcome this by doing one of two things.
First see if it works by simply tailing the control name with the '$' character.
<UpdateParameters>
<asp:Parameter Name="name" Type="String" />
<asp:ControlParameter ControlID="MyGridView$DropDownList2" Type="string"
PropertyName="SelectedValue" Name="genre" />
</UpdateParameters>
This sometimes works if you're lucky.
If not, then you'll need to find the control, get its value and pass it into the SQL parameter programmatically in code behind.
Try something like this (I use C#) ...
protected void MyGridView_RowUpdating(object sender, GridViewUpdateEventArgs e)
{
...
GridViewRow gvRow = (GridViewRow)sender;
DropDownList myDDL = (DropDownList)gvRow.FindControl("DropDownList2");
if (myDDL != null)
{
//assuming your datasource is outside the gridview, you have to find it!
SqlDataSource sds1 = (SqlDataSource)page.FindControl("myDataSource");
if (sds1 != null)
{
sds1.UpdateParameter["genre"].DefaultValue = myDDL.SelectedValue;
... and do your databinding etc
sds1.Update();
}
}
}
I always rely on the client-side code (ASPX) to do most of the work for me, like Bind(), Eval(), etc, but over time you'll realise that to really control your program, you'll have to rely on JavaScript or code behind to do the finer bits. ASP is not always "clever" in doing what you expect it to do.

How do you have multiple controls for a gridview?

For example, I have a gridview and two textboxes.
One textbox is for the text to search for.
The second textbox is an order number to search for.
I want my gridview to populate based on one or the other. I don't know how to tell my form if the user is using a number search by that and if a name, instead search by that.
Thanks for any help.
OK hope you haven't solved this yet because I took a few minutes to come up with an example that I think will do pretty much what you want.
DB access uses a stored procedure but you can use a ObjectDataSource with DAL, or just inline the SQL statement on the SqlDataSource, etc.
Markup:
Product ID:
<asp:TextBox ID="TextBox1" runat="server"></asp:TextBox>
<asp:CompareValidator ID="CompareValidator1" ControlToValidate="TextBox1" runat="server" ErrorMessage="You must enter a number"
ValidationGroup="vg1" Type="Integer" Operator="DataTypeCheck"></asp:CompareValidator>
<br />
Description:
<asp:TextBox ID="TextBox2" runat="server"></asp:TextBox><br />
<asp:Button ID="cmdSearch" runat="server" Text="Search" ValidationGroup="vg1" /><br />
<asp:GridView ID="GridView1" runat="server" DataSourceID="SqlDataSource1">
</asp:GridView>
<asp:SqlDataSource ID="SqlDataSource1" runat="server" SelectCommand="spGetProducts"
CancelSelectOnNullParameter="False" SelectCommandType="StoredProcedure">
<SelectParameters>
<asp:ControlParameter ControlID="TextBox1" PropertyName="Text" DbType="String" DefaultValue="" />
<asp:ControlParameter ControlID="TextBox2" PropertyName="Text" DbType="Int32" DefaultValue="" />
</SelectParameters>
</asp:SqlDataSource>
And T-SQL for your query:
CREATE PROCEDURE spGetProducts
#ProductId int = NULL
,#ProductDescription nvarchar(100) = NULL
AS
BEGIN
SELECT [ProductId]
,[ProductDescription]
FROM [Products]
WHERE (
(
(#ProductId IS NULL)
OR
([ProductId] LIKE % + #ProductId + %)
)
AND
(
(#ProductDescription IS NULL)
OR
([ProductDescription] LIKE % + #ProductDescription + %;)
)
);
END
If the user doesn't enter anything in either of the fields, the SqlDataSource will still bind due to SqlDataSource.CancelSelectOnNullParameter = False but the empty parameter will not be sent with the query due to ControlParameter.DefaultValue being set. The stored procedure will then insert the NULL value into the parameter and basically skip that part of the filtering in the WHERE clause.
Hope this helps.
You can check the textboxes by using (TextBox1.Text.Trim.Length > 0) or (TextBox1.Text = "")
It sounds like what you are really asking is how do you filter your data source based on more than one possible filter parameter. Explaining that would require knowing what your data source is. Either way, the gridview is just going to display the filtered results, right?
If you are using SQL for your data source the technique is going to be totally different than filtering a collection in memory. So more information on that would be helpful.

LinqDataSource and DateTime Format

I'm trying to create a LinqDataSource to bind to a DropDownList in an ASP.NET form. I only want to show the elements according to a date (which is one of the fields in the database).
Basically, the elements I want to show are the ones that will happen in the futures (i.e. after DateTime.Now).
I was trying the following markup :
<asp:DropDownList runat="server" ID="DropDownList1"
AppendDataBoundItems="True" DataSourceID="LinqDataSource1"
DataTextField="TextField" DataValueField="ValueField">
</asp:DropDownList>
<asp:LinqDataSource ID="LinqDataSource1" runat="server"
ContextTypeName="DataContext1" TableName="Table"
Where="DateField >= #DateField">
<WhereParameters>
<asp:Parameter DefaultValue="DateTime.Now" Name="DateField"
Type="DateTime" />
</WhereParameters>
</asp:LinqDataSource>
I'm getting a format exception saying that "The string was not recognized as a valid DateTime" when I try to run it. However, the dates in my database seem to be fine, because a DateTime.Parse works perfectly on them. The DateField is of type datetime in SQL.
What am I missing here?
Thanks!
The DefaultValue was what was wrong with the code as was suggested by the others.
However, setting the DefaultValue to
"<%# DateTime.Now %>"
like Andomar suggested (which would make the markup look something like this :
<WhereParameters>
<asp:Parameter DefaultValue="<%# DateTime.Now %>" Name="DateField" Type="DateTime" />
</WhereParameters>
will not work either because DataBinding expressions are only supported on objects that have a DataBinding Event, and neither Parameter or ControlParameter have one.
For a String, it's fairly easy to create a TextBox or Label and put the <%# %> expression in the value of that new field (more details here), but it was a bit more complicated with a DateTime value, as comparing an SQL DateTime with a .NET DateTime caused an exception.
It can be done quite easily in the Page_Load event by using
DataContext DataContext1 = new DataContext();
var c = from a in DataContext1.Field
where a.DateField >= DateTime.Now
select a;
DropDownList.DataSource = c;
DropDownList.DataBind();
I suspect it is failing on the DefaultValue.
Try:
DefaultValue="<%# DateTime.Now %>"

Resources