Integration with external data

Topics: General, Troubleshooting
Jan 18, 2011 at 7:58 AM

When attempting to follow the guides on the main site for integrating external data with C1 it seems that the guide is outdated. Whenever I attempt to create the datatype, C1 never shows a list of data providers available. I've created a data provider for my (single) table and loaded it into C1, like in the Northwind sample.

What do I do now?

Coordinator
Jan 18, 2011 at 11:37 AM

Where are you expecting something to show up? Do you have any messages in the server log that may relate to this issue?

Jan 18, 2011 at 11:57 AM
Edited Jan 20, 2011 at 8:04 AM

I do have an error in the error log:

System.Reflection.TargetException: Non-static method requires a target.
   at System.Reflection.RuntimeMethodInfo.CheckConsistency(Object target)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
   at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, BindingFlags invokeAttr, Binder binder, Object[] index, CultureInfo culture)
   at System.Reflection.RuntimePropertyInfo.GetValue(Object obj, Object[] index)
   at Composite.C1Console.Trees.DataElementsTreeNode.OnGetElements(EntityToken parentEntityToken, TreeNodeDynamicContext dynamicContext)
   at Composite.C1Console.Trees.TreeNode.<GetElements>d__4.MoveNext()
   at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   at Composite.Core.Linq.IEnumerableExtensions.Evaluate[T](IEnumerable`1 enumerable)
   at Composite.C1Console.Trees.TreeElementAttachingProvider.GetChildren(EntityToken parentEntityToken, Dictionary`2 piggybag)

Thrown by TreeFacade (type: Error)

I'm guessing it's an issue with my tree file, but I took the northwind one and (attempted) to adjust it to my own project.

<?xml version="1.0" encoding="utf-8"?>
<!--
  Put this file here: App_Data\Composite\TreeDefinitions\
-->
<ElementStructure xmlns="http://www.composite.net/ns/management/trees/treemarkup/1.0" xmlns:f="http://www.composite.net/ns/function/1.0">
  <ElementStructure.AutoAttachments>
    <NamedParent Name="Data" Position="Top" />
  </ElementStructure.AutoAttachments>
  <ElementRoot>
    <Children>
      <Element Label="Scanconsult" Id="ScanconsultRoot" >
        <Children>
          <DataElements Type="Scanconsult.INewsEntry, CompositeC1IntegrationNewsTest"
                            Label="${C1:Data:Scanconsult.INewsEntry:Title}">
          </DataElements >
        </Children>
      </Element>
    </Children>
  </ElementRoot>
</ElementStructure>
I was hoping to see my data type in the data view, but so far I only see the namespace there.
Jan 20, 2011 at 7:58 AM

The method 'Composite.C1Console.Trees.DataElementsTreeNode.OnGetElements' has three calls to 'RuntimePropertyInfo.GetValue' and all of them requires that the property is non-static. And therefor it needs an object to call the property on. In all three cases this object should be an IData instance and I'll guess that it should be a Scanconsult.INewsEntry instance reading your tree xml.

So my question is, did you do your own IDataProvider implementation? And are you sure that both the GetData implementations returns non-null and a queryable with non-nulls? If you want, would you share your implementation so I can see if there is anything that would provoke this exception.

Also. I have just added some more checks in the code and if you want you can get it here: http://compositec1.codeplex.com/SourceControl/changeset/changes/5110. Though you have to compile it by your self.

Jan 20, 2011 at 8:12 AM
Edited Jan 20, 2011 at 12:18 PM

I did write my own DataProvider, ScanconsultDataProvider, based on the Northwind sample, which implements both GetData methods.

I've uploaded my code here: http://bit.ly/dQhueJ

Jan 20, 2011 at 10:51 AM

In your file: Scanconsult.designer.cs the class NewsEntry should inherit INewsEntry and NewsEntryBase. And every time you open Scanconsult.dbml in design mode, Scanconsult.designer.cs is regenerated and you have to make the changes again.

