Isolating/Accessing set session information within custom validator, servicestack API - session

I was wondering the best way of accessing the a user's AuthSession from within a custom validator we have hooked up via the servicestack's fluent-validation API hooks. Basically, the requirements are forcing us to access the database through this validator using a class called "DocNumberValidator ". The user's Id is saved into session when they authenticate, down the line we need this information to complete a successful SQL query. How does one access this session information (see below....). The IoC container does not have reference to the AuthSession?
I guess the question is, how does someone pass the necessary session value into a class invoked by SS validation framework?
Sample Code :
public class MyValidator : AbstractValidator<Statuses>
{
public IDocNumberValidator DocNumberValidator { get; set; }
public StatusesValidator()
{
RuleFor(s => s.DocNumber)
.Must(docNum => this.DocNumberValidator.Validate(docNum))
.WithMessage("Document Number is invalid.")
.WithErrorCode("S-001");
}
}
public class DocNumberValidator : IDocNumberValidator
{
public IDbConnectionFactory Db { get; set; }
public bool Validate(string docNum)
{
var isFound = false;
this.Db.Run(conn =>
{
var sql = "SELECT COUNT([docNumber]) FROM [TABLE] WHERE [docNumber] = #DocNum AND [UserId] = #UserID";
var cmd = conn.CreateCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = sql;
cmd.Parameters.Add(new SqlParameter("#DocNum", docNum));
cmd.Parameters.Add(new SqlParameter("#UserID", ????????)); // how does one get access to saved users session here
int cnt = (int) cmd.ExecuteScalar();
if (cnt == 1)
isFound = true;
});
return isFound;
}
}

