1. Michael Kench
  2. PowerBuilder
  3. Wednesday, 8 May 2019 07:18 AM UTC

Hi PB Team,
I am trying to get my head around what the benefits would be for using PB C# objects in a future .net core 2 web site being planned.  I am planning on using razor pages without using the entity framework. I am not a fan of LINQ so was planning on sticking with SQL with something like Dapper.
However it appears using Dapper it would involve writing insert/update statements for all rows whether they are changed or not. I'm assuming this is not the case with PB data/model store on posts?
Below is an example code behind page for a editable multi row form I have been testing with for proof of concept. I would be interested in how below would change with PB objects?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
using RazorPagesExample.Entity;
using System.Data.SqlClient;
using Dapper;

namespace RazorPagesExample.Pages
{    
    public class multirowformModel : PageModel
    {
        public class Proddaplocal
        {
            public int? Id { get; set; }
            public string Name { get; set; }
            public string Model { get; set; }
            public int Price { get; set; }
            public string Country { get; set; }
        }

         [BindProperty]        
        public List products { get; set; }

        IConfiguration config;
        public multirowformModel(IConfiguration configuration)
        {
            if (configuration != null)
            {
                config = configuration;
            }
        }
        public void OnGet()
        {
            var connectionString = config.GetSection("ConnectionStrings").GetSection("ProductContext").Value;
            using (var con = new SqlConnection(connectionString))
            {
                try
                {
                    con.Open();
                    var query = "SELECT Id,Name,Model,Price,Country FROM Product";
                    products = con.Query(query).ToList();
                }
                catch (Exception ex)
                {
                    throw ex;
                }
                finally
                {
                    con.Close();
                }
            }
        }
        public IActionResult OnPostUpdate()
        {
            var data = products;
            if (ModelState.IsValid)
            {
                var connectionString = config.GetSection("ConnectionStrings").GetSection("ProductContext").Value;
                var count = 0;
                using (var con = new SqlConnection(connectionString))
                {
                    try
                    {
                        con.Open();
                        foreach (var item in data)
                        {
                            if (item.Id == null)
                            {
                                var query = "INSERT INTO Product(Name, Model, Price, Country) VALUES(@Name, @Model, @Price, @Country); SELECT CAST(SCOPE_IDENTITY() as INT);";
                                count = con.Execute(query, item);
                            }
                            else
                            {
                                var query = "UPDATE Product SET Name = @Name, Model = @Model, Price = @Price, Country = @Country WHERE Id = @Id";
                                count = con.Execute(query, item);
                            }
                        }
                    }
                    catch (Exception ex)
                    {
                        throw ex;
                    }
                    finally
                    {
                        con.Close();
                    }
                }
            }
            return Page();
        }
        
        public IActionResult OnPostInsert()
        {
            var data = products;
            if (ModelState.IsValid)
            {
                data.Add(new Proddaplocal { Name = "Type Name here"});
            }
            return Page();
        }
    }
}

Michael Kench Accepted Answer Pending Moderation
  1. Thursday, 9 May 2019 07:04 AM UTC
  2. PowerBuilder
  3. # 1

Thanks Armeen for your advice/links and Adrian for the sample code.

I do like the idea of using the the model mapper as it requires less coding setting up connections however feel like its features are limited as appears to be missing out on lots of cool methods compared to modelstore like find,sort,filter,getmodelstate etc

Also I'm not sure how the class/model would handle more complex sql that have columns you want to update in a table but also other table columns and sql functions which are just descriptive.  How would my model(below)  change with the following sql.

SELECT Product.Id,Name,Model,Price,Country,model.status,dbo.f_onorder(Product.Id) as onorder FROM {oj product LEFT OUTER JOIN model ON product.model = model.id}

 [Table("Product", Schema = "dbo")]
public class Proddaplocal
 {
 [Key]
 [Identity]
public int? Id { get; set; }
public string Name { get; set; }
public string Model { get; set; }
public int Price { get; set; }
public string Country { get; set; }
public string status { get; set; }
public int onorder { get; set; }
 }

Comment
  1. Armeen Mazda @Appeon
  2. Thursday, 9 May 2019 07:32 AM UTC
Yes, if you want something more similar to DataStore but do not need to use the DW painter to create then we recommend the ModelStore. If you prefer to visually create (using the DW painter) then we recommend the .NET DataStore. Any one of the objects we provide should be much more productive than Dapper in our opinion.
  1. Helpful
  1. David Peace (Powersoft)
  2. Thursday, 9 May 2019 16:26 PM UTC
Do not forget the Json export & import functions that will copy the DW data with sate values ensuring that only updated columns are updated and inserts for new row with a single update.
  1. Helpful
  1. Michael Kench
  2. Friday, 10 May 2019 06:43 AM UTC
Great. I think I'd be happy to use the datastore but need understanding how to relate from SQL like my example above with updateable and non updateable columns from various sources to the model class.
  1. Helpful