Try to make NewsEntry inherit INewsEntry and NewsEntryBase and see if it fixes your problem :)

More information here: http://docs.composite.net/C1/Data/CustomDataProviders/NorthwindDatabase.aspx

Jan 20, 2011 at 12:25 PM

This did not change my situation. I've made sure both are inherited by adding a new code file containing the interface and baseclass, like this:

    public partial class NewsEntry : NewsEntryBase, INewsEntry
    {
    }

That way it won't be gone whenever I open the visual editor. But I still don't see my data type in C1. Nor do I see the data provider when I attempt to add a new data type (like the guide says here http://docs.composite.net/C1/Data/Integrating-with-C1.aspx/An-example-MOSS-2007 )

This is what it looks like, after I've loaded the treedefinition and my compiled code: http://bit.ly/eIO4eI


Jan 20, 2011 at 12:43 PM

Could you zip your DB and mail it to me? I need to run this through the debugger to locate the problem.

Jan 25, 2011 at 10:00 AM

Thanks for the help, Martin.

I ended up re-doing my data context to ensure all data types were the same, even though the casts were valid in SQL server.

 

Jan 26, 2011 at 1:16 PM

As an extension to the old problem, I'm now running into a new issue where whenever I click my "Add Entry" button for my data type, it will create an entry but it will do so using a Guid as Id instead of the specified Int.

INewsEntry.xml (placed in ~/App_Data/Composite/DynamicTypeForms/<namespace>):

<?xml version="1.0" encoding="utf-8" ?>
<cms:formdefinition xmlns:cms="http://www.composite.net/ns/management/bindingforms/1.0"
                    xmlns="http://www.composite.net/ns/management/bindingforms/std.ui.controls.lib/1.0"
                    xmlns:f="http://www.composite.net/ns/management/bindingforms/std.function.lib/1.0">
  <cms:bindings>
    <cms:binding name="Id" type="System.Int32" optional="false" />
    <cms:binding name="Title" type="System.String" optional="false" />
    <cms:binding name="LongText" type="System.String" optional="false" />
  </cms:bindings>
  <cms:layout>
    <cms:layout.label>
      <cms:read source="Title"/>
    </cms:layout.label>
    <FieldGroup>
      <TextBox Label="News Id" Type="ReadOnly">
        <TextBox.Text>
          <cms:read source="Id" />
        </TextBox.Text>
      </TextBox>
      <TextBox Label="Title">
        <TextBox.Text>
          <cms:bind source="Title"/>
        </TextBox.Text>
      </TextBox>
      <TextBox Label="LongText">
        <TextBox.Text>
          <cms:bind source="LongText"/>
        </TextBox.Text>
      </TextBox>
    </FieldGroup>
  </cms:layout>
</cms:formdefinition>

And my TreeDefinition:

<?xml version="1.0" encoding="utf-8"?>
<ElementStructure xmlns="http://www.composite.net/ns/management/trees/treemarkup/1.0" xmlns:f="http://www.composite.net/ns/function/1.0">

  <ElementStructure.AutoAttachments>
    <NamedParent Name="Data" Position="Top" />
  </ElementStructure.AutoAttachments>

  <ElementRoot>
    <Children>
      <Element Id="ScanConsultNewsEntries" ToolTip="News Entries" Label="NewsEntries">
        <Actions>
          <AddDataAction Label="Add Entry" Type="Scanconsult.INewsEntry" />
        </Actions>
        <Children>
          <DataElements Type="Scanconsult.INewsEntry">
            <Actions>
              <EditDataAction Label="Edit" />
              <DeleteDataAction Label="Delete" />
            </Actions>
          </DataElements>
        </Children>
      </Element>
    </Children>
  </ElementRoot>
</ElementStructure>
My interface, INewsEntry is defined as
using Composite.Data;
using Composite.Data.Hierarchy;
using Composite.Data.Hierarchy.DataAncestorProviders;
using Composite.Data.ProcessControlled;
using Composite.Data.ProcessControlled.ProcessControllers.GenericPublishProcessController;

namespace Scanconsult
{
    [KeyPropertyName("Id")]
    [ImmutableTypeId("{863898DF-7A14-4BFA-A1A7-1680B0F967BC}")]
    [DataScope(DataScopeIdentifier.PublicName)]
    //[DataScope(DataScopeIdentifier.AdministratedName)] //enable for publishable content
    //[PublishProcessControllerType(typeof(GenericPublishProcessController))] //enable for publishable content
    [DataAncestorProvider(typeof(NoAncestorDataAncestorProvider))]
    [Title("News Entry")]
    [AutoUpdateble]
    //interface INewsEntry : IPublishControlled //use for publishable content
    public interface INewsEntry : IData
    {
        [StoreFieldType(PhysicalStoreFieldType.Integer)]
        [ImmutableFieldId("{915246D9-079E-4309-8515-0B0F9814B5BE}")]
        int Id { get; set; }

        [StoreFieldType(PhysicalStoreFieldType.String, 255)]
        [ImmutableFieldId("{4C49C79A-13A8-4FB0-AF15-A818B9F00683}")]
        string Title { get; set; }

        [StoreFieldType(PhysicalStoreFieldType.String, 1000)]
        [ImmutableFieldId("{AD6D60FC-9604-4A88-B009-8A2915728D75}")]
        string LongText { get; set; }
    }

    public class NewsEntryBase
    {
        private DataSourceId _dataSourceId = null;


        public DataSourceId DataSourceId
        {
            get
            {
                if (_dataSourceId == null)
                {
                    DataId intDataId = new DataId { Id = ((INewsEntry)this).Id };

                    _dataSourceId = ScanconsultDataProvider.DataProviderContextHolder.CreateDataSourceId(intDataId, typeof(INewsEntry));
                }

                return _dataSourceId;
            }
        }
    }
}

The specific errror/exception I get is:
System.ArgumentException: Object of type 'System.Guid' cannot be converted to type 'System.Int32'.
  at System.RuntimeType.TryChangeType(Object value, Binder binder, CultureInfo culture, Boolean needsSpecialCast)
  at System.RuntimeType.CheckValue(Object value, Binder binder, CultureInfo culture, BindingFlags invokeAttr)
  at System.Reflection.MethodBase.CheckArguments(Object[] parameters, Binder binder, BindingFlags invokeAttr, CultureInfo culture, Signature sig)
  at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)
  at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
  at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
  at Composite.Data.GeneratedTypes.GeneratedTypesHelper.SetNewIdFieldValue(IData data) in C:\Users\kim\Desktop\Composite C1\Development\Development (dev 1) - build 5194\Composite\Data\GeneratedTypes\GeneratedTypesHelper.cs:line 661
  at Composite.C1Console.Trees.Workflows.GenericAddDataWorkflow.initializeCodeActivity_BuildNewData_ExecuteCode(Object sender, EventArgs e) in C:\Users\kim\Desktop\Composite C1\Development\Development (dev 1) - build 5194\Composite.Workflows\C1Console\Trees\Workflows\GenericAddDataWorkflow.cs:line 179
  at System.Workflow.ComponentModel.Activity.RaiseEvent(DependencyProperty dependencyEvent, Object sender, EventArgs e)
  at System.Workflow.Activities.CodeActivity.Execute(ActivityExecutionContext executionContext)
  at System.Workflow.ComponentModel.ActivityExecutor`1.Execute(T activity, ActivityExecutionContext executionContext)
  at System.Workflow.ComponentModel.ActivityExecutor`1.Execute(Activity activity, ActivityExecutionContext executionContext)
  at System.Workflow.ComponentModel.ActivityExecutorOperation.Run(IWorkflowCoreRuntime workflowCoreRuntime)
  at System.Workflow.Runtime.Scheduler.Run()

 