Not really sure this is the best way to do it. Open to suggestions.
Use SessionFeature.GetSessionKey() to get the session key
Add public ICacheClient CacheClient { get; set; } to the validator.
Will be injected by IoC container
Get the AuthUserSession (or whatever type you're using) from the
cache using the key
Added into your example
public class DocNumberValidator : IDocNumberValidator
{
public IDbConnectionFactory Db { get; set; }
public ICacheClient CacheClient { get; set; }
public bool Validate(string docNum)
{
var isFound = false;
var sessionKey = SessionFeature.GetSessionKey();
var user = CacheClient.Get<AuthUserSession>(sessionKey); //Use whatever class you stored in the session
this.Db.Run(conn =>
{
var sql = "SELECT COUNT([docNumber]) FROM [TABLE] WHERE [docNumber] = #DocNum AND [UserId] = #UserID";
var cmd = conn.CreateCommand();
cmd.CommandType = CommandType.Text;
cmd.CommandText = sql;
cmd.Parameters.Add(new SqlParameter("#DocNum", docNum));
cmd.Parameters.Add(new SqlParameter("#UserID", user.UserAuthId)); // user whatever property you need access to
int cnt = (int) cmd.ExecuteScalar();
if (cnt == 1)
isFound = true;
});
return isFound;
}
}

Related

Oracle sequence EF Core 6.0?

I need to get a NEXTVAL from a SEQUENCE in an Oracle database. The modelbuilder does have a
builder.HasSequence("TABLE_SEQ");
But I have no clue on how to use that. The only way I can think of is scalar executing a raw SQL to retrieve the next value. Is that the way to go or are there better ways to do this?
I've found several posts that say I should use context.Database.SqlQuery() but in my solution that is missing. Do I need to add a library to get this functionality for EF 6.0?
Examples I found:
Example 1:
public int GetNewCertificateTradeRequestIdentity()
{
using var command = _context.Database.GetDbConnection().CreateCommand();
command.CommandText = "SELECT ts.seq_certificate_trade_request.NEXTVAL FROM DUAL";
_context.Database.OpenConnection();
using var reader = command.ExecuteReader();
reader.Read();
return reader.GetInt32(0);
}
Example 2:
users = await context.Database.SqlQuery<User>("Select * from User", new object[] { }).ToListAsync();
Both the _context.Database.GetDbConnection() context.Database.SqlQuery<x> are missing. Where can I find them?
Ok, in EF6 you have the context.Database.GetDbConnection().CreateCommand(). With that command you can execute a query on the database and receive the result. I've also found a solution for getting the tablename from the EF6 Metadata and added an extension method to handle that. Now I can do the the following:
private Tijdverantwoording Create(decimal? mdwid, decimal? deelprjid, Datum? date)
{
if (mdwid == null || deelprjid == null || date == null) throw new ArgumentNullException();
Weekstaatstatus weekstaatStatus = _WeekstaatStatusService.GetOrCreate(mdwid.Value, date.Jaarweekcode, WeekStaatStatussen.InBewerking, DateTime.Now);
var tijdverantwoording = new Tijdverantwoording
{
Tijdverantwoordingid = GetId<Tijdverantwoording>(), // <= Generate id
Mdwid = mdwid.Value,
Deelprjid = deelprjid.Value,
Datum = date.DagDatum,
Syncstatus = (decimal)SyncStatuses.InBewerking,
Syncdate = DateTime.Now.Date,
Weekstaatstatusid = weekstaatStatus.Weekstaatstatusid
};
_modelContext.Tijdverantwoordingen.Add(tijdverantwoording);
return tijdverantwoording;
}
The base class used for a service.
using Microsoft.EntityFrameworkCore;
using MyProjects.Core.Extensions;
using MyProjects.Core.Model;
namespace MyProjects.Core.Services
{
public class ServiceBase
{
private ModelContext? _modelContext;
public ServiceBase(ModelContext modelContext)
{
_modelContext = modelContext;
}
public decimal GetId<T>()
where T : class
{
var command = _modelContext.Database.GetDbConnection().CreateCommand();
var tableName = _modelContext.TableName(typeof(T));
command.CommandType = System.Data.CommandType.Text;
command.CommandText = $"SELECT {tableName}_SEQ.NEXTVAL FROM DUAL";
_modelContext.Database.OpenConnection();
try
{
var result = (decimal?)command.ExecuteScalar();
return result.Value;
}
finally
{
_modelContext.Database.CloseConnection();
}
}
}
}
And the extension method
using Microsoft.EntityFrameworkCore;
namespace MyProjects.Core.Extensions
{
public static class DatabaseExtensions
{
public static string? TableName(this DbContext context, Type type)
{
var entityType = context.Model.FindEntityType(type);
return entityType?.GetTableName() ?? throw new NullReferenceException($"Can't find name for type {type.Name}");
}
}
}

How could I read one to many linked table using link with Entity Framework?

I am new to Entity Framework and Linq (Visual Studio 2017 - EF 5.0) . Currently, I could read tables without issue but wonder how could I read a linked table.
My current functions do it but sure there is a simple way than two step reading that I have developed.
public override List<CartItem> GetMyCartOrderItems(int UserID)
{
try
{
using (foodorderingdbEntities oMConnection = new foodorderingdbEntities())
{
var oCart = oMConnection.carts.SingleOrDefault(p => p.USER_ID == UserID);
if (oCartItems != null)
{
int CartID = oCart.CART_ID;
var oCartItems = oMConnection.cart_item.Where(p => p.CART_ITEM_CART_ID == CartID);
if (oCartItems != null)
{
List<CartItem> oRecList = new List<CartItem>();
foreach (cart_item oDBrec in oCartItems)
{
CartItem oRec = new CartItem();
oRec.CartID = oDBrec.CART_ITEM_ID;
oRec.CartItemID = oDBrec.CART_ITEM_CART_ID;
oRec.DateTime = oDBrec.CART_ITEM_ADDED_DATE_TIME;
oRec.SystemComments = oDBrec.CART_ITEM_SYSTEM_COMMENTS;
oRecList.Add(oRec);
}
return oRecList;
}
else { return null; }
}
else { return null; }
}
}
catch (Exception ex)
{
//IBLogger.Write(LOG_OPTION.ERROR, "File : MHCMySQLDataConection.cs, Method : GetPatientByID(1), Exception Occured :" + ex.Message + Environment.NewLine + "Trace :" + ex.StackTrace);
return null;
}
}
You could see that I get Cart ID from Carts table using UserID and then I use the CartID retrieve cart Items from Cart_Item table. Cart_Item_Cart_ID is a foreign key in cart_item table. (This is a one to many table)
This is what I am thinking but obviously does not work.
List<cart_item> oCartItems = oMConnection.carts.SingleOrDefault(c => c.USER_ID == UserID).cart_item.Where(p => p.CART_ITEM_CART_ID = c.CART_ID).ToList<cart_item>();
Any help ?
My entity relation
public partial class cart
{
public cart()
{
this.cart_item = new HashSet<cart_item>();
}
public int CART_ID { get; set; }
public int USER_ID { get; set; }
public decimal ORDER_TOTAL_COST { get; set; }
public virtual ICollection<cart_item> cart_item { get; set; }
public virtual user user { get; set; }
}
Because your query has multiple levels of one to many relationships, and you just want the cart_items, it's easier to go the other way like this:
var oCart = oMConnection.cart_item
.Where(c=>c.cart.user.USER_ID == UserID);
Going the way you did should have worked as well, but you needed to use SelectMany instead of select like this:
var oCartItems = oMConnection.carts
.Where(c=>c.USER_ID==UserID)
.SelectMany(c=>c.cart_item);

The entity or complex type 'AdventureWorks2012Model.Product' cannot be constructed in a LINQ to Entities query

As you can see, I got this error when I built Data Gird using Kendo UI. Does anybody could point out where I'm wrong in my code below.
private IEnumerable<Product> GetSubProduct()
{
var context = new AdvenDBEntities();
var subcate = context.Products.Select(p => new Product
{
ProductID = p.ProductID,
Name = p.Name,
Color = p.Color,
ListPrice = p.ListPrice,
}).ToList();
return subcate;
}
Error:
The entity or complex type 'AdventureWorks2012Model.Product' cannot be constructed in a LINQ to Entities query.
Thank you so much for your time!
Since Product is an entity of model, you are creating new object of this entity while selecting the records, which is NOT good idea, I am NOT sure how model will handle this kind of behaviour that is why it is preventing you to do so, (I guess). Anyway you can change the code to this,
private IEnumerable<Product> GetSubProduct()
{
var context = new AdvenDBEntities();
var subcate = context.Products.ToList();
return subcate;
}
BTW your function name indicating that you are missing a Where clause.
Also you can create some custom DTO class and use it instead.
E.g.
class ProductDTO
{
public int ProductID { get; set; }
public string Name { get; set; }
public string Color { get; set; }
public decimal ListPrice { get; set; }
}
private IEnumerable<ProductDTO> GetSubProduct()
{
var context = new AdvenDBEntities();
var subcate = context.Products.Select(p => new ProductDTO
{
ProductID = p.ProductID,
Name = p.Name,
Color = p.Color,
ListPrice = p.ListPrice,
}).ToList();
return subcate;
}
The first bad smell code I can point out for you. DBContext implements IDisposable so you are responsible for calling Dispose on it. In all, but one case in here, using block
You must build query to get all the product and then extract from it.
private IEnumerable<Product> GetSubProduct()
{
using (var context = new AdvenDBEntities())
{
// Get all constructed type product and then select from it
var subcate = context.Products
.ToList()
.Select(p => new Product
{
ProductID = p.ProductID,
Name = p.Name,
Color = p.Color,
ListPrice = p.ListPrice,
});
return subcate;
}
}

Why SELECT N + 1 with no foreign keys and LINQ?

I have a database that unfortunately have no real foreign keys (I plan to add this later, but prefer not to do it right now to make migration easier). I have manually written domain objects that map to the database to set up relationships (following this tutorial http://www.codeproject.com/Articles/43025/A-LINQ-Tutorial-Mapping-Tables-to-Objects), and I've finally gotten the code to run properly. However, I've noticed I now have the SELECT N + 1 problem. Instead of selecting all Product's they're selected one by one with this SQL:
SELECT [t0].[id] AS [ProductID], [t0].[Name], [t0].[info] AS [Description]
FROM [products] AS [t0]
WHERE [t0].[id] = #p0
-- #p0: Input Int (Size = -1; Prec = 0; Scale = 0) [65]
Controller:
public ViewResult List(string category, int page = 1)
{
var cat = categoriesRepository.Categories.SelectMany(c => c.LocalizedCategories).Where(lc => lc.CountryID == 1).First(lc => lc.Name == category).Category;
var productsToShow = cat.Products;
var viewModel = new ProductsListViewModel
{
Products = productsToShow.Skip((page - 1) * PageSize).Take(PageSize).ToList(),
PagingInfo = new PagingInfo
{
CurrentPage = page,
ItemsPerPage = PageSize,
TotalItems = productsToShow.Count()
},
CurrentCategory = cat
};
return View("List", viewModel);
}
Since I wasn't sure if my LINQ expression was correct I tried to just use this but I still got N+1:
var cat = categoriesRepository.Categories.First();
Domain objects:
[Table(Name = "products")]
public class Product
{
[Column(Name = "id", IsPrimaryKey = true, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)]
public int ProductID { get; set; }
[Column]
public string Name { get; set; }
[Column(Name = "info")]
public string Description { get; set; }
private EntitySet<ProductCategory> _productCategories = new EntitySet<ProductCategory>();
[System.Data.Linq.Mapping.Association(Storage = "_productCategories", OtherKey = "productId", ThisKey = "ProductID")]
private ICollection<ProductCategory> ProductCategories
{
get { return _productCategories; }
set { _productCategories.Assign(value); }
}
public ICollection<Category> Categories
{
get { return (from pc in ProductCategories select pc.Category).ToList(); }
}
}
[Table(Name = "products_menu")]
class ProductCategory
{
[Column(IsPrimaryKey = true, Name = "products_id")]
private int productId;
private EntityRef<Product> _product = new EntityRef<Product>();
[System.Data.Linq.Mapping.Association(Storage = "_product", ThisKey = "productId")]
public Product Product
{
get { return _product.Entity; }
set { _product.Entity = value; }
}
[Column(IsPrimaryKey = true, Name = "products_types_id")]
private int categoryId;
private EntityRef<Category> _category = new EntityRef<Category>();
[System.Data.Linq.Mapping.Association(Storage = "_category", ThisKey = "categoryId")]
public Category Category
{
get { return _category.Entity; }
set { _category.Entity = value; }
}
}
[Table(Name = "products_types")]
public class Category
{
[Column(Name = "id", IsPrimaryKey = true, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)]
public int CategoryID { get; set; }
private EntitySet<ProductCategory> _productCategories = new EntitySet<ProductCategory>();
[System.Data.Linq.Mapping.Association(Storage = "_productCategories", OtherKey = "categoryId", ThisKey = "CategoryID")]
private ICollection<ProductCategory> ProductCategories
{
get { return _productCategories; }
set { _productCategories.Assign(value); }
}
public ICollection<Product> Products
{
get { return (from pc in ProductCategories select pc.Product).ToList(); }
}
private EntitySet<LocalizedCategory> _LocalizedCategories = new EntitySet<LocalizedCategory>();
[System.Data.Linq.Mapping.Association(Storage = "_LocalizedCategories", OtherKey = "CategoryID")]
public ICollection<LocalizedCategory> LocalizedCategories
{
get { return _LocalizedCategories; }
set { _LocalizedCategories.Assign(value); }
}
}
[Table(Name = "products_types_localized")]
public class LocalizedCategory
{
[Column(Name = "id", IsPrimaryKey = true, IsDbGenerated = true, AutoSync = AutoSync.OnInsert)]
public int LocalizedCategoryID { get; set; }
[Column(Name = "products_types_id")]
private int CategoryID;
private EntityRef<Category> _Category = new EntityRef<Category>();
[System.Data.Linq.Mapping.Association(Storage = "_Category", ThisKey = "CategoryID")]
public Category Category
{
get { return _Category.Entity; }
set { _Category.Entity = value; }
}
[Column(Name = "country_id")]
public int CountryID { get; set; }
[Column]
public string Name { get; set; }
}
I've tried to comment out everything from my View, so nothing there seems to influence this. The ViewModel is as simple as it looks, so shouldn't be anything there.
When reading this ( http://www.hookedonlinq.com/LinqToSQL5MinuteOVerview.ashx) I started suspecting it might be because I have no real foreign keys in the database and that I might need to use manual joins in my code. Is that correct? How would I go about it? Should I remove my mapping code from my domain model or is it something that I need to add/change to it?
Note: I've stripped parts of the code out that I don't think is relevant to make it cleaner for this question. Please let me know if something is missing.
EDIT: Gert Arnold solved the issue of all Products from the Category being queried one by one. However I'm still having the issue that all Products displayed on the page gets queried one by one.
This happens from my view code:
List.cshtml:
#model MaxFPS.WebUI.Models.ProductsListViewModel
#foreach(var product in Model.Products) {
Html.RenderPartial("ProductSummary", product);
}
ProductSummary.cshtml:
#model MaxFPS.Domain.Entities.Product
<div class="item">
<h3>#Model.Name</h3>
#Model.Description
#if (Model.ProductSubs.Count == 1)
{
using(Html.BeginForm("AddToCart", "Cart")) {
#Html.HiddenFor(x => x.ProductSubs.First().ProductSubID);
#Html.Hidden("returnUrl", Request.Url.PathAndQuery);
<input type="submit" value="+ Add to cart" />
}
}
else
{
<p>TODO: länk eller dropdown för produkter med varianter</p>
}
<h4>#Model.LowestPrice.ToString("c")</h4>
</div>
Is it something with .First() again? I tried .Take(1) but then I couldn't select the ID anyway...
EDIT: I tried adding some code to my repository to access the DataContext and this code to create a DataLoadOptions. But it still generates a query for each ProductSub.
var dlo = new System.Data.Linq.DataLoadOptions();
dlo.LoadWith<Product>(p => p.ProductSubs);
localizedCategoriesRepository.DataContext.LoadOptions = dlo;
var productsInCategory = localizedCategoriesRepository.LocalizedCategories.Where(lc => lc.CountryID == 1 && lc.Name == category)
.Take(1)
.SelectMany(lc => lc.Category.ProductCategories)
.Select(pc => pc.Product);
The SQL generated is slightly different though, and the order of the queries is also different.
For the queries that select ProductSub the DataLoadOptions-code generates variables named #x1 and without them the variables are named #p0.
SELECT [t0].[products_id] AS [ProductID], [t0].[id] AS [ProductSubID], [t0].[Name], [t0].[Price]
FROM [products_sub] AS [t0]
WHERE [t0].[products_id] = #x1
The difference in order for queries to me indicate that DataLoadOptions is in fact doing something, but not what I expect. What I'd expect is for it to generate something like this:
SELECT [t0].[products_id] AS [ProductID], [t0].[id] AS [ProductSubID], [t0].[Name], [t0].[Price]
FROM [products_sub] AS [t0]
WHERE [t0].[products_id] = #x1 OR [t0].[products_id] = #x2 OR [t0].[products_id] = #x3 ... and so on
It is the First(). It triggers execution of the part before it and the part following it is fetched by lazy loading in separate queries. Tricky, hard to spot.
This is what you can do to prevent it and fetch everything in one shot:
LocalizedCategories.Where(lc => lc.CountryID == 1 && lc.Name == category)
.Take(1)
.SelectMany(lc => lc.Category.ProductCategories)
.Select (pc => pc.Product)
You should make the member ProductCategories public. I think it is also better to remove the derived properties Category.Products and Product.Categories, because I think they will trigger a query whenever their owner is materialized or addressed.

Returning LINQ to Entities Query Result as JSON string

I'm attempting to construct a web service that allows for RESTful requests to return LINQ to Entities data as JSON string data. I have no problem executing a call to the database that returns one specific object:
public Product GetTicket(string s)
{
int id = Convert.ToInt32(s);
MWAEntities context = new MWAEntities();
var ticketEntity = (from p
in context.HD_TicketCurrentStatus
where p.Ticket_ID == id
select p).FirstOrDefault();
if (ticketEntity != null)
return TranslateTicketEntityToTicket(ticketEntity);
else
throw new Exception("Invalid Ticket ID");
/**
Product product = new Product();
product.TicketId = 1;
product.TicketDescription = "MyTest";
product.TicketOperator = "Chad Cross";
product.TicketStatus = "Work in Progress";
return product;
*/
}
private Product TranslateTicketEntityToTicket(
HD_TicketCurrentStatus ticketEntity)
{
Product ticket = new Product();
ticket.TicketId = ticketEntity.Ticket_ID;
ticket.TicketDescription = ticketEntity.F_PrivateMessage;
ticket.TicketStatus = ticketEntity.L_Status;
ticket.TicketOperator = ticketEntity.L_Technician;
return ticket;
}
Using curl, I get json data:
curl http://192.168.210.129:1111/ProductService/ticket/2
{"TicketDescription":"Firewall seems to be blocking her connection to www.rskco.com","TicketId":2,"TicketOperator":"Jeff","TicketStatus":"Completed"}
That being said, I have no idea how to get a string of JSON objects using the following query:
public List<MyTicket> GetMyTickets(string userId)
{
MWAEntities context = new MWAEntities();
/**
* List of statuses that I consider to be "open"
* */
string[] statusOpen = new string[] { "Work in Progress", "Assigned", "Unassigned" };
/**
* List of tickets with my userID
* */
var tickets = (from p
in context.HD_TicketCurrentStatus
where statusOpen.Contains(p.L_Status) & p.L_Technician == userId
select new MyTicket(p.Ticket_ID, p.Ticket_CrtdUser, p.F_PrivateMessage, p.Ticket_CrtdDate, p.L_Status));
return ???;
}
MyTicket is a type defined as follows:
[DataContract]
public class MyTicket
{
public MyTicket(int ticketId, string TicketCreator, string FirstPrivateMessage, DateTime TicketCreatedDate, string Status)
{
this.TicketId = ticketId;
this.TicketCreator = TicketCreator;
this.FirstPrivateMessage = FirstPrivateMessage;
this.TicketCreatedDate = TicketCreatedDate;
this.Status = Status;
}
[DataMember]
public int TicketId { get; set; }
[DataMember]
public string TicketCreator { get; set; }
[DataMember]
public string FirstPrivateMessage { get; set; }
[DataMember]
public DateTime TicketCreatedDate { get; set; }
[DataMember]
public string Status { get; set; }
//p.Ticket_CrtdUser, p.Ticket_CrtdDate, p.Ticket_ID, p.F_PrivateMessage
}
I would just like to get a list of JSON strings as output in order to parse using JS. I've tried using a foreach loop to parse "var" into a List of MyTicket objects, calling .ToList()), etc., to no avail.
I cannot change the backend (SQL 2005/2008), but I'm trying to use a standard HTML/JS client to consume a .NET 4.0 web service. Any help would be greatly appreciated. I've spent literally days searching and reading books (especially on O'Reilly's Safari site) and I have not found a reasonable solution :(.
use Json.NET: http://james.newtonking.com/pages/json-net.aspx
using Newtonsoft.Json;
var serializer = new JsonSerializer();
serializer.Serialize(Response.Output, tickets); // per your example
EDIT: Argh, the above is if you want to handle the serialization yourself.
In your example, change the return of the method from List to Ticket[] and do
return tickets.ToArray();
I wanted to add that I eventually got help to solve this. I'm not using business entities even though I'm using the Entity Framework. This may not be a wise decision, but I'm increasingly confused with Linq2SQL and Linq2EF. Here is the the code that made the above work:
public List<MyTicket> GetMyTickets(string userId)
{
MWAEntities context = new MWAEntities();
/**
* List of statuses that I consider to be "open"
* */
string[] statusOpen = new string[] { "Work in Progress", "Created"};
var tickets = (from p
in context.HD_TicketCurrentStatus
where statusOpen.Contains(p.L_Status) & p.L_Technician == userId
select new MyTicket{
TicketId = p.Ticket_ID,
TicketCreatedDate = p.Ticket_CrtdDate,
FirstPrivateMessage = p.F_PrivateMessage,
Status = p.L_Status,
TicketCreator = p.Ticket_CrtdUser
});
return tickets.ToList();
}

Resources