DNN DAL 2 hydrate object and other - dotnetnuke

Using basic DAL 2 to grab data from a table that has tabid. Would like to also get the Tab Url through the DNN API. I could join to the Tabs table, but want to work with the api.
Here is my model.
[TableName("My_Products")]
[PrimaryKey("ProductId")]
[Cacheable("My_Products_", CacheItemPriority.Normal, 20)]
public class ProductInfo
{
public ProductInfo()
{
Langs = new List<ProductLangInfo>();
}
public int ProductId { get; set; }
[ReadOnlyColumn]
public string ProductImage { get; set; }
public int LineID { get; set; }
[ReadOnlyColumn]
public string Culture { get; set; }
[ReadOnlyColumn]
public string ProductName { get; set; }
[ReadOnlyColumn]
public string ProductShortDesc { get; set; }
[ReadOnlyColumn]
public int TabId { get; set; }
[ReadOnlyColumn]
public string ProductURL { get; set; } //GET THIS FROM API
[ReadOnlyColumn]
public List<ProductLangInfo> Langs { get; set; }
}
This is my Controller
public IEnumerable<ProductInfo> GetProducts(string language)
{
using (IDataContext ctx = DataContext.Instance())
{
string sqlCmd = ";WITH cte as (SELECT * FROM [ProductsLang] WHERE Culture = #0)" +
" SELECT Products.*,cte.ProductName, cte.ProductShortDesc, cte.TabId" +
" FROM [Products] as Products" +
" INNER JOIN cte ON Products.ProductId = cte.ProductId";
string order = " ORDER BY Products.ProductId DESC";
return ctx.ExecuteQuery<ProductInfo>(CommandType.Text, sqlCmd + order, language);
}
}
I guess my question is where is the best way to pass in the tabid from my query to the DNN API?

This should be fairly straightforward using the NavigateURL method off DotNetNuke.Common.Globals
var url = DotNetNuke.Common.Globals.NavigateURL(TabId);
There are other ways to get URLs in DNN, but that is the easiest way that should respect all the various Providers that can be used to build out a URL
private string _productUrl = '';
public string ProductURL { get return DotnetNuke.Common.Globals.NavigateURL(TabId); }

Related

Populate two table data using ASP.NET Core