How or where do I tell C1 that my Id is using Int32 and not Guid?

Jan 26, 2011 at 5:02 PM

It seems to me that there are some weird things going on with the code in C1 on this matter. What i particular don't understand is why the IData interface doesn't have a Guid Id { get; set } property directly.

If we look at the method in question here, omposite.Data.GeneratedTypes.GeneratedTypesHelper.SetNewIdFieldValue the source for it is as follows

public static void SetNewIdFieldValue(IData data)
{
   if (data == null) throw new ArgumentNullException("data");

   PropertyInfo propertyInfo = data.GetType().GetProperty(IdFieldName);
   if (propertyInfo == null) throw new ArgumentException(string.Format("The type '{0}' does not have a property named '{1}'", data.GetType(), IdFieldName));

   MethodInfo methodInfo = propertyInfo.GetSetMethod();
   if (methodInfo == null) throw new ArgumentException(string.Format("The type '{0}' does not have a setter property named '{1}'", data.GetType(), IdFieldName));

   methodInfo.Invoke(data, new object[] { Guid.NewGuid() });
}

and of course the IdFieldName is hardcoded to be "Id". So why on earth all this trouble with reflection when Id could be defined directly on the interface?

 

Anyway, to answer your question. No, you can't use Int32 as a id type, it defined in C1 core to be of type Guid.

