UpdateRelatedObject method only works when the sourceProperty is not collection - asp.net-web-api

I am getting the following error when I try to update tree of object using asp.net webapi OData:
"UpdateRelatedObject method only works when the sourceProperty is not collection."
My code is provided below. I got this error when the mehod "UpdateRelatedObject" is called. Can you please advise what is wrong with my code and how to update tree of objects (meaning object contains collection of child objects) using asp.net webapi odata v4.
var container = new Container(new Uri("http://JohnAlbert.com/MyOdataTest/odata"));
Product product = container.Products.Expand(p=> p.ProductItems).Expand(p=>p.ProductInvoices).Where(p => p.PId == Guid.Parse("28C508B8-F2DC-45C2-B401-7F94E79AB347")).FirstOrDefault();
if (product != null)
{
product.Name = product.Name + "_Modified";
var pitem1 = product.ProductItems[0];
product.ProductItems.Remove(pitem1);
container.UpdateRelatedObject(product, "ProductItems", pitem1);
var pitem2 = product.ProductItems[0];
pitem2.Price = 999;
container.UpdateRelatedObject(product, "ProductItems", pitem1);
var pInv1 = product.ProductInvoices[0];
product.ProductInvoices.Remove(pInv1);
container.UpdateRelatedObject(product, "ProductInvoices", pInv1);
}
container.UpdateObject(product);
container.SaveChanges(SaveChangesOptions.BatchWithSingleChangeset);

What you actually want to delete the relationship between some items in a collection-valued navigation property of an entity and itself. In such case DeleteLink() is the right method to use. In this case the following code should do the work:
if (product != null)
{
var pitem1 = product.ProductItems[0];
var pitem2 = product.ProductItems[0];
var pInv1 = product.ProductInvoices[0];
container.DeleteLink(product, "ProductItems", pitem1);
container.DeleteLink(product, "ProductItems", pitem2);
container.DeleteLink(product, "ProductInvoices", pInv1);
container.SaveChanges();
}
You may think the above way isn't as intuitive as directly removing the navigation items from the entity using .Remove() as you did. For this problem, the entity tracking enabled by using DataServiceCollection<T> can help. You can use this blog post as a tutorial for how to use DataServiceCollection<T>: http://blogs.msdn.com/b/odatateam/archive/2014/04/10/client-property-tracking-for-patch.aspx

To delete a contained navigation property, you can use DataServiceContext.DeleteObject.
To delete a relationship between an entity and its navigation property, you can use DataServiceContext.DeleteLink
To update an object, you can use DataServiceContext.UpdateObject.
So according to your scenario, you could use following code
if (product != null)
{
product.Name = product.Name + "_Modified";
dsc.UpdateObject(product);
var pitem1 = product.ProductItems[0];
//This is to remove relationship
container.DeleteLink(product, "ProductItems", pitem1);
// This is to remove the object
//container.DeleteObject(pitem1);
var pitem2 = product.ProductItems[0];
pitem2.Price = 999;
container.UpdateObject(pitem2);
var pInv1 = product.ProductInvoices[0];
//This is to remove relationship
container.DeleteLink(product, "ProductInvoices", pInv1);
// This is to remove the object
//container.DeleteObject(pInv1);
container.SaveChanges(SaveChangesOptions.BatchWithSingleChangeset);
}

Related

Acumatica - How to update custom field in Customer Location from custom field in Customer

