Setting custom properties on EntityFrameworkCore Model - asp.net-core

I am having a table to store individual pages of courses. The table looks like :
Pages :
---------------------------------------------------
CourseId | LessonId | TopicId | Url
---------------------------------------------------
1 | 1 | 0 | lesson-1
---------------------------------------------------
1 | 1 | 1 | lesson-1-Topic-1
---------------------------------------------------
1 | 1 | 2 | lesson-1-Topic-2
---------------------------------------------------
1 | 1 | 3 | lesson-1-Topic-3
---------------------------------------------------
1 | 2 | 0 | lesson-2
---------------------------------------------------
1 | 2 | 1 | lesson-2-Topic-1
---------------------------------------------------
1 | 2 | 2 | lesson-2-Topic-2
---------------------------------------------------
The CourseId, LessonId & TopicId are integers and constitute the Primary Key for the table. This model enables me to quickly know the lesson, the topic serial no. , topics in a lesson.
I wish to have entity model like :
Public class Page
{
public int CourseId {get;set;}
public int LessonId {get;set;}
public int TopicId {get;set;}
// Need to write custom logic to fetch and set below properties
// Whenever this entity is initialized
public Page PreviousPage {get;set;}
public Page NextPage {get;set;}
public Page LessonPage {get;set;}
public ICollection<Page> ChildPages {get;set;}
}
How can i set these custom properties within the model ? We can query the DB in the controller and set these properties. Wouldn't that push the application logic in the controller ?
Platform / libraries : Asp.Net Core 2.0, MySQL 5.7, Pomelo.EntityFrameworkCore.Mysql

Related

How Inheritance Weakens Encapsulation

In the book The object oriented thought process it is stated that:
Encapsulation is the process of packaging classes into the public
interface and the private implementation.
Example:
---------------------------
| Cabbie |
---------------------------
| -companyName: String |
---------------------------
| -name: String |
---------------------------
| +Cabbie: void |
| +Cabbie: void |
| +setName: void |
| +getName: String |
| +giveDirections: void |
| -turnRight: void |
| -turnLeft: void |
| +getCompanyName: String |
---------------------------
---------------------------
| PartTimeCabbie |
---------------------------
| partTimeHours: int |
---------------------------
| +setPartTimeHours: void |
| +getPartTimeHours: int |
---------------------------
PartTimeCabbie is a subclass of Cabbie. Thus, PartTimeCabbie inherits
the public implementation of Cabbie, including the method
giveDirections(). If the method giveDirections()is changed in Cabbie,
it will have a direct impact on PartTimeCabbie and any other classes
that might later be subclasses of Cabbie. In this subtle way, changes
to the implementation of Cabbie are not necessarily encapsulated
within the Cabbie class.
I don't uderstand the concept of public implementation? Is it not a private implementation and public interface?

SignalR-Hub with Entity Framework returns wrong entity