There are no comments made yet.
Armeen Mazda @Appeon Accepted Answer Pending Moderation
  1. Wednesday, 8 May 2019 17:22 PM UTC
  2. PowerBuilder
  3. # 2

Hi Michael,

Writing SQL is not productive for many reasons, but I won't debate that point.  What I will contend is that many people turn to Dapper because 1) Dapper is much simpler than most other ORMs on the market (e.g. Entity Framework), and 2) you inherently have control over the SQL since you are writing it yourself.

Often times I hear people citing Dapper is faster than Entity Framework, but in our tests there was not a huge difference if Entity Framework (.NET Core version) was used correctly.  The issue goes back to the complexity of the Entity Framework and that many people don't know how to truly use it correctly so that's why the result is bad.  

The new .NET ORM framework we are providing is as simple (arguably simpler) than Dapper and you have control over the generated SQL, but we achieve all this without requiring you to write SQL by hand yourself, which we believe kills productivity.  In addition, Dapper is a Micro ORM not full-blown ORM so we provide far more features than Dapper out of the box.  For example, we have strong transaction management features.

I would encourage you to do some tests and compare our ORM to Dapper for yourself before you embark down a big project... I believe our ORM will save you a lot of work compared to Dapper without suffering from the traditional issues associated with other ORMs like Entity Framework.

Here are some links you may find useful to learn more:

Product page: https://www.appeon.com/products/snapobjects.html

Framework on NuGet: https://www.nuget.org/packages/SnapObjects.Data/

Documentation: https://www.appeon.com/documents#so

Regards,
Armeen Mazda
CEO, Appeon

Comment
There are no comments made yet.
Adrian Hernandez Accepted Answer Pending Moderation
  1. Wednesday, 8 May 2019 15:09 PM UTC
  2. PowerBuilder
  3. # 3

Hi Michael

I would advice you to use the Model Mapper Option, this way you don't have to care about opening/closing Database connections and you don't have to write any SQL sentence, I've adapted your code to show you could use this techhique.

But if you still want to use pure SQL, SnapObjects also provide an option to do it:

https://www.appeon.com/support/documents/appeon_online_help/snapobjects/api_references/Appeon.Data/SqlExecutor/ISqlExecutor/ISqlExecutor.html

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Configuration;
using RazorPagesExample.Entity;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using SnapObjects.Data;
using SnapObjects.Data.SqlServer;

namespace RazorPagesExample.Pages
{
///
/// This Class provides the 'fisical' DataBase Connection, and should be in a separated file
///
public class DBContext : SqlServerDataContext
{
public DBContext(string connectionString)
: this(new SqlServerDataContextOptions<DBContext>(connectionString))
{
}

public DBContext(IDataContextOptions<DBContext> options)
: base(options)
{
}

public DBContext(IDataContextOptions options)
: base(options)
{
}
}

public class multirowformModel : PageModel
{
 
private DBContext _dataContext;
private IModelMapper mapper;
IConfiguration config;
 
 
[Table("Product", Schema = "yourSchema")]
public class Proddaplocal
{
[Key]
[Identity]
public int? Id { get; set; }
public string Name { get; set; }
public string Model { get; set; }
public int Price { get; set; }
public string Country { get; set; }
}

[BindProperty]
public List products { get; set; }

 
public multirowformModel(IConfiguration configuration)
{
if (configuration != null)
{
config = configuration;
_dataContext = new DBContext(config.GetSection("ConnectionStrings").GetSection("ProductContext").Value);
mapper = _dataContext.ModelMapper;
}
}
public void OnGet()
{
try
{
products = mapper.Load<Proddaplocal>().ToList();;
}
catch (Exception ex)
{
throw ex;
}
}
public IActionResult OnPostUpdate()
{
var data = products;
if (ModelState.IsValid)
{
var count = 0;
try
{
foreach (var item in data)
{
if (item.Id == null)
{
 
count = mapper.TrackCreate<Proddaplocal>(item).SaveChanges().InsertedCount;
}
else
{
//In Beta you have to map properties, dev team is already working in a method to update directly from an object, without mapping.
var oldProduct = mapper.LoadByKey<Proddaplocal>(item.id).FirstOrDefault();

_dataContext.ModelMapper.TrackUpdate(oldProduct);
oldProduct.Name = item.Name;
oldProduct.Model = item.Model;
oldProduct.Price = item.Price;
oldProduct.Country = item.Country;

count = mapper.SaveChanges().ModifiedCount;

}
}
}
catch (Exception ex)
{
throw ex;
}
}
return Page();
}
 
public IActionResult OnPostInsert()
{
var data = products;
if (ModelState.IsValid)
{
data.Add(new Proddaplocal { Name = "Type Name here"});
}
return Page();
}
}
}
Comment
There are no comments made yet.
  • Page :
  • 1


There are no replies made for this question yet.
However, you are not allowed to reply to this question.