Archive

Archive for the ‘Developing Using CodeFluent Entities’ Category

Table-Valued Parameters: Basics

March 24, 2015 Leave a comment

You can use table-valued parameters (TVP) to send multiple rows of data to a Transact-SQL statement or a routine, such as a stored procedure or function, without creating a temporary table or many parameters.

Great news, you can use TVP with CodeFluent Entities! Let’s see how you can use TVP to bulk load rows based on a list of id.

Create a new CFQL method:

Note the usage of “[]” after the type name. The generated method is:

public static CustomerCollection LoadByIds(System.Guid[] ids)

And the generated stored procedure:

CREATE TYPE [dbo].[cf_type_Customer_LoadByIds_0] AS TABLE (
 [Item] [uniqueidentifier] NULL)
GO
CREATE PROCEDURE [dbo].[Customer_LoadByIds]
(
 @ids [dbo].[cf_type_Customer_LoadByIds_0] READONLY
)
AS
SET NOCOUNT ON
DECLARE @_c_ids int; SELECT @_c_ids= COUNT(*) FROM @ids
SELECT DISTINCT [Customer].[Customer_Id], [Customer].[Customer_FirstName], [Customer].[Customer_LastName], [Customer].[Customer_DateOfBirth]
    FROM [Customer]
    WHERE [Customer].[Customer_Id] IN (((SELECT * FROM @ids)))

RETURN
GO

Table-Valued Parameters require SQL Server 2008. Don’t forget to change the target of the SQL Server producer to use at least this version.

Additionally set Legacy String Array Mode to false :

Happy Coding,

The R&D Team.

References: http://www.softfluent.com/documentation/Methods_WorkingWithArrays.html

Table-Valued Parameters: Basics

February 11, 2015 Leave a comment

First, what is Table-Valued Parameters ?

Table-valued parameters provide an easy way to marshal multiple rows of data from a client application to SQL Server without requiring multiple round trips or special server-side logic for processing the data.

You can use table-valued parameters (TVP) to send multiple rows of data to a Transact-SQL statement or a routine, such as a stored procedure or function, without creating a temporary table or many parameters.

Great news, you can use TVP with CodeFluent Entities! Let’s see how you can use TVP to bulk load rows based on a list of id.

Create a new CFQL method:

Note the usage of “[]” after the type name. The generated method is:

public static CustomerCollection LoadByIds(System.Guid[] ids)

And the generated stored procedure:

CREATE TYPE [dbo].[cf_type_Customer_LoadByIds_0] AS TABLE (
 [Item] [uniqueidentifier] NULL)
GO
CREATE PROCEDURE [dbo].[Customer_LoadByIds]
(
 @ids [dbo].[cf_type_Customer_LoadByIds_0] READONLY
)
AS
SET NOCOUNT ON
DECLARE @_c_ids int; SELECT @_c_ids= COUNT(*) FROM @ids
SELECT DISTINCT [Customer].[Customer_Id], [Customer].[Customer_FirstName], [Customer].[Customer_LastName], [Customer].[Customer_DateOfBirth]
    FROM [Customer]
    WHERE [Customer].[Customer_Id] IN (((SELECT * FROM @ids)))

RETURN
GO

Table-Valued Parameters require at least SQL Server 2008. Don’t forget to change the target of the SQL Server producer to use at least this version. Additionally set Legacy String Array Mode to false.

Additionally set Legacy String Array Mode to false.


References: http://www.softfluent.com/documentation/Methods_WorkingWithArrays.html

Happy coding,

The R&D Team.

Custom Naming Convention

January 8, 2015 Leave a comment

Since CodeFluent Entities infers a meta-model from your model, before any producer is called to generate a single line of code, a full representation of your application is in-memory. Thanks to this inference step and the resulting meta-model, developers can apply application wide changes.

One of the possible application wide changes is to change the way all database objects are named through a naming convention. By default a set of naming conventions are provided by CodeFluent Entities:

  • FormatNamingConvention
  • LowerCaseNamingConvention
  • UpperCaseNamingConvention
  • DecamelizeNamingConvention
  • DecamelizeLowerCaseNamingConvention
  • DecamelizeUpperCaseNamingConvention

And you can also implement your own naming convention to fit your needs as we’ll see in this post.