We have a problem with SignalR, which we currently cannot fully understand:
System
Framework: .NET Core 2.2
Entity Framework: 2.2.6.0
Description of the problem
We use SignalR to communicate with multiple clients. Via the connection the clients log on to the server with a unique key. We use the Entity Framework to access the database and select the client for the given key. Now it has happened for the second time that the wrong client entry was selected for the key during the access and therefore a wrong login with the wrong client has made. This happened when
about 8 clients have logged on via the hub at the same time.
We assume that there are problems with the DbContext because it is not Threadsafe and for some reason the same Context is accessed for multiple requests(maybe this can be solved by updating to Core 3.1). However, we would like to understand how this can happen or why it failes. How does it happen that accessing the DbContext via FirstOrDefaultAsync() returns the wrong client? The Hub and the DbContext should be instantiated per request?
Example
The database currently contains several client entries:
+------------+---------+
| ClientId | Key |
+------------+---------+
| 1 | ABC |
| 2 | DEF |
| 3 | GHI |
| ... | ... |
+------------+---------+
Now several clients log in with their key at the same time. In one of the cases the wrong client was returned:
Key ABC returns Client 1
Key DEF returns Client 2
Key GHI returns Client 1
Structure
Our server provides a SignalR-Hub, which is configured in the Startup:
app.UseSignalR(routes =>
{
routes.MapHub<MyServerHub>("/myhub");
});
According to the documentation and source code, the hub should be registered as transient (Use hubs in SignalR for ASP.NET Core).
We also use a service in the hub, which is injected by DI.
public MyServerHub(IMyDatabaseService myDatabaseService)
{
_myDatabaseService = myDatabaseService;
}
This service uses the DbContext:
private MyContext _myContext { get; }
public MyDatabaseService(MyContext myContext)
{
_myContext= myContext;
}
In the Startup the service and the DbContext is defined as Scoped:
services.AddScoped<IMyDatabaseService, MyDatabaseService>();
services.AddDbContext<MyContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
In this method of hub we get the wrong client for a key:
public async Task Connect(string key)
{
if (!string.IsNullOrWhiteSpace(key))
{
var client = await _myDatabaseService.GetByKey(key);
if(client == null)
{
await LogInformation(key, null, ConnectionEvent.ConnectWithInvalidKey);
await Clients.Caller.ReceiveInvalidKey();
return;
}
await Clients.Caller.ReceiveValidClientKey(client.Id);
// This created following logs:
// "ABC" with id "1" at 09:25:47
// "GHI" with id "3" at 09:34:03
// "GHI" with id "1" at 10:06:02
// "ABC" with id "1" at 10:06:03
await LogInformation(key, client.Id, ConnectionEvent.ConnectedSuccessfully);
}
else
await Clients.Caller.ReceiveInvalidKey();
}
Access to the DbContext in MyDatabaseService:
public async Task<Client> GetByKey(string key)
{
ClientRecord client = await _myContext.Clients.FirstOrDefaultAsync(p => p.Key == key);
if (client == null)
return null;
return new Client
{
Id = client.Id,
Key = client.Key
};
}
EDIT: Logging Informations
We log every call on the server hub. At 10:06:01 all connected SignalR clients (~18) were disconnected by with unkown reason (OnDisconnectedAsync was executed for each client). From 10:06:02 to 10:06:03 the clients automatically reconnected via our client application.
+------------+---------+-----------------------+---------------------+----------+
| ClientId | Key | ConnectionId | Event | Date |
+------------+---------+-----------------------+---------------------+----------+
| - | - |jF2OqBVgBb1PLOiVRn8SPQ | SignalR-Connected | 9:25:47 |
| 1 | ABC |jF2OqBVgBb1PLOiVRn8SPQ | Connected | 9:25:47 |
| ... | ... |... | ... | ... |
| - | - |5fg1CwmZxFjGXF42zb-7BQ | SignalR-Connected | 9:34:02 |
| 3 | GHI |5fg1CwmZxFjGXF42zb-7BQ | Connected | 9:34:03 |
| ... | ... |... | ... | ... |
| 1 | - |jF2OqBVgBb1PLOiVRn8SPQ | SignalR-Disconnected| 10:06:01 |
| 3 | - |5fg1CwmZxFjGXF42zb-7BQ | SignalR-Disconnected| 10:06:01 |
| ... | ... |... | ... | ... |
| - | - |2lZfpu08UerqbjwNkKLC3Q | SignalR-Connected | 10:06:02 |
| - | - |fg0oXdDAoKW3D4Ps6WOhhg | SignalR-Connected | 10:06:02 |
| 1 | GHI |fg0oXdDAoKW3D4Ps6WOhhg | Connected | 10:06:02 |
| 1 | ABC |2lZfpu08UerqbjwNkKLC3Q | Connected | 10:06:03 |
+------------+---------+-----------------------+---------------------+----------+

Laravel 7 query relationships tables

