PDA

View Full Version : [CLOSED] MVC Ext 2.5 RestProxy and Grid Insert Issue



LAEUser
Jul 17, 2014, 4:02 PM
Hi

I am having an issue using RestProxy with grid row editor. My update/Read/Delete controller actions are called fine and work properly. However, when I try to add new record The Create action method is not called. Also, if I just cancel, the new row is not removed from the store it is still showing in the grid.

I am getting this error message



{"success":false,"message":"Store update, insert, or delete statement affected an unexpected number of rows (0). Entities may have been modified or deleted since entities were loaded. Refresh ObjectStateManager entries.","data":null}


What I am missing. Can you see anything wrong. Here is some of the code. Thank you in advance

// Model


using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;

using LAE.Model;
using Ext.Net.MVC;

namespace LAE.Web.ViewModels.SFAccount
{
[Model(Name = "GoalDetails")]
//[ClientResource(Path = "~/ViewModels/Customer/storeHelpers.js")]
public class GoalDetails
{
[Field(FieldType = typeof(Ext.Net.Hidden))]
[ModelField(IDProperty = true)]
public Guid GoalID { get; set; }

[ModelField()]
[Column(Order = 1, Text = "Goal Name")]
[Field(FieldLabel = "Goal Name", AllowBlank = false)]
public string GoalName { get; set; }

[Field(FieldType = typeof(Ext.Net.TextField), ReadOnly = true, AllowBlank = true)]
[ModelField()]
public Guid AccountID { get; set; }

[Field(FieldType = typeof(Ext.Net.ComboBox), AllowBlank = true)]
[ModelField()]
public Guid DeptID { get; set; }

[Field(AllowBlank = true)]
[ModelField(UseNull = true)]
public string DeptName { get; set; }

[Field(FieldLabel = "Account Name22", AllowBlank = true)]
[ModelField(UseNull = true)]
public string AccountName { get; set; }

}

}


// View is loaded as partial view in a tab panel. The view is card layout


@using Ext.Net;
@using Ext.Net.MVC;
@using System.Collections;
@using System.Collections.Generic;
@using LAE.Web.ViewModels.SFAccount;

@{
ViewBag.Title = "Initiate";
var X = Html.X();
Layout = null;
//Layout = "~/Views/Shared/_BaseLayoutNoMgr.cshtml";
}

<script>
var onWrite = function (store, operation) {
var record = operation.getRecords()[0],
name = Ext.String.capitalize(operation.action),
verb;

if (name == 'Destroy') {
record = operation.records[0];
verb = 'Deletion Request';
} else {
verb = name + 'd';
}
Ext.net.Notification.show({ title: name, html: Ext.String.format("{0}<br/>{1}", verb, operation.resultSet.message), hideDelay: 4000 });
};
</script>

@model IEnumerable<GoalDetails>