Create the custom Naming Convention

The naming convention is a class so we create a class library project and add references to

  • CodeFluent.Runtime.dll
  • CodeFluent.Model.dll
  • CodeFluent.Model.Common.dll

Those DLL are located in the installation folder of CodeFluent Entities.

Create a class that implements IProjectNamingConvention or inherits from an existing Naming Convention:

using System.Collections;
using CodeFluent.Model.Common.Naming;
using CodeFluent.Model.Naming;
using CodeFluent.Model.Persistence;

namespace SoftFluent.Samples.CustomNamingConvention
{
    public class MyNamingConvention : FormatNamingConvention
    {
        public override string GetName(INamedObject obj, IDictionary context)
        {
            var procedure = obj as Procedure;
            if (procedure != null)
            {
                if (procedure.Table != null &&
                    procedure.Table.Entity != null &&
                    procedure.Table.Entity.Store != null)
                {
                    return procedure.Table.Entity.Store.Name + "_" + base.GetName(obj, context);
                }

                if (procedure.Method != null &&
                    procedure.Method.Entity != null &&
                    procedure.Method.Entity.Store != null)
                {
                    return procedure.Method.Entity.Store.Name + "_" + base.GetName(obj, context);
                }
            }

            return base.GetName(obj, context);
        }
    }
}

This naming convention prefix parameter name with the store name

Setting the naming convention

Add a reference in the CodeFluent entities project to your class library project:

Now you can open model project properties and set the naming convention with its full type name:

 

That’s all J Now all procedures are prefixed by the store name:

Happy naming,

The R&D Team

Multi-tenant using multiple Schemas

December 4, 2014 Leave a comment

In this post we will see how to create a multi-tenant application using one schema by tenant. The idea is to create one CodeFluent Entities model and use it to generate as many database schemas as needed.

The generated database will contains one schema by tenant:

But to keep the usage as simple as possible, only one Business Object Model is generated. The schema is selected at runtime:

Note: The following procedure uses the Custom meta-compiler host feature which is available only with CodeFluent Entities Personal or Ultimate

Generate the database

 

The idea is to keep the model untouched, so we create a console application which:

  • Load the CodeFluent Entities model
  • Change entity schema in memory (the original model won’t be changed)
  • Generate code
class Program
{
    private static string _schema;
    private static string _projectPath;
    private static bool _changeTargetDirectory;

    static void Main()
    {
        _projectPath = CommandLineUtilities.GetArgument("path", (string)null) ?? CommandLineUtilities.GetArgument(0, (string)null);
        _schema = ConvertUtilities.Nullify(CommandLineUtilities.GetArgument("schema", (string)null) ?? CommandLineUtilities.GetArgument(1, (string)null), true);
        _changeTargetDirectory = CommandLineUtilities.GetArgument("changeTargetDirectory", true);

        // Load the model
        Project project = new Project();
        project.Entities.ListChanged += Entities_ListChanged; // Change schema as soon as the entity is loaded
        project.Load(_projectPath, ProjectLoadOptions.Default);

        // Update producer target directory
        if (!string.IsNullOrEmpty(_schema) && _changeTargetDirectory)
        {
            foreach (var producer in project.Producers)
            {
                var sqlServerProducer = producer.Instance as SqlServerProducer;
                if (sqlServerProducer != null)
                {
                    sqlServerProducer.Production += SqlServerProducer_Production;
                }
            }
        }

        // Generate code
        project.Produce();
    }

    private static readonly HashSet<IProducer> _producers = new HashSet<IProducer>();
    private static void SqlServerProducer_Production(object sender, ProductionEventArgs e)
    {
        SqlServerProducer sqlServerProducer = sender as SqlServerProducer;
        if (sqlServerProducer == null)
            return;

        if (_producers.Contains(sqlServerProducer))
            return;

        sqlServerProducer.EditorTargetDirectory = Path.Combine(sqlServerProducer.EditorTargetDirectory, _schema);
        _producers.Add(sqlServerProducer);
    }