I need a bit of help with getting data from tables that are in relationship.
I have 2 tables: cards and statuses:
I have following data in cards table:
id_card | card_type
--------|----------
123 | tag
281 | card
455 | card
721 | tag
In statuses table I have:
id_status | id_card | status_date | status_type | status_company | status_user
----------|---------|-------------|-------------|----------------|------------
1 | 123 | 2018-12-04 | available | |
2 | 281 | 2018-12-04 | available | |
3 | 455 | 2018-12-04 | available | |
4 | 721 | 2019-03-26 | available | |
5 | 281 | 2020-01-25 | issued | Company A | User One
6 | 123 | 2020-01-10 | issued | Company B | User Two
7 | 281 | 2020-01-25 | available | |
8 | 123 | 2020-02-02 | lost | |
9 | 455 | 2020-02-14 | issued | Company C | Third User
For table cards I have following in Card model:
public function Status() {
return $this->hasMany('App\Status', 'id_card', 'id_card')
->orderBy('status_date', 'desc');
}
And for statuses table I have in Status model:
public function Card() {
return $this->hasOne('App\Card', 'id_card', 'id_card')
}
Now, how should I write a query, that would return me list of cards together with values from statuses table, but only with latest status for each card, like this:
id_card | card_type | status_type | status_date | status_company | status_user
--------|-----------|-------------|-------------|----------------|------------
123 | tag | lost | 2020-02-02 | |
281 | card | available | 2020-01-25 | |
455 | card | issued | 2020-02-14 | Company C | Third User
721 | tag | available | 2019-03-26 |
And is there a way, to also filter such a query i.e. to get only available cards:
id_card | card_type | status_type | status_date | status_company | status_user
--------|-----------|-------------|-------------|----------------|------------
281 | card | available | 2020-01-25 | |
721 | tag | available | 2019-03-26 | |
Thank you very much in advance :)
For getting all the cards having status_type 'available'
Card::join('statuses', 'cards.id_card', '=', 'statuses.id_card')
->where('statuses.status_type', 'available')
->select('cards.id_card', 'cards.card_type', 'statuses.status_type', 'statuses.status_date', 'statuses.status_company', 'statuses.status_user')
->get();
For getting latest status you can follow these steps,
Add following method in Card model
public function latestStatus()
{
return $this->hasOne('\App\Status')->latest();
}
Method latest() orders all rows by created_at desc, and takes the first one. If you don't have created_at column you can add it in statuses table.
$cards = Card::with('latestStatus')->get();
foreach ($cards as $card) {
echo $card->type . ' has latest status of ' . $card->latestStatus->status_type;
}
last but not the least, I think if you follow laravel conventions then you'll already have half of your problem solved.
Laravel is a framework that works better for you if you follow some conventions. The database models standards in Laravel in your case for Card and Status would be:
Then, you could simply have:
Status.php
use App\Card;
class Status extends Model
{
public function card()
{
return $this->hasOne(Card::class);
}
}
Card.php
use App\Status;
class Card extends Model
{
public function statuses()
{
return $this->hasMany(Status::class);
}
}
Finally, you could do:
$cards = App\Card::with(['statuses' => function($q){
$q->latest()->take(1);
}])->get();
Laravel will give out of the box a collection of Cards, with the last status attached.
To filter results based in related Status you could do:
$filter = 'available';
$cards = App\Card::with(['statuses' => function($q) use($filter) {
$q->where('status_type', $filter)->latest()->take(1);
}])->get();
Hope this helps to guide you in the right direction!
Firstly since you have a hasMany relationship, the naming should be plural.
public function Statuses() {
Defining an Accessor can help you on your status problem. In your Card.php model define it.
public function getStatusAttribute()
{
return $this->statuses->first();
}
Using the appends property on your Card.php model, to tell which properties should be serialized.
protected $appends = ['status'];
This will result in your model looking like so, when it is transformed to JSON.
{
"card_type": "tag",
"status": {
"id_card": 1,
"status_date": "2020-02-02",
"status_company": "Company C",
"status_user": "Third User"
}
}
If you are using it, in a Blade or PHP -context you can access like so.
$card->status; //your first status
For your query needs a simply where() clause combined with joining the table. For performance it would be smart to eager load statuses as you will access the first status, with() can be used for this.
Card::leftJoin('statuses', 'cards.id_card', 'statuses.id_card')
->where('statuses.status_type', 'available')
->select('cards.*')
->with('statuses')
->get();
Your naming of the columns is inconsistent with the Laravel naming convention and your data structure seems a little bit off, but this solves your problem, i would consider to rethink it.

Laravel hasManyThrough() returning no results

I have 3 tables and I want to join 2 of them through another using eloquent
ProductGroup table
--------------------------
| ProductGroupId | Name |
-------------------------
| 1 | Test1 |
| 2 | Test2 |
--------------------------
ProductLine table
-------------------------------------------------
| ProductLineId | ProductGroupId | Name |
------------------------------------------------|
| 1 | 1 | AnotherTest1 |
| 2 | 1 | AnotherTest2 |
-------------------------------------------------
ProductType table
----------------------------------------------
| ProductTypeId | ProductLineId | Name |
---------------------------------------------|
| 1 | 1 | Something1 |
| 2 | 1 | Something2 |
----------------------------------------------
I want to join ProductGroup with ProductType
I tried using this as my ProductGroup model (not sure if I've done the hasManyThrough() correctly)
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class ProductGroup extends Model
{
protected $primaryKey = 'ProductGroupId';
public $timestamps = false;
public function producttype()
{
return $this->hasManyThrough('App\ProductGroup', 'App\ProductLine', 'ProductGroupId', 'ProductGroupId');
}
}
I want to join the 2 tables on a particular id from the ProductGroup table so really in SQL it would be
SELECT * FROM ProductType pt
INNER JOIN ProductLine pl ON
pt.ProductLineId = pl.ProductLineId
INNER JOIN ProductGroup pg ON
pg.ProductGroupId = pl.ProductGroupId
WHERE pg.ProductGroupId = 3
I have tried this but I get no results
I could do this in query builder but i'd rather use eloquent if it can be helped
$test = ProductGroup::with('producttype')->whereHas('producttype', function ($query) {
$query->where('ProductGroup.ProductGroupId', 3);
})->get();
Change
public function producttype()
{
return $this->hasManyThrough('App\ProductGroup', 'App\ProductLine',
'ProductGroupId', 'ProductGroupId');
}
to
public function producttype()
{
return $this->hasManyThrough('App\ProductType', 'App\ProductLine', 'ProductGroupId', 'ProductLineId');
}

Get last price by user role?

How do I get the last price claimed by the retailer b#gmail.com (the 5th row in the price table) with the condition that the role = 'retailer' ?
So here is my simple table:
table users (using Entrust Role package with the default relationship in the model)
__________________________
| id | email | password |
|-------------------------|
| 1 | a#g.com | 123 |
| 2 | b#g.com | 123 |
| 3 c#g.com | 123 |
| 4 d#g.com | 123 |
--------------------------
table roles (using Entrust Role package with the default relationship in the model)
______________
|id | name |
|--------------|
|1 | customer |
|2 | retailer |
----------------
table role_user (using Entrust Role package with the default relationship in the model)
__________________
|id_user | id_role|
|------------------|
| 1 | 1 | -> a#gmail.com is a customer
| 2 | 2 | -> b#gmail.com is a retailer
| 3 | 1 | -> c#gmail.com is a customer
| 4 | 1 | -> d#gmail.com is a customer
------------------
Here is the tricky part how to query the price:
I have the following prices table (Users can post 1 or more prices. Look at the relationship below):
____________________
|id| user_id| price |
|--------------------|
|1 | 1 | 10.00 | -> price claimed by a customer a#gmail.com
|2 | 2 | 5.00 | -> price claimed by a retailer b#gmail.com
|3 | 1 | 6.00 | -> price claimed by a previous customer a#gmail.com
|4 | 3 | 5.00 | -> price claimed by a customer c#gmail.com
|5 | 2 | 7.00 | -> price claimed by a previous retailer b#gmail.com //How to get this one? This is the last price claimed by the retailer.
|6 | 3 | 8.00 | -> price claim by a customer c#gmail.com
---------------------
The relationship in my Price model:
class Price extends Model{
public function product()
{
return $this->belongsTo('App\Product');
}
public function user()
{
return $this->belongsTo('App\User');
}
How do I get the last price claimed by the retailer b#gmail.com (the 5th row in the price table) with the condition that the role = 'retailer' ?
The purpose is to get the last price that the retailer has claimed.
Update my question:
I want to access the price claimed by the last retailer from my Product model using the $products variable.
A sample table product that I have:
_______________________________
|id | user_id| name |
|------------------------------
| 1 | 1 | Milk |
| 2 | 2 | Phone |
| 3 | 1 | computer |
| 4 | 1 | Banana |
------------------------------
My Product.php model relationship:
class Product extends Model{
public function prices()
{
return $this->hasMany('App\Price');
}
}
So, in my ProductController.php I send the $product variable to the view like this:
class ProductController extends Controller
{
public function show($id)
{
$product = Product::where('id', '=', $id)->
return view('products.show')->with('product', $product);
}
}
and in my view show.blade.php, I loop through the $product variables and I can display the prices claimed for the product.
#foreach($product->prices as $price)
<li>Price claimed: {{$price->price. " " }} </li>
#endforeach
I want to get like something like
$price_last = $product->prices()->where(role, 'retailer')->last().
dd($price_last);
The last() function there is the last price that the retailer claimed but this code is just example. How do I achieve this?
Please let me know if you need more information.
You're looking for the whereHas method:
$query = Price::latest('id')->whereHas('user', function ($query) {
$query->whereHas('role', function ($query) {
$query->where('name', 'retailer');
});
});
$price = $query->value('price');
This assumes you've set up the relationships between your User, Role and Price models.