@(X.Panel()
.ID("WizardPanel")
.Title("Discover Process")
.BodyPadding(15)
.Height(300)
.Layout(LayoutType.Card)
.ActiveIndex(0)
.Items(
Html.X().Panel()
.ID("pnlStep1")
.Header(false)
.Frame(false)
.Border(false)
.Layout(LayoutType.Border)
.Padding(10)
.BodyStyle("background: #ffffff;")
.Margins("0 0 0 0")
.Items(
Html.X().ComboBox()
.Region(Region.North)
.FieldLabel("Account Name")
.ID("cbAccountSearch")
.InputWidth(390)
.Margins("2 0 4 0")
.LabelWidth(120)
.LabelAlign(LabelAlign.Top)
.PageSize(25)
.Note("Type Account Name to search and select an account. (**) to show all")
.MinChars(2)
.DisplayField("Name")
.ValueField("ID")
.HideBaseTrigger(true)
.TriggerAction(TriggerAction.Query)
.TypeAhead(false)
.ListConfig(Html.X().BoundList()
.LoadingText("Searching...")
.Height(300)
.ItemTpl(Html.X().XTemplate()
.Html(@<text>
<div class="search-item">
<h3><span>{Name}</span>{AccountNumber}</h3>
{AccountType}
</div>
</text>)
)
)
.Listeners(ls =>
ls.Select.Handler = "App.AccountTpl.overwrite(App.pnlAccountDetail.body , App.cbAccountSearch.store.getById(App.cbAccountSea rch.value).data);App.storeAccountDetail.reload();A pp.storeGoals.reload();"
)
.Store(store => store.Add(Html.X().Store()
.Proxy(proxy => proxy.Add(Html.X().AjaxProxy()
.Url(Url.Action("GetAccountsActive", "Process"))
.Reader(reader => reader.Add(Html.X().JsonReader()
.Root("data")
.TotalProperty("total")
))
))
.Model(model => model.Add(Html.X().Model()
.IDProperty("ID")
.Fields(modelFields =>
{
modelFields.Add(Html.X().ModelField().Name("Name"));
modelFields.Add(Html.X().ModelField().Name("Description"));
modelFields.Add(Html.X().ModelField().Name("Email1"));
modelFields.Add(Html.X().ModelField().Name("AccountNumber"));
modelFields.Add(Html.X().ModelField().Name("AccountType"));
modelFields.Add(Html.X().ModelField().Name("Industry"));
modelFields.Add(Html.X().ModelField().Name("AnnualRevenue").Type(ModelFieldType.Float));
modelFields.Add(Html.X().ModelField().Name("Website"));
modelFields.Add(Html.X().ModelField().Name("MailingAddress").IsComplex(true));
modelFields.Add(Html.X().ModelField().Name("ID"));
})
))
)),
Html.X().Panel()
.Layout(LayoutType.Border)
.Region(Region.Center)
.Border(false)
.Frame(false)
.Header(false)
.Margins("4 0 4 0")
.Items(
Html.X().Panel()
.ID("pnlAccountDetail")
.Region(Region.North)
.Border(false)
.Height(160)
.BodyPadding(6)
.Html("Please select an account to see additional details."),
Html.X().GridPanel()
.ID("gridAccountDetail")
.Region(Region.Center)
.Frame(true)
.Store(Html.X().Store()
.ID("storeAccountDetail")
.AutoLoad(false)
.Model(Html.X().Model()
.Name("AccountContactModel")
.Fields(
new ModelField("ID"),
new ModelField("AccountID"),
new ModelField("FullNameLast"),
new ModelField("LastName"),
new ModelField("LastName"),
new ModelField("Title"),
new ModelField("Email1"),
new ModelField("PhoneWork"),
new ModelField("PhoneMobile")
)
)
.Proxy(
Html.X().AjaxProxy()
.Url(Url.Action("GetContactData", "Process"))
.ActionMethods(action =>
{
action.Read = HttpMethod.POST;
})
.Reader(
Html.X().JsonReader().Root("data")
)
)
.Parameters(p =>
{
p.Add(new StoreParameter("AccountID", "App.cbAccountSearch.value", ParameterMode.Raw));
})
)
.ColumnModel(
Html.X().Column().Text("Name").Width(120).DataIndex("FullNameLast"),
Html.X().Column().Text("Title").Width(120).DataIndex("Title").Editor(Html.X().TextField().AllowBlank(true)),
Html.X().Column().Text("Email").Width(120).DataIndex("Email1").Editor(Html.X().TextField().AllowBlank(true).Sta ndardVtype(ValidationType.Email)),
Html.X().Column().Text("Work Phone").Width(120).DataIndex("PhoneWork").Editor(Html.X().TextField().AllowBlank(true)),
Html.X().Column().Text("Mobile Phone").Width(120).DataIndex("PhoneMobile").Editor(Html.X().TextField().AllowBlank(true))
)
.Plugins(
Html.X().RowEditing()
.Listeners(l =>
{
l.CancelEdit.Handler = "if(e.record.phantom){e.store.remove(e.record);}";
}),
Html.X().FilterHeader()
)
.BottomBar(
Html.X().PagingToolbar().HideRefresh(true)
)
)
),
X.Panel()
.ID("Panel2")
.Header(false)
.Border(false)
.Items(
Html.X().GridPanel().ID("gridGoals")
.Layout(LayoutType.Fit)
.Height(500)
.Frame(false)
.Store(
Html.X().StoreForModel().Control(s =>
{
s.ID = "storeGoals";
s.AutoSync = true;
s.Proxy.Add(
new RestProxy
{
AppendAction = false,
Reader = {
new JsonReader {
Root = "data",
IDProperty = "GoalID",
ModelName = "GoalDetails",
MessageProperty = "message"
}
},
API =
{
Read = Url.Action("GetGoalsByAccountID", "Process"),
Update = Url.Action("Update", "Process"),
Create = Url.Action("Create", "Process"),
Destroy = Url.Action("Destroy", "Process")
},
Writer = {
new JsonWriter
{
AllowSingle = true
}
}
}
);
s.Listeners.Write.Fn = "onWrite";
})
.Parameters(p =>
{
p.Add(new StoreParameter("AccountID", "App.cbAccountSearch.value", ParameterMode.Raw));
})
)
.TopBarItem(
Html.X().Button().ItemID("btnAdd").Text("Add Goal").Icon(Icon.Add)
.Handler("this.up('grid').store.insert(0, new GoalDetails({GoalID: '41197ea2-622e-46b3-92d1-c6c49deee7d8', GoalName: 'Goal...', AccountName: 'Account A', DeptName: 'Sales', DeptID: '601AD9E3-6E2A-4642-92A3-813D1234A87D', AccountID: 'D6C5A8A3-F22B-4388-9938-B9AEC8FCBBAF'}));this.up('grid').editingPlugin.sta rtEdit(0, 0);"),

Html.X().Button()
.ItemID("btnRemove").Text("Delete").Icon(Icon.Delete)
.Handler("this.up('grid').deleteSelected();"),

Html.X().Button()
.ItemID("btnRefresh").Text("Refresh").Icon(Icon.Reload)
.Handler("this.up('grid').store.reload();")

)
.ColumnModel(
Html.X().ColumnFor(Model, m => m.GoalID)
.ToBuilder<Column.Builder>()
.Width(40),

Html.X().ColumnFor(Model, m => m.GoalName)
.ToBuilder<Column.Builder>()
.Text("Goal Name").Flex(1)
.Editor(
Html.X().TextField().AllowBlank(false)
),

Html.X().ColumnFor(Model, m => m.AccountName)
.ToBuilder<Column.Builder>()
.Text("Account Name").Width(190),

Html.X().ColumnFor(Model, m => m.DeptName)
.ToBuilder<Column.Builder>()
.Text("Department Name")
.Width(190)
.Editor(
Html.X().ComboBox()
.TypeAhead(true)
.AllowBlank(false)
.SelectOnTab(true)
.Items(
"Sales",
"IT",
"Marketing",
"Finance"
)
)
)
.Plugins(
Html.X().RowEditing()
.Listeners(l =>
{
l.CancelEdit.Handler = "if(e.record.phantom){e.store.remove(e.record);}";
})
)
),

X.Panel()
.ID("Panel3")
.Html("<h1>Congratulations!</h1><p>Step 3 of 3 - Complete</p>")
.Border(false)
.Header(false)
)
.Buttons(
X.Button()
.ID("btnPrev")
.Text("Prev")
.Disabled(true)
.Icon(Icon.PreviousGreen)
.DirectEvents(de =>
{
de.Click.Url = Url.Action("Prev_Click");
de.Click.ExtraParams.Add(new Parameter("index", "#{WizardPanel}.items.indexOf(#{WizardPanel}.layout .activeItem)", ParameterMode.Raw));
}),

X.Button()
.ID("btnNext")
.Text("Next")
.Icon(Icon.NextGreen)
.DirectEvents(de =>
{
de.Click.Url = Url.Action("Next_Click");
de.Click.ExtraParams.Add(new Parameter("index", "#{WizardPanel}.items.indexOf(#{WizardPanel}.layout .activeItem)", ParameterMode.Raw));
})
)
)