    private static void Entities_ListChanged(object sender, ListChangedEventArgs e)
    {
        if (e.ListChangedType != ListChangedType.ItemAdded)
            return;

        var entityCollection = sender as EntityCollection;
        if (entityCollection == null || e.NewIndex < 0 || e.NewIndex >= entityCollection.Count)
            return;

        Entity entity = entityCollection[e.NewIndex];
        Console.WriteLine("Changing schema of entity '{0}' from '{1}' to '{2}'", entity.ClrFullTypeName, entity.Schema, _schema);
        entity.Schema = _schema;
    }
}

That’s it… We can now use this console application to generate the persistence layer:

SoftFluent.MultiTenantGenerator.exe "Sample.Model\Sample.Model.cfxproj" "SoftFluent"
SoftFluent.MultiTenantGenerator.exe "Sample.Model\Sample.Model.cfxproj" "Contoso"

You can create a script to call program this for each tenant.

Select the right tenant at runtime

 

We generate only one Business Object Model (BOM) for all tenants. This BOM access by default to the schema specified in the CodeFluent Entities model. In our case we want to change this schema at runtime depending on the context (user, Uri, etc.).

To access the database, the generated code use CodeFluentPersistence:

CodeFluentPersistence has a hook system (ICodeFluentPersistenceHook) that allows to change the default CodeFluentPersistence behavior. In our case the idea is to change the CreateStoredProcedureCommand method behavior to use the right schema. Here’s the code:

public class SchemaPersistenceHook : BasePersistenceHook
{
    private bool _processing = false;
    public override bool BeforeCreateStoredProcedureCommand(string schema, string package, string intraPackageName, string name)
    {
        if (_processing)
            return false;

        _processing = true;
        try
        {
            string currentSchema = GetTenant();
            Persistence.CreateStoredProcedureCommand(currentSchema, package, intraPackageName, name);
        }
        finally
        {
            _processing = false;
        }

        return true;
    }

    public virtual string GetTenant()
    {
            // TODO: Implement your own logic
            return CodeFluentUser.Current.UserDomainName;
    }
}

Finally we have to declare our persistence hook in the configuration file so CodeFluentPersistence will use it automatically:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="Sample" type="CodeFluent.Runtime.CodeFluentConfigurationSectionHandler, CodeFluent.Runtime" />
  </configSections>

  <Sample persistenceHookTypeName="Sample.SchemaPersistenceHook, Sample" />
</configuration>

That’s it. With a few lines of codes and the power of CodeFluent Entities you can change the default behavior to fit your needs. Can you do the same with other products?

The source code is available on our GitHub repository.

Happy tenanting,

The R&D team

CodeFluent Entities and SignalR

November 21, 2014 Leave a comment

ASP.NET SignalR is a new library for ASP.NET developers that makes it incredibly simple to add real-time web functionality to your applications. What is “real-time web” functionality? It’s the ability to have your server-side code push content to the connected clients as it happens, in real-time.

Let’s see how easy it is to use CodeFluent Entities with SignalR! This post introduces SignalR development by using CodeFluent Entities and showing how to create an application that shares the state of an CodeFluent entity (Customer) with other clients in real time.

Setting up the solution

The solution contains 4 projects:

  • The CodeFluent Entities model
  • A class project to contains the generated Business Object Model
  • The SignalR server (Console application)
  • The SignalR client (WPF application)

The CodeFluent Entities model
The model is very simple, just one entity:

To generate the server code we add the SQL Server Producer and the Business Object Model producer.

SignalR uses Json.NET to serialize object. The way this library finds a way to serialize an object is weird and does not works with generated object by default because of the following attribute:

[TypeConverterAttribute(typeof(CodeFluent.Runtime.Design.NameTypeConverter))]

So we have to remove it so the object is serialize correctly:

To generate the client object we add the Service Model sub producer (with the same setting as above):

Don’t forget to remove runtime design attributes:

Finally the model project looks like:

The SignalR server

The server is a Console application. First we add the “Microsoft.AspNet.SignalR.SelfHost” nuget package.

We can register the SignalR server:

class Program
{
    static void Main()
    {
        using (WebApp.Start<Startup>("http://localhost:12345"))
        {
            Console.WriteLine("Server started");
            Console.ReadKey();
        }
    }
}

public class Startup
{
    public void Configuration(IAppBuilder app)
    {
        HubConfiguration hubConfiguration = new HubConfiguration();
        hubConfiguration.EnableDetailedErrors = true;
        app.MapSignalR(hubConfiguration);
    }
}