I've tried a few different ways to do this and each way has a piece of code I am just not getting right. I need to update a custom field UsrCustomerShipAccount in Customer Locations if it is updated in the Customer Delivery Tab. I tried SetValueExt and creating a graph instance. Sorry about the dumb question.
The way that seemed to get me the closest is below:
protected void LocationExtAddress_UsrCustomerShipAccnt_FieldUpdated(PXCache cache, PXFieldUpdatedEventArgs e, PXFieldUpdated InvokeBaseHandler)
{
if(InvokeBaseHandler != null)
InvokeBaseHandler(cache, e);
var row = (LocationExtAddress)e.Row;
if (row == null) return;
PXSelectBase<Location> locationObj = new PXSelect<Location, Where<Location.bAccountID, Equal<Required<Location.bAccountID>>>>(Base);
Location deliveryLocation = locationObj.Select(row.LocationBAccountID);
var locationExt = PXCache<Location>.GetExtension<LocationExt>(location); <-- This generates error that there is no LocationExt.
deliveryLocation.Cache.SetValueExt(deliveryLocation, "UsrCustomerShipAccount", -->This needs to be the value that changed LocationExtAddress.UsrCustomerShipAccount but I don't see how to get this<--);
deliveryLocation.Cache.IsDirty = true;
deliveryLocation.Update(deliveryLocation); <--I don't know if this doesn't work because it is wrong or if it is because "UsrCustomerShipAccount" is not in deliverLocation.
}
You have
var locationExt = PXCache<Location>.GetExtension<LocationExt>(location);
shouldn't this be
var locationExt = PXCache<Location>.GetExtension<LocationExt>(deliveryLocation );
?

How to get a standalone / unmanaged RealmObject using Realm Xamarin

Is there a way that when I read an object from Realm that it can become a standalone or unmanaged object? In EF, this is called no tracking. The usage for this would be when I want to implement more business logic on my data objects before they are updated on the persistent data storage. I may want to give the RealmObject to a ViewModel, but when the changes come back from the ViewModel, I want to compare the disconnected object to the object in the datastore to determine what was changed, so If there was a way that I could disconnect the object from Realm when I give it to the ViewModel, then I can better manage what properties have changed, using my biz logic to do what I need, then save the changes back to realm.
I understand Realm does a lot of magic and many people will not want to add a layer like this but in my app, I cant really have the UI directly updating the datastore, unless there is a event that is raised that I can subscribe too and then attach my business logic this way.
I only saw one event and it does not appear to perform this action.
Thanks for your assistance.
First, get json NUGET :
PM> Install-Package Newtonsoft.Json
And, try this "hack" :
Deserialize the modified IsManaged property does the tricks.
public d DetachObject<d>(d Model) where d : RealmObject
{
return Newtonsoft.Json.JsonConvert.DeserializeObject<d>(
Newtonsoft.Json.JsonConvert.SerializeObject(Model)
.Replace(",\"IsManaged\":true", ",\"IsManaged\":false")
);
}
.
If you facing slow-down on JsonConvert:
According to source code
, the 'IsManaged' property only has get accessor and return true when private field _realm which is available
So, we has to set the instance of field _realm to null does the tricks
public d DetachObject<d>(d Model) where d : RealmObject
{
typeof(RealmObject).GetField("_realm",
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)
.SetValue(Model, null);
return Model.IsManaged ? null : Model;
}
.
You will get empty RealmObject body after Realm are now implemented same strategy as LazyLoad
Record down live RealmObject and (deactivate) realm instance in object by Reflection. And set back recorded values to RealmObject. With handled all the ILists inside too.
public d DetachObject<d>(d Model) where d : RealmObject
{
return (d)DetachObjectInternal(Model);
}
private object DetachObjectInternal(object Model)
{
//Record down properties and fields on RealmObject
var Properties = Model.GetType().GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)
.Where(x => x.Name != "ObjectSchema" && x.Name != "Realm" && x.Name != "IsValid" && x.Name != "IsManaged" && x.Name != "IsDefault")
.Select(x =>(x.PropertyType.Name == "IList`1")? ("-" + x.Name, x.GetValue(Model)) : (x.Name, x.GetValue(Model))).ToList();
var Fields = Model.GetType().GetFields(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public)
.Select(x => (x.Name, x.GetValue(Model))).ToList();
//Unbind realm instance from object
typeof(RealmObject).GetField("_realm", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).SetValue(Model, null);
//Set back the properties and fields into RealmObject
foreach (var field in Fields)
{
Model.GetType().GetField(field.Item1, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public).SetValue(Model, field.Item2);
}
foreach (var property in Properties.OrderByDescending(x=>x.Item1[0]).ToList())
{
if (property.Item1[0] == '-')
{
int count = (int)property.Item2.GetType().GetMethod("get_Count").Invoke(property.Item2, null);
if (count > 0)
{
if (property.Item2.GetType().GenericTypeArguments[0].BaseType.Name == "RealmObject")
{
for (int i = 0; i < count; i++)
{
var seter = property.Item2.GetType().GetMethod("set_Item");
var geter = property.Item2.GetType().GetMethod("get_Item");
property.Item2.GetType().GetField("_realm", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Public).SetValue(property.Item2, null);
DetachObjectInternal(geter.Invoke(property.Item2, new object[] { i }));
}
}
}
}
else
{
Model.GetType().GetProperty(property.Item1, System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public).SetValue(Model, property.Item2);
}
}
return Model;
}
.
For List of the RealmObject , Using Select():
DBs.All<MyRealmObject>().ToList().Select(t => DBs.DetachObject(t)).ToList();
.
(Java)You dont need this if youre in java:
Maybe some day, this feature will come to .NET Realm
Realm.copyFromRealm();
#xamarin #C# #Realm #RealmObject #detach #managed #IsManaged #copyFromRealm
Until its added to Realm for Xamarin, I added a property to my Model that creates a copy of the object. This seems to work for my use. The TwoWay Binding error messages are now also not an issue. For a more complicated application, I don't want to put business or data logic in the ViewModel. This allows all the Magic of xamarin forms to work and me to implement logic when its finally time to save the changes back to realm.
[Ignored]
public Contact ToStandalone()
{
return new Contact()
{
companyName = this.companyName,
dateAdded = this.dateAdded,
fullName = this.fullName,
gender = this.gender,
website = this.website
};
}
However, If there are any relationships this method does not work for the relationships. Copying the List is not really an option either as the relationship cant exist if the object is not attached to Realm, I read this some where, can't find it to ref now. So I guess we will be waiting for the additions to the framework.
Not currently in the Xamarin interface but we could add it. The Java interface already has copyFromRealm which performs a deep copy. That also has a paired merging copyToRealmOrUpdate.
See Realm github issue for further discussion.
However, as a design issue, is this really meeting your need in an optimal way?
I have used converters in WPF apps to insert logic into the binding - these are available in Xamarin Forms.
Another way in Xamarin forms is to use Behaviours, as introduced in the blog article and covered in the API.
These approaches are more about adding logic between the UI and ViewModel, which you could consider as part of the ViewModel, but before updates are propagated to bound values.
After wasting too much time in 3rd party libraries like AutoMapper, I've created my own extension function which works pretty well. This function simply uses Reflection with the recession. (Currently, Only for List type. You can extend the functionality for Dictionary and other types of the collection very easily or you can completely modify the functionality based on your own requirements.).
I didn't do too much time and complexity analysis. I've tested only for my test case which contains many nested RealmObject, built from a 3500+ line of JSON object, took only 15 milliseconds to clone the object.
Here the complete extension available via Github Gist. If you want to extend the functionality of this extension please update this Github Gist, So, other developers can take advantage of it.
Here the complete extension -
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Realms;
namespace ProjectName.Core.Extensions
{
public static class RealmExtension
{
public static T Clone<T>(this T source) where T: new()
{
//If source is null return null
if (source == null)
return default(T);
var target = new T();
var targetType = typeof(T);
//List of skip namespaces
var skipNamespaces = new List<string>
{
typeof(Realm).Namespace
};
//Get the Namespace name of Generic Collection
var collectionNamespace = typeof(List<string>).Namespace;
//flags to get properties
var flags = BindingFlags.IgnoreCase | BindingFlags.DeclaredOnly | BindingFlags.Public | BindingFlags.Instance;
//Get target properties list which follows the flags
var targetProperties = targetType.GetProperties(flags);
//if traget properties is null then return default target
if (targetProperties == null)
return target;
//enumerate properties
foreach (var property in targetProperties)
{
//skip property if it's belongs to namespace available in skipNamespaces list
if (skipNamespaces.Contains(property.DeclaringType.Namespace))
continue;
//Get property information and check if we can write value in it
var propertyInfo = targetType.GetProperty(property.Name, flags);
if (propertyInfo == null || !property.CanWrite)
continue;
//Get value from the source
var sourceValue = property.GetValue(source);
//If property derived from the RealmObject then Clone that too
if (property.PropertyType.IsSubclassOf(typeof(RealmObject)) && (sourceValue is RealmObject))
{
var propertyType = property.PropertyType;
var convertedSourceValue = Convert.ChangeType(sourceValue, propertyType);
sourceValue = typeof(RealmExtension).GetMethod("Clone", BindingFlags.Static | BindingFlags.Public)
.MakeGenericMethod(propertyType).Invoke(convertedSourceValue, new[] { convertedSourceValue });
}
//Check if property belongs to the collection namespace and original value is not null
if (property.PropertyType.Namespace == collectionNamespace && sourceValue != null)
{
//get the type of the property (currently only supported List)
var listType = property.PropertyType;
//Create new instance of listType
var newList = (IList)Activator.CreateInstance(listType);
//Convert source value into the list type
var convertedSourceValue = Convert.ChangeType(sourceValue, listType) as IEnumerable;
//Enumerate source list and recursively call Clone method on each object
foreach (var item in convertedSourceValue)
{
var value = typeof(RealmExtension).GetMethod("Clone", BindingFlags.Static | BindingFlags.Public)
.MakeGenericMethod(item.GetType()).Invoke(item, new[] { item });
newList.Add(value);
}
//update source value
sourceValue = newList;
}
//set updated original value into the target
propertyInfo.SetValue(target, sourceValue);
}
return target;
}
}
}