I want to get two table data in ASP.NET Core. I can get one table detail by using model class. then I can show data by using below code.
[HttpGet]
public async Task<ActionResult<IEnumerable<OrderMaster>>> GetOrderDetails()
{
return await _context.OrderDetails.ToListAsync();
}
So my question is how to get two tables data to the above method? As a example I want to retrieve data for below query:
select a.ItemDescription,a.Quantity,a.Amount, a.CustomerCode, b.CustomerName,b.CustomerAddress,b.MobileNumber,b.Email from OrderDetails as a left join CustomerDetails as b ON a.CustomerCode=b.CustomerCode
Thank you
My model classes
public class CustomerMaster
{
[Key]
public int CustomerCode { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string CustomerName { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string CustomerAddress { get; set; }
[Column(TypeName = "nvarchar(10)")]
public string MobileNumber { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string Email { get; set; }
}
public class OrderMaster
{
[Key]
public int OrderId { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string ItemCode { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string ItemName { get; set; }
[Column(TypeName = "nvarchar(MAX)")]
public string ItemDescription { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string Quantity { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string OrderDate { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string Amount { get; set; }
[Column(TypeName = "nvarchar(100)")]
public string CustomerCode { get; set; }
}
this is my Context class
public class AppDbcontext : DbContext
{
public AppDbcontext(DbContextOptions<AppDbcontext> options) : base(options)
{
}
public DbSet<CustomerMaster> CustomerDetails { get; set; }
public DbSet<OrderMaster> OrderDetails { get; set; }
}
}
For how to join two tables,a simple demo you could follow:
var model = (from a in _context.CustomerDetails
join b in _context.OrderDetails
on a.ID equals b.ID
select new {
Name = a.Name,
Address = b.Address
}).ToList();
Update 1:
You use left join sql,you need change like below:
var model = (from a in _context.OrderDetails
join b in _context.CustomerDetails
on a.CustomerCode equals b.CustomerCode.ToString() into ab
from b in ab.DefaultIfEmpty()
select new
{
ItemDescription = a.ItemDescription,
Quantity = a.Quantity,
Amount = a.Amount,
CustomerCode = a.CustomerCode,
CustomerName = b.CustomerName,
CustomerAddress = b.CustomerAddress,
MobileNumber = b.MobileNumber,
Email = b.Email,
}).ToList();
Update 2:
For how to return the two tables data,I think a better way is to create a view model to display:
public class OrderDetailViewModel
{
public string ItemDescription { get; set; }
public string Quantity { get; set; }
public string Amount { get; set; }
public string CustomerCode { get; set; }
public string CustomerName { get; set; }
public string CustomerAddress { get; set; }
public string MobileNumber { get; set; }
public string Email { get; set; }
}
[HttpGet]
public IEnumerable<OrderDetailViewModel> GetOrderDetails()
{
var model = (from a in _context.OrderDetails
join b in _context.CustomerDetails
on a.CustomerCode equals b.CustomerCode.ToString() into ab
from b in ab.DefaultIfEmpty()
select new OrderDetailViewModel
{
ItemDescription = a.ItemDescription,
Quantity = a.Quantity,
Amount = a.Amount,
CustomerCode = a.CustomerCode,
CustomerName = b.CustomerName,
CustomerAddress = b.CustomerAddress,
MobileNumber = b.MobileNumber,
Email = b.Email,
}).ToList();
return model;
}
You have to get the two models connected through navigation properties. Something like
public class Table1 {
int Table1Id;
string Name;
ICollection<Table2> Tables2;
}
public class Table2 {
int Table2Id;
string Address;
Table1 Table1;
}
Then you can have your business layer code
[HttpGet]
... GetNameWithAddresses(int id) {
return (from c in _context.Table1 where c.Table1Id == id
select new { c.Name, c.Tables2 }).ToList();
}
If the tables have primary-foreign key relationship, it will be done automatically for you if you scaffold them. And if you don't have FK relationship, you have to ask yourself why!
Use this code
using (var context = new AppDbcontext())
{
var table1 = context.CustomerDetails.ToList();
var table2 = context.OrderDetails.ToList();
}

Pass parameters to report Devexpress

Please tell me. Created 2 classes (Data Model)
public class User
{
public int UserID { get; set; }
public string UserName { get; set; }
public string Department { get; set; }
public int Office { get; set; }
public string Position { get; set; }
public string Phone { get; set; }
public float Mobile { get; set; }
public string EMail { get; set; }
public string Login { get; set; }
public int idArm { get; set; }
}
and
public class arm
{
public int id { get; set; }
public string name { get; set; }
public string Detalis { get; set; }
}
I installed 2 GridControlls on the form
And through DataSet showed data
string connectionString = ConfigurationManager.ConnectionStrings["connectionSIPiT"].ConnectionString;
string command = "SELECT * FROM Users";
string command2 = "SELECT * FROM arm";
sqlConnection = new SqlConnection(connectionString);
SqlDataAdapter adapter = new SqlDataAdapter(command2, sqlConnection);
SqlDataAdapter adapter1 = new SqlDataAdapter(command, sqlConnection);
DataSet dataset1 = new DataSet();
adapter.Fill(dataset1, "arm");
adapter1.Fill(dataset1, "Users");
DataColumn keyColumn = dataset1.Tables[0].Columns[0];
DataColumn foreignKeyColumn = dataset1.Tables[1].Columns[9];
dataset1.Relations.Add("armUsers", keyColumn, foreignKeyColumn);
armBindingSource.DataSource = dataset1;
armBindingSource.DataMember = "arm";
userBindingSource.DataSource = armBindingSource;
userBindingSource.DataMember = "armUsers";
gridControl1.DataSource = userBindingSource;
gridControl2.DataSource = armBindingSource;
How do I select a row in the main table GridControll. Send report data. Or pass the id of the main table to build the report? Can anyone come across such a task?
Make sure that the Modifiers property for the report parameter is set to Public or Internal
Use the GridView.GetRowCellValue method to get the ID column value of the focused record
The following assumes that you have a report called MyReport and it has a parameter called MyParameter.
var id = Convert.ToInt32(gridView1.GetRowCellValue(gridView1.FocusedRowHandle, gridView1.Columns["UserID"]));
var rpt = new MyNewReport();
rpt.MyParameter.Value = id; //Make sure the MyParameter's Modifiers property is set to Public or Internal.

Entity Framework Core - Very slow performance

I have the following entities (I'll show the properties I'm working with because I don't want to make it larger than needed):
PROPERTY: Where a property can be child of another one and has a 1-1 relationship with GeoLocation and can have multiple Multimedia and Operation
public partial class Property
{
public Property()
{
InverseParent = new HashSet<Property>();
Multimedia = new HashSet<Multimedia>();
Operation = new HashSet<Operation>();
}
public long Id { get; set; }
public string GeneratedTitle { get; set; }
public string Url { get; set; }
public DateTime? DatePublished { get; set; }
public byte StatusCode { get; set; }
public byte Domain { get; set; }
public long? ParentId { get; set; }
public virtual Property Parent { get; set; }
public virtual GeoLocation GeoLocation { get; set; }
public virtual ICollection<Property> InverseParent { get; set; }
public virtual ICollection<Multimedia> Multimedia { get; set; }
public virtual ICollection<Operation> Operation { get; set; }
}
GEOLOCATION: As mentioned, it has a 1-1 relationship with Property
public partial class GeoLocation
{
public int Id { get; set; }
public double? Latitude { get; set; }
public double? Longitude { get; set; }
public long? PropertyId { get; set; }
public virtual Property Property { get; set; }
}
MULTIMEDIA: it can hold multiple Images, with different sizes, for a single Property. The detail here is that Order specifies the order of the images to be shown in the client application, but it doesn't start always with 1. There're some cases where a Property has Multimedia files that starts with 3 or x.
public partial class Multimedia
{
public long Id { get; set; }
public long? Order { get; set; }
public string Resize360x266 { get; set; }
public long? PropertyId { get; set; }
public virtual Property Property { get; set; }
}
OPERATIONS: defines all the operations a Property can have, using OperationType to name this operation. (rent, sell, etc.)
public partial class Operation
{
public Operation()
{
Price = new HashSet<Price>();
}
public long Id { get; set; }
public long? OperationTypeId { get; set; }
public long? PropertyId { get; set; }
public virtual OperationType OperationType { get; set; }
public virtual Property Property { get; set; }
public virtual ICollection<Price> Price { get; set; }
}
public partial class OperationType
{
public OperationType()
{
Operation = new HashSet<Operation>();
}
public long Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Operation> Operation { get; set; }
}
PRICE: defines the price for each operation and the currency type. (i.e.: A property can have the rent option - Operation - for X amount in USD currency, but another price registered for the same Operation in case of use another currency type )
public partial class Price
{
public long Id { get; set; }
public float? Amount { get; set; }
public string CurrencyCode { get; set; }
public long? OperationId { get; set; }
public virtual Operation Operation { get; set; }
}
Said that, I want to get all the records (actually are about 40K-50K), but only for a few properties. As I mentioned before, the Multimedia table can have a lot of records for each Property, but I only need the first one with the smaller Order value and sorted by DatePublished. After that, I need to convert the result into MapMarker object, which is as follows:
public class MapMarker : EstateBase
{
public long Price { get; set; }
public int Category { get; set; }
public List<Tuple<string, string, string>> Prices { get; set; }
}
In order to achieve this, I made the following:
public async Task<IEnumerable<MapMarker>> GetGeolocatedPropertiesAsync(int quantity)
{
var properties = await GetAllProperties().AsNoTracking()
.Include(g => g.GeoLocation)
.Include(m => m.Multimedia)
.Include(p => p.Operation).ThenInclude(o => o.Price)
.Include(p => p.Operation).ThenInclude(o => o.OperationType)
.Where(p => p.GeoLocation != null
&& !string.IsNullOrEmpty(p.GeoLocation.Address)
&& p.GeoLocation.Longitude != null
&& p.GeoLocation.Latitude != null
&& p.StatusCode == (byte)StatusCode.Online
&& p.Operation.Count > 0)
.OrderByDescending(p => p.ModificationDate)
.Take(quantity)
.Select(p => new {
p.Id,
p.Url,
p.GeneratedTitle,
p.GeoLocation.Address,
p.GeoLocation.Latitude,
p.GeoLocation.Longitude,
p.Domain,
p.Operation,
p.Multimedia.OrderBy(m => m.Order).FirstOrDefault().Resize360x266
})
.ToListAsync();
var mapMarkers = new List<MapMarker>();
try
{
foreach (var property in properties)
{
var mapMarker = new MapMarker();
mapMarker.Id = property.Id.ToString();
mapMarker.Url = property.Url;
mapMarker.Title = property.GeneratedTitle ?? string.Empty;
mapMarker.Address = property.Address ?? string.Empty;
mapMarker.Latitude = property.Latitude.ToString() ?? string.Empty;
mapMarker.Longitude = property.Longitude.ToString() ?? string.Empty;
mapMarker.Domain = ((Domain)Enum.ToObject(typeof(Domain), property.Domain)).ToString();
mapMarker.Image = property.Resize360x266 ?? string.Empty;
mapMarker.Prices = new List<Tuple<string, string, string>>();
foreach (var operation in property.Operation)
{
foreach (var price in operation.Price)
{
var singlePrice = new Tuple<string, string, string>(operation.OperationType.Name, price.CurrencyCode, price.Amount.ToString());
mapMarker.Prices.Add(singlePrice);
}
}
mapMarkers.Add(mapMarker);
}
}
catch (Exception ex)
{
throw;
}
return mapMarkers;
}
but the results take more than 14 minutes and this method could be called multiple times in a minute. I want to optimize it to return the results in the less time possible. I alreay tried removing ToListAsync(), but in the foreach loop it takes a lot of time too, and that makes all the sense.
So, what do you think I can do here?
Thanks in advance.
UPDATE:
Here is GetAllProperties() method, I forgot to include this one.
private IQueryable<Property> GetAllProperties()
{
return _dbContext.Property.AsQueryable();
}
And the SQL query that Entity Framework is making against SQL Server:
SELECT [p].[Id], [p].[Url], [p].[GeneratedTitle], [g].[Address], [g].[Latitude], [g].[Longitude], [p].[Domain], (
SELECT TOP(1) [m].[Resize360x266]
FROM [Multimedia] AS [m]
WHERE [p].[Id] = [m].[PropertyId]
ORDER BY [m].[Order]), [t].[Id], [t].[CreationDate], [t].[ModificationDate], [t].[OperationTypeId], [t].[PropertyId], [t].[Id0], [t].[CreationDate0], [t].[ModificationDate0], [t].[Name], [t].[Id1], [t].[Amount], [t].[CreationDate1], [t].[CurrencyCode], [t].[ModificationDate1], [t].[OperationId]
FROM [Property] AS [p]
LEFT JOIN [GeoLocation] AS [g] ON [p].[Id] = [g].[PropertyId]
LEFT JOIN (
SELECT [o].[Id], [o].[CreationDate], [o].[ModificationDate], [o].[OperationTypeId], [o].[PropertyId], [o0].[Id] AS [Id0], [o0].[CreationDate] AS [CreationDate0], [o0].[ModificationDate] AS [ModificationDate0], [o0].[Name], [p0].[Id] AS [Id1], [p0].[Amount], [p0].[CreationDate] AS [CreationDate1], [p0].[CurrencyCode], [p0].[ModificationDate] AS [ModificationDate1], [p0].[OperationId]
FROM [Operation] AS [o]
LEFT JOIN [OperationType] AS [o0] ON [o].[OperationTypeId] = [o0].[Id]
LEFT JOIN [Price] AS [p0] ON [o].[Id] = [p0].[OperationId]
) AS [t] ON [p].[Id] = [t].[PropertyId]
WHERE (((([g].[Id] IS NOT NULL AND ([g].[Address] IS NOT NULL AND (([g].[Address] <> N'') OR [g].[Address] IS NULL))) AND [g].[Longitude] IS NOT NULL) AND [g].[Latitude] IS NOT NULL) AND ([p].[StatusCode] = CAST(1 AS tinyint))) AND ((
SELECT COUNT(*)
FROM [Operation] AS [o1]
WHERE [p].[Id] = [o1].[PropertyId]) > 0)
ORDER BY [p].[ModificationDate] DESC, [p].[Id], [t].[Id], [t].[Id1]
UPDATE 2: As #Igor mentioned, this is the link of the Execution Plan Result:
https://www.brentozar.com/pastetheplan/?id=BJNz9KdQI
Ok, a few things that should help. #1. .Include() and .Select() should in general be treated mutually exclusive.
You are selecting:
p.Id,
p.Url,
p.GeneratedTitle,
p.GeoLocation.Address,
p.GeoLocation.Latitude,
p.GeoLocation.Longitude,
p.Domain,
p.Operation,
p.Multimedia.OrderBy(m => m.Order).FirstOrDefault().Resize360x266
but then in your foreach loop accessing Price and OperationType entities off it.
Edit Updated the example for the collection of operation. (Whups)
Instead I would recommend:
p.Id,
p.Url,
p.GeneratedTitle,
p.GeoLocation.Address,
p.GeoLocation.Latitude,
p.GeoLocation.Longitude,
p.Domain,
Operations = p.Operation.Select( o => new
{
OperationTypeName = o.OperationType.Name,
o.Price.Amount,
o.Price.CurrencyCode
}).ToList(),
p.Multimedia.OrderBy(m => m.Order).FirstOrDefault().Resize360x266
Then adjust your foreach logic to use the returned properties rather than a returned entity and related entity values.
Loading 40-50k records with something like that image field (MultiMedia) is potentially always going to be problematic. Why do you need to load all 50k in one go?
This looks like something that would put markers on a map. Solutions like this should consider applying a radius filter at the very least to get markers within a reasonable radius of a given center point on a map, or if loading a larger area (zoomed out map) calculating regions and filtering data by region or getting a count falling in that region and loading/rendering the locations in batches of 100 or so rather than potentially waiting for all locations to load. Something to consider.

How to get all data from ef core many to many

On EF core have Two tables(Page, Group) both have many to many relations with junction table GroupPage. Want to get all pages data with junction table related data based on groupId as like bellow.
If you construct your EF relation correctly you should not have a GroupPage entity.
See Entity Framework Database First many-to-many on how to construct your EF EDM correctly.
Once you have your EDM correctly mapped, you should have the classes
public class Page
{
public int Id { get; set; }
public ICollection<Group> Groups { get; set; }
...
}
public class Group
{
public int Id { get; set; }
public ICollection<Page> Pages { get; set; }
...
}
Then you just need to do the following
public IQueryable<Page> GetPages(int groupId)
{
return from group in _context.Groups
where group.Id == groupId
from page in group.Pages
select page;
}
The following syntax is self-descriptive. Here are the entities structure and Page Dto.
public class Page
{
public int Id { get; set; }
public ICollection<Group> Groups { get; set; }
...
}
public class Group
{
public int Id { get; set; }
public ICollection<Page> Pages { get; set; }
...
}
public class PageGroup
{
public int PageId { get; set; }
public Page Page { get; set; }
public int GroupId { get; set; }
public Group Group { get; set; }
}
public class PagesDto
{
public string Name { get; set; }
public int GroupId { get; set; }
public int PageId { get; set; }
public string Description { get; set; }
public string Tab { get; set; }
public string Module { get; set; }
public bool? IsActive { get; set; }
public bool? IsDefault { get; set; }
public PagesDto()
{
IsActive = false;
IsDefault = false;
}
}
Following function help us to get group related pages information.
public async Task<List<PagesDto>> GetAllPagesByGroupId(int selectedGroupId)
{
//get all pages
var pages = await _pagesRepository.GetAll().Select(p => new PagesDto {
PageId = p.Id,
Name = p.Name,
GroupId = 0
}).ToListAsync();
//get group ralated pages
var selectedGroupPageIds = _groupPagesRepository
.GetAll()
.Where(p => p.GroupId == selectedGroupId)
.Select(p => p.PageId);
//update page information base on group related pages info.
foreach (var item in pages.Where(p=>selectedGroupPageIds.Contains(p.PageId)))
{
item.GroupId = selectedGroupId;
}
return pages;
}

Dapper: How to return selected columns?

public List<Customer> getCustomer()
{
using (IDbConnection con=DapperConnection())
{
string sql = "Select * from Customer";
return con.Query<Customer>(sql).Select(x => new { x.Id, x.LastName })
.ToList();
}
}
class Customer
{
public string Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set;}
}
Does anyone know how to return specific columns using dapper? What I am trying to achieve is to return just the Id and LastName as List so that I can bind them to my controls.
Unsure exactly what you mean but surely you should return the customer object instead of an anonymous type, or at least make a smaller version of the customer object to be used by the controls
public List<Customer> getCustomers()
{
using (IDbConnection con = DapperConnection())
{
string sql = "Select * from Customer";
return con.Query<Customer>(sql).ToList();
}
}
Or if you dont like the overhead of returning the full customer object
public List<CustomerBase> getCustomers()
{
using (IDbConnection con = DapperConnection())
{
string sql = "Select * from Customer";
return con.Query<CustomerBase>(sql).ToList();
}
}
public class CustomerBase
{
public string Id { get; set; }
public string LastName { get; set; }
}
public class Customer: CustomerBase
{
public string FirstName { get; set; }
//Other props...
}

Resources