Now we can create the Customer hub:

public class CustomerHub : Hub
{
    public IEnumerable<Customer> Get()
    {
        return CustomerCollection.LoadAll();
    }

    public bool Save(Customer customer)
    {
        bool save = Customer.Save(customer);
        if (save)
            Clients.All.Saved(customer); // Notify clients

        return save;
    }

    public bool Delete(Customer customer)
    {
        bool delete = Customer.Delete(customer);
        if (delete)
            Clients.All.Deleted(customer.Id); // Notify clients

        return delete;
    }
}

The generated Business Object Model is easy to use with any technology J.

The SignalR Client

The client is a WPF application. First we need to add the “Microsoft.AspNet.SignalR.Client” nuget package.

The project already contains generated class from the model so we don’t need to create a Customer class:

Let’s create the XAML:

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition/>
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal" Grid.Row="0">
            <Button Content="Load customers" Click="ButtonLoadCustomers_OnClick" Margin="5"/>
        </StackPanel>

        <DataGrid Grid.Row="1" x:Name="DataGrid" AutoGenerateColumns="False" RowEditEnding="DataGrid_RowEditEnding">
            <DataGrid.Columns>
                <DataGridTextColumn Binding="{Binding EntityKey, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Entity Key"/>
                <DataGridTextColumn Binding="{Binding FirstName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="First Name"/>
                <DataGridTextColumn Binding="{Binding LastName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Header="Last Name"/>

                <DataGridTemplateColumn>
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <Button Command="Delete" Content="X" Click="ButtonDelete_OnClick" DataContext="{Binding}"/>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>

Create the connection to the server and register callbacks:

private async Task<bool> EnsureProxy()
{
    if (HubProxy != null)
        return true;

    Connection = new HubConnection(ServerUri);
    HubProxy = Connection.CreateHubProxy("CustomerHub");

    // Register callbacks
    HubProxy.On<Customer>("Saved", OnCustomerSaved);
    HubProxy.On<Guid>("Deleted", OnCustomerDeleted);
    try
    {
        await Connection.Start();
        return true;
    }
    catch (HttpRequestException)
    {
        Connection.Dispose();
        Connection = null;
        MessageBox.Show("Unable to connect to server: Start server before connecting clients.");
        return false;
    }
}

Handle events:

private void OnCustomerDeleted(Guid id)
{
    var customerCollection = DataGrid.ItemsSource as CustomerCollection;
    if (customerCollection != null)
    {
        customerCollection.Remove(id);
    }
}

private void OnCustomerSaved(Customer customer)
{
    var customerCollection = DataGrid.ItemsSource as CustomerCollection;
    if (customerCollection != null)
    {
        var c = customerCollection[customer.Id];
        if (c != null)
        {
            customer.CopyTo(c, true); // Update existing customer
        }
        else
        {
            customerCollection.Add(customer); // Add new customer
        }
    }
}

Handle UI events (load, edit, delete):

private async void ButtonLoadCustomers_OnClick(object sender, RoutedEventArgs e)
{
    if (!await EnsureProxy())
        return;

    var customers = await HubProxy.Invoke<CustomerCollection>("Get");
    if (customers == null)
        customers = new CustomerCollection();

    BindingOperations.EnableCollectionSynchronization(customers, _lock);
    DataGrid.ItemsSource = customers;
}

private async void DataGrid_RowEditEnding(object sender, DataGridRowEditEndingEventArgs e)
{
    if (e.Cancel)
        return;

    var result = await HubProxy.Invoke<bool>("Save", e.Row.Item);
}

private async void ButtonDelete_OnClick(object sender, RoutedEventArgs e)
{
    var customer = ((Button)sender).DataContext as Customer;
    if (customer == null)
        return;

    if (!await EnsureProxy())
        return;

    var result = await HubProxy.Invoke<bool>("Delete", customer);
}

The Business Object Model and the Service Object Model are very easy to use with any .NET technologies such as SignalR or Web API.

If your SignalR API is as simple as the one we create, you can automate its creation with templates.

The code sample is available on our GitHub repository.

Happy Coding,

The R&D Team

Target Name Transformation aka TNT

October 10, 2014 Leave a comment