Coordinator
Jan 26, 2011 at 5:31 PM

We will look into this - obviously SetNewIdFieldValue() is assuming that the data passing through have a primary key field of type Guid, which is not true for custom data types like this one. I'm not sure if this is a trivial fix, since the current behavior will allow "data identity" to be set at an early stage before data is actually added to the data store (here a sql server table). I am guessing that the Id integer column in your sql db is an Identity column - a feature we have refrained from relying upon in the 'generic' elements so we can be data store agnostic.

The problem here is the use of the element

 <AddDataAction Label="Add Entry" Type="Scanconsult.INewsEntry" />

which will automatically deliver the UI (input form), the data insert logic, data identity management, tree open/highlight coordination and afterwords the edit logic while the form is still open - all those actions generated from that single XML element do require some assumptions ;-)

You could bypass this by taking control of the UI/data logic - one way would be to implement a custom form work flow - see http://docs.composite.net/C1/Console/FormWorkflows.aspx for a sample. Here you basically own the process, so there is a lot more work to be done, but then you should be able to tweak things just the way you like them. The sample include info on what markup to use in the tree definition to fire your form work flow - this action is also described here: http://docs.composite.net/C1/Console/Guide-to-Applications.aspx/How-to-Execute-Custom-Commands

... or if you own the data schema and do not have any other code depending on it you could just make your Id columns use type Guid instead of int/identity. That would probably save you a lot of coding.

My colleague @MartinJensen might have some insight on this topic (?)

 

Jan 26, 2011 at 6:21 PM

Yeah, handling a Int-id differently when using XML store or SQL database is going to be messy. All scenarios i can think of introducing either nullable ints or an attribute where you could point to a id-generator would at the end of the day have to know whether we're storing in XML or SQL database. 

Jan 27, 2011 at 12:58 PM

I will probably stick to using Guid ids for my custom schema, but I only found it natural to use the int ids because the Northwind sample uses int id, so I assumed it would be possible to extend the sample and allow adding data.

Feb 8, 2011 at 9:14 AM

I have changed SetNewIdFieldValue into the following:

