I'm working in the following Orchard code. For any reason, I can't get my property ProductsContent persisted on DB. I have rendered properly the text fields for it on the View, and created the migrations accordingly. The Leaves property of my Product part record is being stored properly, but my ProductsContent property is not.
Seems to me because you are using a viewmodel in your driver instead of the actual part, the part isn't updated automatically:
protected override DriverResult Editor(
ProductPart part,
IUpdateModel updater,
dynamic shapeHelper)
{
var model = new EditLeavesViewModel<ProductLeafEntry>();
if (updater.TryUpdateModel(model, Prefix, null, null))
{
// set the property manually here
part.ProductsContent = model.ProductsContent;
if (part.ContentItem.Id != 0)
{
_productService.UpdateLeavesForContentItem(
part.ContentItem, model.Leaves);
}
}
return Editor(part, shapeHelper);
}
Related
I am trying to update a custom field on the SOLine (UsrPOPromisedDate) when the POLine promised date is changed. Below is my graph extension, however SOLine is always null.
When I convert the BQL to T-SQL, I get the expected results returned. Why is my view always returning a null value?
using PX.Data;
using PX.KWW.MyProject.DAC;
using PX.Objects.PO;
using PX.Objects.SO;
namespace MyProject.Graph
{
public class POOrderEntryExt : PXGraphExtension<POOrderEntry>
{
public PXSelectJoin<
SOLine,
InnerJoin<SOLineSplit,
On<SOLineSplit.orderType, Equal<SOLine.orderType>,
And<SOLineSplit.orderNbr, Equal<SOLine.orderNbr>,
And<SOLineSplit.lineNbr, Equal<SOLine.lineNbr>>>>>,
Where<SOLineSplit.pOType, Equal<Current<POLine.orderType>>,
And<SOLineSplit.pONbr, Equal<Current<POLine.orderNbr>>,
And<SOLineSplit.pOLineNbr, Equal<Current<POLine.lineNbr>>>>>>
SalesOrderLine;
protected virtual void _(Events.FieldUpdated<POLine, POLine.promisedDate> eventHandler, PXFieldUpdated baseHandler)
{
baseHandler?.Invoke(eventHandler.Cache, eventHandler.Args);
POLine pOLine = eventHandler.Row;
if (pOLine is null) return;
SOLine sOLine = SalesOrderLine.Current;
SOLineExtension sOLineExtension = sOLine.GetExtension<SOLineExtension>();
if (sOLine is null || sOLineExtension is null) return;
sOLineExtension.UsrPOPromisedDate = pOLine.PromisedDate;
SalesOrderLine.Update(sOLine);
}
}
}
If you mean SalesOrderLine.Current is null, I don't believe the Current field is populated by default unless the user has selected a specific record within the view(or you manually set it).
If you are just trying to get the records within the view you would need to use SalesOrderLine.Select().
foreach(SOLine line in SalesOrderLine.Select()){
//Do Something Here
}
Looks like a mistake in your code.
And<SOLineSplit.pONbr, Equal<Current<POLine.orderType>>,
What I want to do is have the user add items to the list. Then when they add an item I need the list to save, so that when the user closes the app and opens it again, the list they've created is still there.
Right now, I can add items to my list, but as soon as i close the app they will be gone.
private static ObservableCollection<ViewModels.ZoneViewModel> Zones = new ObservableCollection<ViewModels.ZoneViewModel>();
public void PopulateListView(string image, string name, string address)
{
if (name != "" && address != "")
{
Zones.Add(new ViewModels.ZoneViewModel { Image = image, Name = name, Address = address });
Application.Current.Properties["zoneslist"] = Zones;
}
}
protected override void OnAppearing()
{
if (Application.Current.Properties.ContainsKey("zoneslist"))
{
// Put the contents of the "zoneslist" key into a variable as a string.
var savedZones = Application.Current.Properties["zoneslist"] as ObservableCollection<ViewModels.ZoneViewModel>;
// Set the listviews' itemssource to the savedzones list.
zonesList.ItemsSource = savedZones;
}
}
Here's the code I use right now, I thought this could work to save it but that doesn't work.
EDIT: So I've tried what #Alessandro Calario suggested and after using json serialization the listview just gives me a ton of empty list items(even though i only added one). But an item is added and is saved, even when the app is closed. Progress, at least, but I'm not quite there yet. Anyone know a solution?
my code:
public void PopulateListView(string image, string name, string address)
{
if (name != "" && address != "")
{
Zones.Add(new ViewModels.ZoneViewModel { Image = image, Name = name, Address = address });
//Serialize to json string
var json = JsonConvert.SerializeObject(Zones);
Application.Current.Properties["zoneslist"] = json;
}
}
protected override void OnAppearing()
{
if (Application.Current.Properties.ContainsKey("zoneslist"))
{
// Put the contents of the "zoneslist" key into a variable as a string.
var savedZones = Application.Current.Properties["zoneslist"] as string; //ObservableCollection<ViewModels.ZoneViewModel>
JsonConvert.DeserializeObject<ObservableCollection<ViewModels.ZoneViewModel>>(savedZones);
// Set the listviews' itemssource to the savedzones list.
zonesList.ItemsSource = savedZones;
}
}
I think you can Serialize your List of Objects to a json String and save it to Application Properties
If using 3rd parties libraries is not a thing for your project I highly recommend you to use Akavache. This is an Async, persistent key-value store.
Once setup is very simple to use.
//To Insert your object
IObservable<Unit> InsertObject<T>(string key, T value, DateTimeOffset? absoluteExpiration = null);
//To Get your object
IObservable<T> GetObject<T>(string key);
where T can be your whole list.
Of course it's a little more than this but trust me just a little. Read the full documentation and hope it fits your needs.
The Application Properties only stores primitive types.
Note: the Properties dictionary can only serialize primitive types for
storage. Attempting to store other types (such as List can
fail silently).
Source: https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/application-class/
Either set it up so you are using the properties as a primitive storage, or go for another local storage mechanism such as Sqlite (a good guide here: https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/databases/)
Entity Framework, Code First
I have a model with a lot of fields. One field ("Name") I do not want to be editable but read-only after insert. But I still want to show this field as part of my "Edit" form (and viewModel for edit). I'd like to show it as readonly textbox (like #Html.TextBoxFor(c => c.Name, new {#readonly = "readonly"})) but if someone change it's value with browser dev.tools it should not be saved to DB.
Is there some beautiful way to do it?
Now I'm getting an instance from DB on and set "Name" field of form data back to value from DB.
When processing the ViewModel which is sent from the frontend, ignore the "read-only" fields before saving them in the database. AFAIK EF does not support ignoring of properties.
One way to approach this using Entity Framework is to create a database model and a view model. Make sure you have the CRUD translation in the back end when saving.
public async Task<YourModelContext> SaveModel(YourModelContext context)
{
var existing = await YourTable.FirstOrDefaultAsync(f => f.Id == context.Id);
if (existing == null)
{
existing = new YourDatabaseModel
{
Created = DateTime.UtcNow
}
YourTable.Add(existing);
}
// Name will be saved and changed by the user
// existing.Name = context.Name;
// existing.Description = context.Description;
// existing.SomeOtherField = context.SomeOtherField;
// Not specifying name will not update the name.
// You'll have to set the readonly on textbox in the web page
existing.Description = context.Description;
existing.SomeOtherField = context.SomeOtherField;
// Save the changes to the underlying database
await SaveChangesAsync();
// Assign the inserted database id to the view model id
context.Id = existing.Id;
// Return the view model context to the html page
return context;
}
I am writing a asp.net mvc4 app and I am using entity framework 5. Each of my entities have fields like EnteredBy, EnteredOn, LastModifiedBy and LastModifiedOn.
I am trying to auto-save them by using the SavingChanges event. The code below has been put together from numerous blogs, SO answeres etc.
public partial class myEntities : DbContext
{
public myEntities()
{
var ctx = ((IObjectContextAdapter)this).ObjectContext;
ctx.SavingChanges += new EventHandler(context_SavingChanges);
}
private void context_SavingChanges(object sender, EventArgs e)
{
ChangeTracker.DetectChanges();
foreach (ObjectStateEntry entry in
((ObjectContext)sender).ObjectStateManager
.GetObjectStateEntries
(EntityState.Added | EntityState.Modified))
{
if (!entry.IsRelationship)
{
CurrentValueRecord entryValues = entry.CurrentValues;
if (entryValues.GetOrdinal("LastModifiedBy") > 0)
{
HttpContext currContext = HttpContext.Current;
string userName = "";
DateTime now = DateTime.Now;
if (currContext.User.Identity.IsAuthenticated)
{
if (currContext.Session["userId"] != null)
{
userName = (string)currContext.Session["userName"];
}
else
{
userName = currContext.User.Identity.Name;
}
}
entryValues.SetString(
entryValues.GetOrdinal("LastModifiedBy"), userName);
entryValues.SetDateTime(
entryValues.GetOrdinal("LastModifiedOn"), now);
if (entry.State == EntityState.Added)
{
entryValues.SetString(
entryValues.GetOrdinal("EnteredBy"), userName);
entryValues.SetDateTime(
entryValues.GetOrdinal("EnteredOn"), now);
}
else
{
string enteredBy =
entry.OriginalValues.GetString(entryValues.GetOrdinal("EnteredBy"));
DateTime enteredOn =
entry.OriginalValues.GetDateTime(entryValues.GetOrdinal("EnteredOn"));
entryValues.SetString(
entryValues.GetOrdinal("EnteredBy"),enteredBy);
entryValues.SetDateTime(
entryValues.GetOrdinal("EnteredOn"), enteredOn);
}
}
}
}
}
}
My problem is that entry.OriginalValues.GetString(entryValues.GetOrdinal("EnteredBy")) and entry.OriginalValues.GetDateTime(entryValues.GetOrdinal("EnteredOn")) are not returning the original values but rather the current values which is null. I tested with other fields in the entity and they are returning the current value which were entered in the html form.
How do I get the original value here?
I think the problem may be that you are using the instance provided by the model binder as the input to your controller method, so EF does not know anything about that entity and its original state. Your code may look like this:
public Review Update(Review review)
{
_db.Entry(review).State = EntityState.Modified;
_db.SaveChanges();
return review;
}
In that case, EF knows nothing about the Review instance that is being saved. It is trusting you and setting it as modified, so it will save all of its properties to the database, but it does not know the original state\values of that entity.
Check the section named Entity States and the Attach and SaveChanges Methods of this tutorial. You can also check the first part of this article, that shows how EF does not know about the original values and will update all properties in the database.
As EF will need to know about the original properties, you may first load your entity from the database and then update its properties with the values received in the controller. Something like this:
public Review Update(Review review)
{
var reviewToSave = _db.Reviews.SingleOrDefault(r => r.Id == review.Id);
//Copy properties from entity received in controller to entity retrieved from the database
reviewToSave.Property1 = review.Property1;
reviewToSave.Property2 = review.Property2;
...
_db.SaveChanges();
return review;
}
This has the advantage that only modified properties will be send and updated in the database and that your views and view models don't need to expose every field in your business objects, only those that can be updated by the users. (Opening the door for having different classes for viewModels and models\business objects). The obvious disadvantage is that you will incur an additional hit to the database.
Another option mentioned in the tutorial I referenced above is for you to save the original values somehow (hidden fields, session, etc) and on save use the original values to attach the entity to the database context as unmodified. Then update that entity with the edited fields. However I would not recommend this approach unless you really need to avoid that additional database hit.
Hope that helps!
I was running into a similar problem when trying to audit log the Modified values of an Entity.
It turns out during the post back the ModelBinder doesn't have access to the original values so the Model received is lacking the correct information. I fixed my problem by using this function which clones the current values, relods the object, and then reset the current values.
void SetCorrectOriginalValues(DbEntityEntry Modified)
{
var values = Modified.CurrentValues.Clone();
Modified.Reload();
Modified.CurrentValues.SetValues(values);
Modified.State = EntityState.Modified;
}
You can gain access to the DbEntityEntry though the change tracker, or the entry function from your context.
I'm creating a custom workflow activity in VS2010 targeting .NET 3.5. The DLL is actually being used in a Microsoft System Center Service Manager custom workflow, but I don't think that is my issue.
I have a public string property, that the user types in the string of what the activity should use. However, when the WF runs, it errors out 'value cannot be null'. I want to target if it is my code or something else.
When we drag my custom activity onto the designer, I'm able to type in the text of the string on the designer for that property.
public static DependencyProperty ChangeRequestStageProperty = DependencyProperty.Register("ChangeRequestStage", typeof(String), typeof(UpdateChangeRequestStage));
[DescriptionAttribute("The value to set the ChangeRequestStage Property in the ChangeRequest Extension class.")]
[CategoryAttribute("Change Request Extension")]
[BrowsableAttribute(true)]
[DesignerSerializationVisibilityAttribute(DesignerSerializationVisibility.Visible)]
public String Stage
{
get { return ((String)(base.GetValue(UpdateChangeRequestStage.ChangeRequestStageProperty))); }
set { base.SetValue(UpdateChangeRequestStage.ChangeRequestStageProperty, value); }
}
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
{
EnterpriseManagementGroup emg = CreateEMG();
//System.WorkItem.ChangeRequest Extension - ClassExtension_928bec0a_cac4_4a0a_bd89_7146c9052fbe
ManagementPackClass mpcChangeRequest = emg.EntityTypes.GetClass(new Guid("8c6c6057-56ad-3862-47ec-dc0dde80a071"));
//System.WorkItemContainsActivity Relationship Class
ManagementPackRelationship workItemContainsActivityRelationship = emg.EntityTypes.GetRelationshipClass(new Guid("2DA498BE-0485-B2B2-D520-6EBD1698E61B"));
EnterpriseManagementObject changeRequest = null;
//Loop thru each emo (Change Request in this case), and assign it. There will never be more than 1 emo returned
foreach (EnterpriseManagementObject obj in emg.EntityObjects.GetRelatedObjects<EnterpriseManagementObject>(executionContext.ContextGuid, workItemContainsActivityRelationship, TraversalDepth.OneLevel, ObjectQueryOptions.Default))
{ changeRequest = obj; }
EnterpriseManagementObjectProjection emop = new EnterpriseManagementObjectProjection(changeRequest);
if (emop != null)
{ emop.Object[mpcChangeRequest, "ChangeRequestStage"].Value = Stage; }
emop.Commit();
return base.Execute(executionContext);
}
Since it is getting a 'value cannot be null' error, I'm guessing it's on this line:
emop.Object[mpcChangeRequest, "ChangeRequestStage"].Value = Stage;
I'm going to test and see if hardcoding a value works or not. Any ideas?
enter code here
try this
if (emop != null && emop.Object[mpcChangeRequest, "ChangeRequestStage"] != null)
emop.Object[mpcChangeRequest, "ChangeRequestStage"].Value = Stage
I didn't want to leave this question wide open, so I'm updating it as to how I resolved this (a long time ago).
Rather than working with an EnterpriseManagementObjectProjection (emop), I worked with a standard EnterpriseManagementObject (emo). From there, I was able to follow a similar format from above:
ManagementPackClass mpcChangeRequest = emg.EntityTypes.GetClass(new Guid("8c246fc5-4e5e-0605-dc23-91f7a362615b"));
changeRequest[mpcChangeRequest, "ChangeRequestStage"].Value = this.Stage;
changeRequest.Commit();