CodeFluent Entities allows you to write RAW methods. Using the name of a table or a column in a RAW method is not safe. Indeed CodeFluent Entities allows to define its own naming convention. So if you write the name of a column in a raw method and then you change the naming convention of your project, your method won’t work anymore.

To handle this case, CodeFluent Entities introduce Target Name Transformation (aka TNT). In a Raw method you can refers to a column by using for example “$Customer::DateOfBirth$”. This will be replaced by CodeFluent Entities by the name of the column corresponding to the property “DateOfBirth” of the entity “Customer”.

TNT supports the following syntaxes:

  • $[EntityName]$ corresponds to the table name,
  • $[PropertyName]$ corresponds to the property name,
  • $[EntityName]::[PropertyName]$ corresponds to the column name,
  • $[EntityName]:[ViewName]$ corresponds to the view name,
  • $[EntityName]:[ViewName]:[PropertyName]$ corresponds to a column name in the defined view,
  • $[Namespace].[EnumerationName].[EnumerationValue]$ corresponds to the enumeration value of an enumeration declared in the model.

We already talk about all of this. So today I’ll show you more.

When you write “$[EntityName]::[PropertyName]$”, CodeFluent Entities find the property and write its persistence name. But you can also specify a format by using curly brackets. Format allows you to navigate in the CodeFluent Entities API and get the desired value. Here’s some examples:

$Customer{Columns}$

[Customer].[Customer_Id],[Customer].[Customer_Name],[Customer].[Customer_DateOfBirth],
[Customer].[_trackLastWriteTime],[Customer].[_trackCreationTime],
[Customer].[_trackLastWriteUser],[Customer].[_trackCreationUser],
[Customer].[_rowVersion] 

$Customer:CustomView{Columns}$

[vCustomerCustomView].[Customer_Id],[vCustomerCustomView].[Customer_Name],
[vCustomerCustomView].[_rowVersion],[vCustomerCustomView].[_trackCreationTime],
[vCustomerCustomView].[_trackLastWriteTime],[vCustomerCustomView].[_trackCreationUser],
[vCustomerCustomView].[_trackLastWriteUser]

$Customer{TrackCreationUserColumn.FullName}$

[Customer].[_trackCreationUser]

$Customer{TrackLastWriteUserColumn.FullName}$

[Customer].[_trackLastWriteUser]

$Customer{TrackCreationTimeColumn.FullName}$

[Customer].[_trackCreationTime]

$Customer{TrackLastWriteTimeColumn.FullName}$

[Customer].[_trackLastWriteTime]

$Customer{RowVersionColumn.FullName}$

[Customer].[_rowVersion]

$Customer{TypeNameColumn.FullName}$

[Customer].[_typeName]

$Customer{Schema}$

Sample

$Customer{Entity.Properties.Count}$

3

$Customer{Columns[0].Name}$

Customer_Id

$Customer{Entity.Properties[“Name”].Column.Name}$

Customer_Name

$FirstName{Name}$

FirstName

$Customer::Name{DefaultValue}$

John Doe

$Customer::Id{FullPersistenceName}$

[Customer].[Customer_Id]

$Customer:CustomView:FirstName{Expression}$

FirstName

$Customer{Procedures[“Customer_Load”].FullName}$

[Customer_Load]

To find all possible formats, open Visual Studio Object Browser (Menu / View / Object Browser), and add “C:\Program Files (x86)\SoftFluent\CodeFluent\Modeler\CodeFluent.Model.dll” and look at properties of classes:

  • CodeFluent.Model.Persistence.Table
  • CodeFluent.Model.Property
  • CodeFluent.Model.Persistence.View
  • CodeFluent.Model.ViewProperty
  • CodeFluent.Model.Enumeration
  • CodeFluent.Model.EnumerationValue

If you can’t figure how to get a specific information from your model, please ask your question on the forum.

Happy TNTing,

The R&D Team

CodeFluent Entities and ComponentOne

October 6, 2014 Leave a comment

CodeFluent Entities generates code which can be used easily with many third party component providers. We already show before how to use CodeFluent Entities with Syncfusion. Today we’ll see how easy it is to work with ComponentOne (C1) WPF components!

The sample application displays a list of users and their contacts using a ComponentOne DataGrid. Additionally you can export user list to Excel.