Eager loading with condition

I have question on eager loading in entity framework.
I have two tables ScrappyTemplate and ScarppyTemplateFields, the relationship between the tables is one to many. Note that both the tables have IsActive flag
I want to fetch the data from ScrappyTemplate and ScrappyTemplateFields where IsActive==True, im using the below code to fetch the data via eager loading
using (Entities entities = new Entities())
{
var content = entities.ScrappyTemplates.Include(entities.GetTableName<ScrappyTemplateField> (false)).Where(c => c.ContentSourceId == contentSourceId && c.IsActive == true && c.ScrappyTemplateFields.Any(d=>d.IsActive==true)).ToList<ScrappyTemplate>();
}
Im getting the resultset, which is not right!!, i want to get the result set of Child table i.e ScrappyTemplateFields where IsActive=true, but it is returning all rows ir-respective IsActive flag.
Please any one help me, how to place a condition in Child table.
Thanks in advance
.Include() does not allow filtering on the related entities. Try this:
using (Entities entities = new Entities())
{
var query = from template in entities.ScrappyTemplates
where template.ContentSourceId = contentSourceId && template.IsActive = true && template.ScrappyTemplateFields.Any(d=>d.IsActive==true)
select new {
Template = template,
TemplateFields = template.ScrappyTemplateFields.Where(d=>d.IsActive==true)
};
var content = query.ToList().Select(t=>t.Template);
}