public static void SetNewIdFieldValue(IData data)
{
    if (data == null) throw new ArgumentNullException("data");

    PropertyInfo propertyInfo = data.GetType().GetProperty(IdFieldName);
    if (propertyInfo == null) throw new ArgumentException(string.Format("The type '{0}' does not have a property named '{1}'", data.GetType(), IdFieldName));

    bool hasDefaultFieldValueAttribute = propertyInfo.GetCustomAttributesRecursively<DefaultFieldValueAttribute>().Any();
    bool hasNewInstanceDefaultFieldValueAtteibute = propertyInfo.GetCustomAttributesRecursively<NewInstanceDefaultFieldValueAttribute>().Any();

    if ((!hasDefaultFieldValueAttribute) && (!hasNewInstanceDefaultFieldValueAtteibute))
    {
        if (propertyInfo.PropertyType == typeof(Guid))
        {
            // Assigning a guid key a value because its not part of the genereted UI
            propertyInfo.SetValue(data, Guid.NewGuid(), null);
        }
        else
        {
            // For now, do nothing. This would fix auto increament issue for int key properties
            // throw new InvalidOperationException(string.Format("The property '{0}' on the data interface '{1}' does not a DefaultFieldValueAttribute or NewInstanceDefaultFieldValueAttribute and no default value could be created", propertyInfo.Name, data.GetType());
        }
    }
}

This fixes the current issue. And will be in the next release and next source code release.

These auto genereted forms that you see when using either Data types added through the UI or using the Add/Edit in XML trees are not showing the key property in the form UI - obvious reasons. This also means that the key property has go get a value from somewhere.

There is code handling XML vs SQL vs ... issues regarding default values on the storage level, also for keys - DefaultFieldValueAttribute. Though we might have to look close at auto increment for ints. This is easy handled in the C1 SqlDataProvider (and other LINQ2SQL like in this post). You just dont assign the int key property a value. The C1 XmlDataProvider could handle this by finding the larges used int key and then take the next int (CURRENT_MAX+1). Until this is implemented, the above code will not throw any exception. If/when this is implemented, one should use the correct DefaultFieldValueAttribute on an ínt auto increament property.

These DefaultFieldValueAttribute can not be used in the SetNewIdFieldValue method because its storage depended - SQL: do not assign any key value vs XML find next int value and assign. We also have a 'above storage level' attribute for assigning default values: NewInstanceDefaultFieldValueAttribute. But again, this can not be used in this case because we may not assign the key property in the SqlDataProvider (SQL2LINQ) if we want SQL to do the auto increament.

I have logged this issue so we wont forget to fix it :)

 

 

Jun 1, 2011 at 12:23 AM

I figured out following way to integrate external data and it works perfectly:

 

1 . Create your DAL and compile it to dll form, we used Entity Framework for that.

2. Add the compiled dll to composite > "bin" folder. Add required connection strings in web.config for your dll.

3. Create a Composite "Inline C# function" and write LINQ code to access data from your DAL. Return the function results in xml form. I used "XElement".

4. Create an "XSLT function" and add "Function call" to your "Inline C# function" to consume and display returned XML and Thats it. Now this xslt function is

available to you to be inserted anywhere inside your web pages.

 

Happy Coding!

Coordinator
Jun 1, 2011 at 8:23 AM

Sweet - thanks for sharing!

Jan 24, 2012 at 10:45 PM

Hi

I'm trying to implement a writable Entity Framework data provider over an existing database and am getting similar issues to those described above.  When I come to launch the "add" form, the following error is logged:

The type 'Composite.Data.GeneratedTypes.NBLEFC1_ICompanyEmptyClass' does not have a property named 'Id'

This is coming from SetNewIdFieldValue.

My id field is not called "Id".  Is this the problem?  Is there a way around it?

My code is essentially just a mixture of the Northwind and VCard sample projects.

Any help much appreciated.

Jan 27, 2012 at 2:52 PM

Not if you are using the build-in actions in the tree xml stuff. Like these:

<Actions>
<AddDataAction Label="Add" Type="ICompany" />
<EditDataAction Label="Edit" />
<DeleteDataAction Label="Delete" />
</Actions>

These actions assumes that there is and Guid Id property on the data classes. It is possible to make your own workflows to do your own actions.

See more here: http://docs.composite.net/Console/FormWorkflows for how to do your own workflows and http://docs.composite.net/Console/Guide-to-Applications/Tree-Schema-Definition-Reference#_WorkflowAction for how to use these in these xml trees.