The solution contains 4 projects:

The CodeFluent Entities model is very simple:

The email has a validation rule to ensure you can only save a user with an invalid email address to the database.

The relation between User and Contact is configured to save contacts after user. This means that when you call User.Save, associated contacts are also saved. This functionality is very useful in a master-detail view as we are creating!

Now we can create the WPF application. Here’s the main part of the XAML:

<Window.Resources>
<!-- Convert blob to image -->
<design:BinaryLargeObjectValueConverter2 x:Key="BlobConverter"/>
</Window.Resources>

<Grid>

<c1:C1DataGrid x:Name="DataGrid" ItemsSource="{Binding}" AutoGenerateColumns="False" RowDetailsVisibilityMode="VisibleWhenSelected">
  <c1:C1DataGrid.Columns>
    <c1:DataGridImageColumn Binding="{Binding Photo, Converter={StaticResource BlobConverter}}" Header="Photo" IsReadOnly="True" />
    <c1:DataGridTextColumn Binding="{Binding FirstName}" Header="First name" />
    <c1:DataGridTextColumn Binding="{Binding LastName}" Header="Last name"  />
    <c1:DataGridTextColumn Binding="{Binding Email}" Header="Email"  />
    <c1:DataGridBoundColumn Binding="{Binding Contacts.Count}" Header="Contacts" IsReadOnly="True" />
  </c1:C1DataGrid.Columns>

  <!-- Handle validation using IDataErrorInfo (this will validate the Email property) -->
  <c1:C1ValidationBehavior.ValidationBehavior>
    <c1:C1ValidationBehavior/>
  </c1:C1ValidationBehavior.ValidationBehavior>

  <c1:C1DataGrid.RowDetailsTemplate>
    <DataTemplate>
      <StackPanel Orientation="Vertical">
        <TextBlock Text="Contacts" FontSize="14"/>

        <c1:C1DataGrid ItemsSource="{Binding Contacts}" AutoGenerateColumns="False" BeginningNewRow="C1DataGrid_BeginningNewRow">
          <c1:C1DataGrid.Columns>
            <c1:DataGridTextColumn Binding="{Binding FirstName}" Header="First name" />
            <c1:DataGridTextColumn Binding="{Binding LastName}" Header="Last name" />
          </c1:C1DataGrid.Columns>
        </c1:C1DataGrid>
      </StackPanel>
    </DataTemplate>
  </c1:C1DataGrid.RowDetailsTemplate>
</c1:C1DataGrid>

<Button Grid.Row="1" HorizontalAlignment="Left" Click="ButtonExportToExcel_OnClick">Export Users to Excel</Button>
<Button Grid.Row="1" HorizontalAlignment="Right" Click="ButtonSaveAll_OnClick">Save all</Button>

</Grid>

When the window is opened, we load all users:

private readonly UserCollection _userCollection;

public MainWindow()
{
  // Load all users and bind them to the grid
  _userCollection = UserCollection.LoadAll();

  this.DataContext = _userCollection;
}

To save all users and their contacts, we have to call SaveAll method:

private void ButtonSaveAll_OnClick(object sender, RoutedEventArgs e)
{
   // Thanks to the cascade save, contacts are also saved
   _userCollection.SaveAll();
}

When a contact is added, we have to set its User property with the selected user:

private void C1DataGrid_BeginningNewRow(object sender,
DataGridBeginningNewRowEventArgs e)
{
  var contact = e.Item as Contact;

  if (contact == null)
    return;

  var user = DataGrid.CurrentRow.DataItem as User;

  if (user != null)
  {
    contact.User = user;
  }
}

Finally we can export user collection to Excel:

private
void ButtonExportToExcel_OnClick(object sender, RoutedEventArgs e)
{
  DataGrid.Save("export.xlsx", FileFormat.Xlsx);
}

That’s it. With only a few lines of code, CodeFluent Entities and ComponentOne you can create a fully functional application.

The code sample is available on our GitHub repository: https://github.com/SoftFluent/CodeFluent-Entities/tree/master/Samples/SoftFluent.Samples.ComponentOne

Happy componenting,

The R&D Team

Follow

Get every new post delivered to your Inbox.

Join 56 other followers