@(Html.X().XTemplate()
.ID("AccountTpl")
.Html(@<text>
<strong>Account:</strong> {Name}<br />
<strong>Email:</strong> {Email1}<br />
<strong>Description:</strong> {Description}<br />
<strong>Industry:</strong> {Industry}<br />
<strong>Account Type:</strong> {AccountType}<br />
<strong>Annual Revenue:</strong> {AnnualRevenue:usMoney}<br />
<strong>Website:</strong> {Website}<br />
<strong>Mailing Address:</strong> {MailingAddress.Street1}<br />
{MailingAddress.City}, {MailingAddress.State} {MailingAddress.PostalCode}
</text>)
)





// Controller action is never called
[AcceptVerbs(HttpVerbs.Post)]
public RestResult Create(GoalDetails goal)
{
TransactionalInformation transaction;

return new RestResult
{
Success = true,
Message = e.Message
};

}

Daniil
Jul 17, 2014, 5:16 PM
Hi @LAEUser,

It looks to be something with the Entity framework.
http://stackoverflow.com/questions/1836173/entity-framework-store-update-insert-or-delete-statement-affected-an-unexpec


Also, if I just cancel, the new row is not removed from the store it is still showing in the grid.

Yes, you can cancel saving, but it doesn't mean that the records disappear on client. At least, there is no such the functionality. You could do it manually.