How to Cache Multiple Versions of a Page using Razor Dynamic Query & WebCache

I decided to implement caching to improve the performance of the product pages.
Each page contains a large amount of the product's images.
I created the following code in a Razor view.
#{
var productID = UrlData[0].AsInt();
var cacheItemKey = "products";
var cacheHit = true;
var data = WebCache.Get(cacheItemKey);
var db = Database.Open("adldb");
if (data == null) {
cacheHit = false;
}
if (cacheHit == false) {
data = db.Query("SELECT * FROM Products WHERE ProductID = #0", productID).ToList();
WebCache.Set(cacheItemKey, data, 1, false);
}
}
I'm using the data with the following code:
#foreach (dynamic p in data)
{
<a href="~/Products/View/#p.ProductID"
<img src="~/Thumbnail/#p.ProductID"></a>
}
The caching code works well, but when passing the new query string parameter (changing the version of the page) the result in browser is the same for the declared cashing time.
How to make caching every version of the page?
Thanks
Oleg
A very simple approach might be to convert your key (productID) to a string and append it to the name of your cacheItemKey.
So you might consider changing the line:
var cacheItemKey = "products";
to read:
var cacheItemKey = "products" + productID.ToString();
This should produce the behavior you are looking for -- basically mimicking a VaryByParam setup.
ps. Please keep in mind I have not added any sort of defensive code, which you should do.
Hope that helps.

What's problem on linq databinding

