I'm implementing a custom data provider, I have gotten it to the point that it returns data and can be filtered, but am having some trouble getting relationships to work.
When querying the metadata the relationships look correct, and when querying a table the related property links appear, but when attempting to access a ResourceReference property I get the following exception:
Object reference not set to an instance of an object.
System.NullReferenceException
stacktrace at System.Data.Services.Providers.DataServiceProviderWrapper.GetResourceAssociationSet(ResourceSetWrapper resourceSet, ResourceType resourceType, ResourceProperty resourceProperty)
at System.Data.Services.Providers.DataServiceProviderWrapper.GetContainer(ResourceSetWrapper sourceContainer, ResourceType sourceResourceType, ResourceProperty navigationProperty)
at System.Data.Services.Providers.DataServiceProviderWrapper.GetResourceProperties(ResourceSetWrapper resourceSet, ResourceType resourceType)
at System.Data.Services.Serializers.SyndicationSerializer.WriteObjectProperties(IExpandedResult expanded, Object customObject, ResourceType resourceType, Uri absoluteUri, String relativeUri, SyndicationItem item, DictionaryContent content, EpmSourcePathSegment currentSourceRoot)
at System.Data.Services.Serializers.SyndicationSerializer.WriteEntryElement(IExpandedResult expanded, Object element, ResourceType expectedType, Uri absoluteUri, String relativeUri, SyndicationItem target)
at System.Data.Services.Serializers.SyndicationSerializer.WriteTopLevelElement(IExpandedResult expanded, Object element)
at System.Data.Services.Serializers.Serializer.WriteRequest(IEnumerator queryResults, Boolean hasMoved)
at System.Data.Services.ResponseBodyWriter.Write(Stream stream)
Here's a sample of how I create the relationships:
var sourceReference = new ResourceProperty(
relatedType.ResourceTypeName,
ResourcePropertyKind.ResourceReference,
relatedType.ResourceType);
sourceReference.CanReflectOnInstanceTypeProperty = false;
compoundType.ResourceType.AddProperty(sourceReference);
var destinationReference = new ResourceProperty(
compoundType.ResourceSetName,
ResourcePropertyKind.ResourceSetReference,
compoundType.ResourceType);
destinationReference.CanReflectOnInstanceTypeProperty = false;
source.ResourceType.AddProperty(destinationReference);
var sourceAssociation = new ResourceAssociationSet(
"source",
new ResourceAssociationSetEnd(compoundType.ResourceSet, compoundType.ResourceType, sourceReference),
new ResourceAssociationSetEnd(relatedType.ResourceSet, relatedType.ResourceType, null));
var destinationAssociation = new ResourceAssociationSet(
"destination",
new ResourceAssociationSetEnd(relatedType.ResourceSet, relatedType.ResourceType, destinationReference),
new ResourceAssociationSetEnd(compoundType.ResourceSet, compoundType.ResourceType, null));
From looking at the sample code on the OData website I thought I'd done it all correctly, and cannot determine my error. Any ideas? or tips on debugging a custom WCF Data service?
Update:
Here's what happens just before the null exception.
Have a resource set Collars with a relationship to Projects so I do this query:
blah.svc/Collars(1)/Project
My override of GetResourceAssociationSet in my IDataServiceMetadataProvider gets called with the parameters ResourceSet = Collars, ResourceType = Collar, Property = Project and I return the association set specified above.
GetResourceAssociationSet is then called again with ResourceSet = Projects, ResourceType = Collar, Property = Project and I return the same association set.
Then in System.Data.Services.Providers.GetResourceAssociationSetEnd the variables passed in are resourceSet = Projects, resourceType = Collar, resourceProperty = Project, this function returns null.
Which makes thisEnd in System.Data.Services.Providers.DataServiceProviderWrapper.GetResourceAssociationSet equal to null.
Then GetRelatedResourceAssociationSetEnd is called with the same variables and also returns null.
So it then crashes with the call
ResourceSetWrapper relatedSet = this.ValidateResourceSet(relatedEnd.ResourceSet);
because relatedEnd is null.
Well, in my debugging I noticed the last time GetResourceAssociationSet was called before the error occurred was for a case where the resourceSet and resourceType parameters had different values (in the case of a derived type).
So, I looked and found this
WCF data services (OData), query with inheritance limitation?
...and lo and behold this uninformative null reference exception (at least in my case) is caused by that same issue. I removed the offending property (and then moved it to the base resource set, even though it doesn't exist there in practice), and the issue was resolved.
Interesting side note (in case this helps the original poster): ResourceType.Properties includes properties from Base Types. Had to change some code to use PropertiesDeclaredOnThisType instead...
The solution for me was that I had made a mistake in my implementation of GetResourceType in my IDataServiceQueryProvider.
When querying a Resource Property that was a ResourceSetReference I was returning the ResourceType of the parent resource, and not the type of the related resource.
Related
My cq5 Content Structure is....
Content
---mywebsite
------base
-----us
--- en
----pageOne
----pageTwo
----pageThree
----pageFour
----cq:content
----par
----pageFourNew
"pageFourNew" has around 500 Properties.
Now I need to get all the properties of "pageFourNew" and to update their value.
For example if I have:
property=prop1
value = prop1 value
I want to do value = value+"some string value append" and save it on the repository.
I want to do this in a programmatically way.
Please share if you have any solution or idea.
You can use PropertyIterator to iterate through all the properties, setProperty() method of node api to set the new value and jcr session to persist the value to get this done. Sample code:
PropertyIterator propertyIterator = pageFourNew.getProperties();
while (propertyIterator.hasNext()) {
Property property = propertyIterator.nextProperty();
pageFourNew.setProperty(property.getName(),
property.getValue().getString() + "");
jcrSession.save();}
You can easily do this as suggested above at JCR level. But as per CQ practices and this blog
It is better practice to operate at Sling level and NOT JCR level, just to avoid overhead of managing the resources. You can use below code which works:
Resource resource = pageFourNew; // assuming you are getting sling resource properly
ModifiableValueMap valueMap = resource.adaptTo(ModifiableValueMap.class);
for(String key : valueMap.keySet()) {
String value = valueMap.get(key, String.class);
value = value + "additional texts";
valueMap.put(key, value);
}
resource.getResourceResolver().commit();
This is cleaner approach.
When using WCF Data Services Client to communicate with my OData API, I occasionally need to use the
AddRelatedObject method of System.Data.Services.Client.DataServiceContext
This generates a POST to the endpoint that looks like:
http://base_url/odata/Entity(key)/NavigationProperty
The content of the http request is the serialized NavigationProperty entity.
Since a post to this URL is unmapped in a default odata controller, I wrote a new EntitySetRoutingConvention class:
Public Class AddRelatedObjectRoutingConvention
Inherits EntitySetRoutingConvention
Public Overrides Function SelectAction(odataPath As Http.OData.Routing.ODataPath, controllerContext As Http.Controllers.HttpControllerContext, actionMap As ILookup(Of String, Http.Controllers.HttpActionDescriptor)) As String
If controllerContext.Request.Method = Net.Http.HttpMethod.Post Then
If odataPath.PathTemplate = "~/entityset/key/navigation" Then
If actionMap.Contains("AddRelation") Then
Return "AddRelation"
End If
End If
End If
Return Nothing
End Function
End Class
I added this to the default routing conventions list and passed it to the MapODATARoute routine.
In my base controller I implemented AddRelation, and it is called perfectly.
From this I am able to get the odatapath, and parse it to determine the types and keys that are needed.
The problem I am having, is that once I know that my parent entity is a Tenant with a key of 1, and that the NavigationProperty of Users in the Tenant entity is a User entity, I can not figure out how to manually call an odatamediatypeformatter to deserialize the http content into the User entity so that I can then add it to the Tenants Users collection.
I have reviewed the OData source in an effort to find out how to the Web-API pipeline calls and deserializes the entity but have been unable to find the point at which the OData library takes the http content and turns it into an entity.
If anyone has an ideas to point me in the right direction, I will continue researching and update if I figure out more.
UPDATE 06/28/2013
Thanks to RaghuRam, I've been able to get closer. The problem I'm seeing is that the request.getconfiguration().formatters, or odatamediatypeformatters.create both seem to create type specific deserializers.
When I call ReadAsAsync I receive the error:
No MediaTypeFormatter is available to read an object of type 'User' from content with media type 'application/atom+xml'.
As the post shows above, the context of the controller is a Tenant, so I believe the formatters are typed to a Tenant and so can't deserialize the User.
I tried manually creating a formatter, _childDefinition is the EDM Type Reference from the navigation property of the odatapath. In this case a User.
Dim dser As New Formatter.Deserialization.ODataEntityDeserializer(_childDefinition, New Formatter.Deserialization.DefaultODataDeserializerProvider)
Dim ser As New Formatter.Serialization.ODataEntityTypeSerializer(_childDefinition, New Formatter.Serialization.DefaultODataSerializerProvider)
Dim dprovider As New Formatter.Deserialization.DefaultODataDeserializerProvider
dprovider.SetEdmTypeDeserializer(_childDefinition, dser)
Dim sprovider As New Formatter.Serialization.DefaultODataSerializerProvider
sprovider.SetEdmTypeSerializer(_childDefinition, ser)
Dim fmt As New Formatter.ODataMediaTypeFormatter(dprovider, sprovider, New List(Of Microsoft.Data.OData.ODataPayloadKind) From {Microsoft.Data.OData.ODataPayloadKind.Entry})
fmt.SupportedEncodings.Add(System.Text.Encoding.UTF8)
fmt.SupportedEncodings.Add(System.Text.Encoding.Unicode)
fmt.SupportedMediaTypes.Add(Headers.MediaTypeHeaderValue.Parse("application/atom+xml;type=entry"))
fmt.SupportedMediaTypes.Add(New Headers.MediaTypeHeaderValue("application/atom+xml"))
I have then tried:
request.content.ReadAsAsync(of User)(new List(of odatamediatypeformatter) from {fmt})
request.content.ReadAsAsync(of User)(request.getConfiguration().formatters)
request.content.ReadAsAsync(of User)(odatamediatypeformatters.create)
All giving the same error, it seems like I'm missing something obvious.
Thanks!
Steve
Just add a parameter of type User to your AddRelation action and you should be good. Web API would automatically invoke the OData formatter to read the User from the request body and bind it to your action parameter.
You can use this helper method to read OData request body,
private static Task<T> ReadODataContentAsAsync<T>(HttpRequestMessage request)
{
var formatters =
ODataMediaTypeFormatters.Create()
.Select(formatter => formatter.GetPerRequestFormatterInstance(typeof(T), request, request.Content.Headers.ContentType));
return request.Content.ReadAsAsync<T>(formatters);
}
like this,
Customer addedCustomer = ReadODataContentAsAsync<Customer>(Request).Result;
I am trying to create a WCF service without knowing its type/interface at runtime. To do this, I use ChannelFactory. ChannelFactory is a generic class so I need to use Type.MakeGenericType. The type I pass to MakeGenericType is from a list of interfaces I previously gathered with reflection by searching some assemblies.
Ultimately, I call MethodInfo.Invoke to create the object. The object is created just fine, but I cannot cast it to the proper interface. Upon casting, I receive the following error:
"Unable to cast transparent proxy to type 'Tssc.Services.MyType.IMyType'"
After some experimenting, I have found that the interface/type passed to MakeGenericType seems to be the problem. If I substitute the interface in my list with the actual interface, then everything works fine. I have combed through the two objects and cannot see a difference. When I modify the code to produce both types, comparing them with Equals returns false. It is unclear to me whether Equals is just checking that they are referring to the same object (not) or thety are checking all properties, etc.
Could this have something to do with how I gathered my interfaces (Reflection, saving in a list...)? A comparison of the objects seems to indicate they are equivalent. I printed all properties for both objects and they are the same. Do I need to dig deeper? If so, into where?
// createService() method
//*** tried both of these interfaces, only 2nd works - but they seem to be identical
//Type t = interfaces[i]; // get type from list created above - doesn't work
Type t = typeof(Tssc.Services.MyType.IMyType); // actual type - works OK
// create ChannelFactory type with my type parameter (t)
Type factoryType = typeof(ChannelFactory<>);
factoryType = factoryType.MakeGenericType(new Type[] { t });
// create ChannelFactory<> object with two-param ctor
BasicHttpBinding binding = new BasicHttpBinding();
string address = "blah blah blah";
var factory = Activator.CreateInstance(factoryType, new object[] { binding, address });
// get overload of ChannelFactory<>.CreateChannel with no parameters
MethodInfo method = factoryType.GetMethod("CreateChannel", new Type[] { });
return method.Invoke(factory, null);
//--------------- code that calls code above and uses its return
object ob = createService();
//*** this cast fails
Tssc.Services.MyType.IMyType service = (Tssc.Services.MyType.IMyType)ob;
Ok, I understand whats happening here - the problem is relating to loading the same assembly being effectively loaded twice - once via a reference, and once via the assembly load command. What you need to do is change the place where you load your assembly, and check to see if it already exists in the current AppDomain, like this maybe:
Assembly assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.GetName().Name.Equals("ClassLibrary1Name"));
if (assembly == null)
{
assembly = System.Reflection.Assembly.LoadFile("path to your assembly");
}
//do your work here
This way if the assembly is already loaded into memory, it'll use that one.
Ektron 8.0.1 SP1
I am using SmartForms and Content Types to read (and hopefully write) data. I can read data but now I am attempting to write a new record similar to the following.
ContentTypeManager<member> contentTypeManager = new ContentTypeManager<member>();
ContentType<member> newmem = new ContentType<member>();
newmem.SmartForm.details.field1 = "Chuck"; // This line throws 'Object reference not set to an instance of an object.' error
newmem.SmartForm.details.field2 = "Norris";
contentTypeManager.Update(newmem);
I get the error "Object reference not set to an instance of an object." for that first assignment line. What am I missing?
I am having trouble finding good documentation on ContentTypes for 8.0.1 now that the Ektron website has been redesigned.
Thx.
Thanks for clarifying, to ADD content to a folder that has a smartform assigned to it, the basic code block should get you started: (Note: the Html attribute of the content is simply the xml matched to the schema you created)
Ektron.Cms.Framework.Content.ContentManager cmanager = new Cms.Framework.Content.ContentManager();
Ektron.Cms.ContentData cdata = new ContentData();
cdata.FolderId = 0;
cdata.XmlConfiguration.Id = 0; //SMARTFORM ID HERE
cdata.Html = "<root><field1>field1 value</field1><field2>field2 value</field2></root>";
cmanager.Add(cdata);
You could update ContentTypes.cs to include an Add method. Just copy the Update method and change contentManager.Update to contentManager.Add.
public void Add(ContentType<T> contentType)
{
Initialize();
contentType.Content.Html = Ektron.Cms.EkXml.Serialize(typeof(T), contentType.SmartForm);
contentManager.Add(contentType.Content);
}
Unfortunately, contentManager.Add returns void. Ideally it should return the new content ID.
In the dbml designer I've set Update Check to Never on all properties. But i still get an exception when doing Attach: "An attempt has been made to Attach or Add an entity that is not new, perhaps having been loaded from another DataContext. This is not supported." This approach seems to have worked for others on here, but there must be something I've missed.
using(TheDataContext dc = new TheDataContext())
{
test = dc.Members.FirstOrDefault(m => m.fltId == 1);
}
test.Name = "test2";
using(TheDataContext dc = new TheDataContext())
{
dc.Members.Attach(test, true);
dc.SubmitChanges();
}
The error message says exactly what is going wrong: You are trying to attach an object that has been loaded from another DataContext, in your case from another instance of the DataContext. Dont dispose your DataContext (at the end of the using statement it gets disposed) before you change values and submit the changes. This should work (all in one using statement). I just saw you want to attach the object again to the members collection, but it is already in there. No need to do that, this should work just as well:
using(TheDataContext dc = new TheDataContext())
{
var test = dc.Members.FirstOrDefault(m => m.fltId == 1);
test.Name = "test2";
dc.SubmitChanges();
}
Just change the value and submit the changes.
Latest Update:
(Removed all previous 3 updates)
My previous solution (removed it again from this post), found here is dangerous. I just read this on a MSDN article:
"Only call the Attach methods on new
or deserialized entities. The only way
for an entity to be detached from its
original data context is for it to be
serialized. If you try to attach an
undetached entity to a new data
context, and that entity still has
deferred loaders from its previous
data context, LINQ to SQL will thrown
an exception. An entity with deferred
loaders from two different data
contexts could cause unwanted results
when you perform insert, update, and
delete operations on that entity. For
more information about deferred
loaders, see Deferred versus Immediate
Loading (LINQ to SQL)."
Use this instead:
// Get the object the first time by some id
using(TheDataContext dc = new TheDataContext())
{
test = dc.Members.FirstOrDefault(m => m.fltId == 1);
}
// Somewhere else in the program
test.Name = "test2";
// Again somewhere else
using(TheDataContext dc = new TheDataContext())
{
// Get the db row with the id of the 'test' object
Member modifiedMember = new Member()
{
Id = test.Id,
Name = test.Name,
Field2 = test.Field2,
Field3 = test.Field3,
Field4 = test.Field4
};
dc.Members.Attach(modifiedMember, true);
dc.SubmitChanges();
}
After having copied the object, all references are detached, and all event handlers (deferred loading from db) are not connected to the new object. Just the value fields are copied to the new object, that can now be savely attached to the members table. Additionally you do not have to query the db for a second time with this solution.
It is possible to attach entities from another datacontext.
The only thing that needs to be added to code in the first post is this:
dc.DeferredLoadingEnabled = false
But this is a drawback since deferred loading is very useful. I read somewhere on this page that another solution would be to set the Update Check on all properties to Never. This text says the same: http://complexitykills.blogspot.com/2008/03/disconnected-linq-to-sql-tips-part-1.html
But I can't get it to work even after setting the Update Check to Never.
This is a function in my Repository class which I use to update entities
protected void Attach(TEntity entity)
{
try
{
_dataContext.GetTable<TEntity>().Attach(entity);
_dataContext.Refresh(RefreshMode.KeepCurrentValues, entity);
}
catch (DuplicateKeyException ex) //Data context knows about this entity so just update values
{
_dataContext.Refresh(RefreshMode.KeepCurrentValues, entity);
}
}
Where TEntity is your DB Class and depending on you setup you might just want to do
_dataContext.Attach(entity);