Data via Entity Framework

Creating a Data Provider That Reads Data via Entity Framework

(You can also check the sample project that demonstrates how a read-only data provider exposes two tables from a  database using Entity Framework.)

Let’s create a data provider that exposes data to C1 CMS from tables in a database (e.g. "Northwind") on Microsoft SQL Server via Entity Framework.

First of all, create a new project, add required assembly references:

  1. In Visual Studio, create a new Class Library project.
  2. Add a reference to Composite.dll and Microsoft.Practices.EnterpriseLibrary.Common.dll from your website, also System.Configuration.dll.
  3. Set the "Copy Local" property for these references to "False".

Now add an Entity Data Model to the project and select the tables you want to expose data from:

  1. Add an Entity Data Model to the project (Project | Add New Item | ADO.NET Entity Data Model) and follow the steps in the wizards.
  2. Choose Model Contents: Generate from database.
  3. Choose Your Data Connection: Specify the database server and the database (e.g. Northwind).
    • If necessary, create a new connection.
    • Also, make sure, you have selected the option "Save entity connection settings in App.config as"
  4. Chose Your Database Objects: Select the needed tables (for example, Suppliers and Products).

IData Interfaces

Next, for each table added to the project (Step 4 above):

  1. Add a type interface inherited from the IData interface (e.g. ISupplier and IProduct)
  2. Add needed properties to represent actual columns in the database. (Properties that you would like to expose in C1 CMS could be a subset of the actual columns in the database, but the key column should be one of them.)
  3. Add the required attributes to the type interface and to its properties (see below).
  4. Add a class that implements IDataId to hold the value of the key column in the table (e.g. DataId). The class can be shared between tables if the key column is the same type in all tables (see the sample code).
  5. Add a base class (e.g. SupplierBase and ProductBase) inheriting from EntityObject and implementing the type interface from Step 1 (e.g. ISupplier or IProduct) that only handles the DataSourceId property.

These are the attributes you should add to the type interfaces and their properties (Step 3 above):

Type attributes:

  • KeyPropertyName
  • ImmutableTypeId
  • DataScope
  • DataAncestorProvider

Property attributes:

  • StoreFieldType
  • ImmutableFieldId

Note: To be able to use exposed data in the CMS Console (e.g. in Visual Functions) as well as in the code (e.g. via DataConnection), consider also using the attributes RelevantToUserType set to UserType.Developer as well as Title and LabelPropertyName set to user-friendly names.

(For more information on creating type interfaces in C1 CMS, please refer to Data Types Using C#.)

As to the base class for handling DataSourceId (Step 5 above), it is convenient to add such a class that implements the table’s IData interface. When or if the Entities classes are regenerated, it will be easy to add this functionality.

Note: This class should only implement the DataSourceId property using the provider (see further below) to get the DataContext instance (see the sample code).

Entities Classes

When tables has been added to Entity Data Model file (.edmx), Visual Studio generates Entities classes (.designer.cs), one class for each added table.

So now do the following:

  1. Close the visual part of the LINQ to SQL file (.edmx).
  2. Edit the Entities classes (.designer.cs).
  3. Make each class generated for every table to implement the corresponding IData-based interface and inherit from the corresponding base class. For example:
public partial class Supplier : SupplierBase, ISupplier

Important: Please note that when you open the visual part of the Entity Data Model file (.edmx), Visual Studio may regenerate EntitiesClasses, so all your changes here are lost.

If you do open the visual part, make sure that these classes should implement the proper interfaces and inherit from the proper classes making changes as described above.

Data Provider

Next, create the DataProvider class:

  1. Create a custom data provider class (e.g. NorthwindDataProvider) and have it implement the IDataProvider interface.
  2. For each call to the data provider, create a new instance of the Entities classes, generated when you have added an Entity Data Model (e.g. NorthwindEntities).
  3. Implement the GetData<>() and GetData<>(IDataId) methods and use the Entities classes instance for that. This instance is also accessed from the base classes (see above: "IDate Interfaces", Step 5).

The provider should cast the Table<> on the EntitiesClasses to IQueryable<IData> and return (see the sample code).

Important: Create the Entities class for each call to the data provider. This would make simultaneous calls into the provider thread-safe. (Entity Framework contexts are not thread-safe).

This requires some mechanism for calling Dispose() on the Entities class. You can implement it as follows:

private NorthwindEntities GetEntityContext()
{
	NorthwindEntities context = new NorthwindEntities();

	ThreadDataManager.GetCurrentNotNull().OnDispose += () =>
	{
		context.Dispose();
	};

	return context;
}

When implementing GetData(), you can access entities like this:

IQueryable<IProduct> products = GetEntityContext().Products;

Registering the Custom Data Provider in C1 CMS

Once you have created your custom data provider:

  1. Compile the project and copy the project’s DLL to /Bin folder of your website.
  2. Copy the connection string from the generated App.config in your project to Web.config of your website. For example:
    <configuration>
    	<connectionStrings>
    		<add name="NorthwindEntities"
    		connectionString="metadata=res://*/Northwind.csdl|res://*/Northwind.ssdl|res://*/Northwind.msl;provider=System.Data.SqlClient;providerconnection string=&quot;Data Source=.\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=True;MultipleActiveResultSets=True&quot;"
    		providerName="System.Data.EntityClient" />
    	</connectionStrings>
    </configuration>
  3. Edit the /App_Data/Composite/Composite.config file and locate the element: Composite.Data.Plugins.DataProviderConfiguration/DataProviderPlugins
  4. Add the configuration element below (just before its end tag </DataProviderPlugins>) specifying its type and name. For example:
    <add type="C1NorthwindIntegration.NorthwindDataProvider, C1NorthwindIntegration" name="NorthwindDataProvider" />
  5. Log in to the CMS Administrative Console and then execute Tools | Restart Server.

(You can use the exposed data types in console applications via XML tree definition files. If so, create a tree definition file and copy it to /App_Data/Composite/TreeDefinitions. Please see the sample code for an example.)