<dx:ASPxGridView ID="ASPxGridView1" runat="server" AutoGenerateColumns="False"
KeyFieldName="CategoryID">
<SettingsEditing Mode="Inline" />
<Columns>
<dx:GridViewCommandColumn VisibleIndex="0">
<EditButton Visible="True"></EditButton>
<NewButton Visible="True"></NewButton>
<DeleteButton Visible="True"></DeleteButton>
</dx:GridViewCommandColumn>
<dx:GridViewDataTextColumn Caption="CategoryID" FieldName="CategoryID"
VisibleIndex="1">
</dx:GridViewDataTextColumn>
<dx:GridViewDataTextColumn Caption="CategoryName" FieldName="CategoryName"
VisibleIndex="2">
</dx:GridViewDataTextColumn>
<dx:GridViewDataTextColumn Caption="Description" FieldName="Description"
VisibleIndex="3">
</dx:GridViewDataTextColumn>
</Columns>
</dx:ASPxGridView>
C# syntax:
NorthwindDataContext db = new NorthwindDataContext();
var lresult = (db.Categories
.Select(p => new { p.CategoryID, p.CategoryName, p.Description}));
ASPxGridView1.DataSource = lresult;
ASPxGridView1.DataBind();
If you run the code, you get a gridview which is filled by NorthWind Categories table. If you click on command button of grid whose are on left side, you get insert/update field, but you have not access to give input. They are gone to read only mode.
If I replace the above C# syntax with below
NorthwindDataContext db = new NorthwindDataContext();
var lresult = (db.Categories);
ASPxGridView1.DataSource = lresult;
ASPxGridView1.DataBind();
then it works fine. Now you can work with command button with out facing any problem.
I want to know what the problem is, why the first syntax does not work. Maybe you say
Anonymous types are class types that consist of one or more public read-only properties. But when you need to join more than one table and need to select several fields not all than what you do. Hope you not say linq is fail to do that or Don't think it is possible. Hope there must be any technique or else something to bind control with Anonymous type. Plz show some syntax .
The problem is that the result set is collection of Anonymous type as you supposed and the grid doesn't know how to treat it. What you have to do is to use RowInserting and RowUpdating events of the grid.
Here is an example of how I use DevExpress grid with NHibernate:
protected void gridAgentGroups_RowInserting(object sender, DevExpress.Web.Data.ASPxDataInsertingEventArgs e)
{
ASPxGridView currentGrid = sender as ASPxGridView;
var currentAgentGroup = new AgentGroup();
if (e.NewValues.Contains("Name"))
{
var newValue = (string)e.NewValues["Name"];
currentAgentGroup.Name = newValue;
}
if (e.NewValues.Contains("PhysicalAddress"))
{
var newValue = (string)e.NewValues["PhysicalAddress"];
currentAgentGroup.PhysicalAddress = newValue;
}
AgentGroupsDataAccess.SaveAgentGroup(currentAgentGroup);
e.Cancel = true;
currentGrid.CancelEdit();
currentGrid.DataBind();
}
protected void gridAgentGroups_RowUpdating(object sender, DevExpress.Web.Data.ASPxDataUpdatingEventArgs e)
{
ASPxGridView currentGrid = sender as ASPxGridView;
int currentAgentGroupId = (int)((AgentGroup)currentGrid.GetRow(currentGrid.EditingRowVisibleIndex)).Id;
var currentAgentGroup = AgentGroups.Where(ag => ag.Id == currentAgentGroupId).FirstOrDefault();
if (e.NewValues.Contains("Name"))
{
var newValue = (string)e.NewValues["Name"];
currentAgentGroup.Name = newValue;
}
if (e.NewValues.Contains("PhysicalAddress"))
{
var newValue = (string)e.NewValues["PhysicalAddress"];
currentAgentGroup.PhysicalAddress = newValue;
}
AgentGroupsDataAccess.SaveAgentGroup(currentAgentGroup);
e.Cancel = true;
currentGrid.CancelEdit();
currentGrid.DataBind();
}
I hope this will help.
Just a wild guess - you're binding your data to the grid using field names - yet, your anonymous type doesn't really have any field names.
Does it make any difference if you try this code:
NorthwindDataContext db = new NorthwindDataContext();
var lresult = (db.Categories
.Select(p => new { CategoryID = p.CategoryID,
CategoryName = p.CategoryName,
Description = p.Description}));
ASPxGridView1.DataSource = lresult;
ASPxGridView1.DataBind();
Again - I don't have the means to test this right now, it's just a gut feeling..... try it - does that help at all??
You actually can bind with anonymous type as you see the already filled rows. But: the grid itself cannot know how you build the query and what to add additionally to the visible columns (if there are valid default values).
As you use Developer Express' grid you have the option to provide your own update / edit form and handle everything needed on your own.

Resources