LAEUser
Jul 17, 2014, 5:45 PM
Danill

Thanks for the quick response. I am using this example as my basis. http://mvc.ext.net/#/GridPanel_Update/Restful/
In this example, when you get the row editor with Update and Cancel, clicking on Cancel does remove the phamtom record.

I do not get that behavior. Also, I will look at your suggestion but I am perplexed since the controller has not fired so I am not sure how EF would return an error from the client. is there a store event I can add to the client to see what is being sent to controller.

**EDIT - i figure out this part. it looks like e.record.phantom was always false since I am assigning an ID to my new record. instead now, I just use e.store.reload() and that seems to work.

thanks
LAE

LAEUser
Jul 17, 2014, 6:19 PM
Daniil

I have solved the issue with the error which was a EF error. but that was happening because the Update action will fire instead of the Create

You can see I have defined the actions for Read, Create, Update, Destroy in the store. Any ideas why the Create will not fire. I have fixed the Update to handle both inserts and update but I just want to understand why the Create would not fire.

thanks for your help



Html.X().StoreForModel().Control(s =>
{
s.ID = "storeGoals";
s.AutoSync = true;
s.Proxy.Add(
new RestProxy
{
AppendAction = false,
Reader = {
new JsonReader {
Root = "data",
IDProperty = "GoalID",
ModelName = "GoalDetails",
MessageProperty = "message"
}
},
API =
{
Read = Url.Action("GetGoalsByAccountID", "Process"),
Create = Url.Action("GoalCreate", "Process"),
Update = Url.Action("Update", "Process"),
Destroy = Url.Action("Destroy", "Process")
},
Writer = {
new JsonWriter
{
AllowSingle = true
}
}
}
);
s.Listeners.Write.Fn = "onWrite";
})

Daniil
Jul 18, 2014, 1:58 PM
but that was happening because the Update action will fire instead of the Create

Please demonstrate how you add a new record to the Store. I guess you might set some value for an IDPropery ModelField. If so, such a record is not considered as a new one. An IDProperty ModelField is supposed to get its value from a server.

LAEUser
Jul 19, 2014, 3:48 PM
Daniil

Thank you. The problem was the IDProperty. you can close this thread.
thanks for your help.
LAE