Mapping fragments exception with Table-per-Hierarchy (Entity Framework 4) - sql-server

If i have SQL Server tables like this:
Location
----------
LocationId int PK
Field1 int
Field2 int
etc
Score
------------------------------------
LocationId int PK, FK
IsSingleLevel bit PK (discriminator)
Field1 int
Field2 int
etc
Technically this is mapped as a Location 1..0..* Score, but with the PK of LocationId/IsSingleLevel, it's a Location 1..0..2.
When i drag this on the EDMX, set everything up as expected (abstract entity, remove discriminator from base entity, etc).
EF give this error three times (one for base entity, and one for the two derived entities):
Error 6 Error 3025: Problem in mapping fragments starting at line 2146:Must specify mapping for all key properties (LocationScore.LocationId, LocationScore.IsSingleLevelScore) of table LocationScore.
I followed the example here.
The error occurs because i have the discriminator as part of the PK in the database, and discriminators are not mapped on the model, so i get the error.
I can't have LocationId as the only field in the PK, because then a location could only have 1 score, i need it to have two scores (one single level, one overall).
The end result is i want to be able to do this:
Locations.Include("LocationOverallScore").Single();
Locations.Include("LocationSingleLevelScore").Single();
Where LocationOverallScore and LocationSingleLevelScore are derived entities from the LocationScore base (abstract entity).
Is TPH not the right design here? Is my database design wrong? The aim is i don't want to have 2 physical tables for the different scores - as the table is huge, and i don't want to repeat the columns.
There's two possible workarounds i can think of:
1 - Make a view (LocationScore), which UNION's the two tables (so it would return 2 rows per LocationId) - but i still don't think i can "TPH" this. I don't want to manually perform the JOIN, i want to eager load.
2 - Add an IDENTITY column to Score, and this can be the PK.
Can you guys think of another solution?

I found a solution. I'm not 100% happy with it, but it does satisfy my requirement of not having two tables.
In the database side:
LocationScore:
LocationScoreId: IDENTITY, PK
LocationId: FK
Unique Index on LocationId/IsSingleLevelScore
I imported that into my EDMX - and included the LocationId FK (i never usually do this - but it's required in this instance).
Created the derived entities, mapped the fields, set the discriminator.
Created an association between Location -> LocationOverallScore, Location -> LocationScore (based on LocationId referential constraint).
All works fine.
The one downside is that because LocationId is not part of the PK on the LocationScore table, it's a 1..* between Location and LocationOverallScore, when in reality it should only be a 1..1.
I enforce this business requirement in the model via a hook property:
public class Location
{
// EF navigational properties - required
public ICollection<LocationOverallScore> LocationOverallScores { get; set; }
public ICollection<LocationSingleLevelScore> LocationSingleLevelScores { get; set; }
// Hook properties
public LocationOverallScore OverallScore
{
get { return LocationOverallScores.SingleOrDefault(); }
}
public LocationSingleLevelScores SingleLevelScore
{
get { return LocationSingleLevelScores .SingleOrDefault(); }
}
}
So the .SingleOrDefault() will throw an exception if there is more than one record - which there never will be, because of the unique index.
So i can now do this:
var location = ctx.Locations.Include("LocationOverallScores").Single();
var overallScore = location.OverallScore;
That's what i'll be going with for now.

Related

How to retrieve a model and it's relationship as a single Array or Object | Eloquent, Eloquent: Relationships

i have 3 tables with one-to-one relationship. The phone table has one to one relationship with Model table and Model table has a one to one relationship with Manufacturer table.
phone_table
id
imei
image
model_id
model_table
id
name
image
manufracturer_id
manufracturer_table
id
name
logo
how to get a result like this :-
App\Phone{
imei : "356554512522148",
model : "Galaxy S-10",
manufracturer : "Samsung",
}
I would never throw it into the same array / object, i would firstly do that on transformation. If you use default Laravel transformation you can use getters for it. Simple example on how to access these fields into the same context would be.
$phone = Phone::with('model.manufactor')->find(1);
With secures the queries are optimal for accessing it. How to get data into same layer.
[
'imei' => $phone->imei,
'model' => $phone->model->name,
'manufactor' => $phone->model->manufactor->name,
]
For this to work, you need relations in your model too.
Phone.php
public function model()
{
return $this->belongsTo(Model::class);
}
Model.php
public function manufactor()
{
return $this->belongsTo(Manufactor::class);
}
Just join them:
\App\Phone::leftjoin('model_table AS mo', 'mo.id', '=','phone_table.model_id')
->leftjoin('manufracturer_table AS ma', 'ma.id', '='. 'mo.manufracturer_id')
->selectRaw('phone_table.imei, mo.name AS model, ma.name AS manufracturer')
->first()
And sometimes you need to think about why you want to split table to one-to-one relationship.
Is there a table not usually be used, or one of them need to be connected by another tables. is this just for saving space or reduce IO cost.
If there are not any other reason and you always need to get these tables' information, maybe you can merge to one table.

Entity Framework returns incorrect data from SQL Server view

I have a SQL Server database that contains a view.
When I execute this query:
select *
from HistoryListingView
order by RequestTime desc`
I obtain this result:
But, in my controller (in an ASP.NET MVC application), I only perform this code:
return Ok(_db.HistoryListingViews.OrderByDescending(r => r.RequestTime));
And the data received doesn't correspond; more precisely the column DataType is incorrect in row 2 and 4, all others columns seem to be correct.
[
{
"dataType":"Type de stationnement",
"actionType":"Obtenir plusieurs entrées",
"requestTime":"2017-10-26T23:06:43.81",
"username":"admin",
"jsonParameters":"[]",
"error":null,
"userSessionRequestErrorId":null
},
{
"dataType":"Type de stationnement",
"actionType":"Obtenir plusieurs entrées",
"requestTime":"2017-10-26T23:06:43.81",
"username":"admin",
"jsonParameters":"[]",
"error":null,
"userSessionRequestErrorId":null
},
{
"dataType":"Local",
"actionType":"Obtenir plusieurs entrées",
"requestTime":"2017-10-26T23:06:42.687",
"username":"admin",
"jsonParameters":"[]",
"error":null,
"userSessionRequestErrorId":null
},
{
"dataType":"Local",
"actionType":"Obtenir plusieurs entrées",
"requestTime":"2017-10-26T23:06:42.687",
"username":"admin",
"jsonParameters":"[]",
"error":null,
"userSessionRequestErrorId":null
}
]
My context is configured with disabled lazyLoading and disabled ProxyCreation, and my JsonFormatter (Newtonsoft) is using basic settings:
CamelCasePropertyNamesContractResolver
and
ReferenceLoopHandling.Ignore
I can't see what can change the data. Is there something like records recycle to get better performance?
There is a subtle problem with views when used from Entity Framework.
If you have a table, do use it with EF, you need to have a primary key to uniquely identify each row. Typically, that's a single column, e.g. an ID or something like that.
With a view, you don't have the concept of a "primary key" - the view just contains some columns from some tables.
So when EF maps a view, it cannot find a primary key - and therefore, it will use all non-nullable columns from the view as "substitute" primary key.
I don't know what these are in your case - you should be able to tell from the .edmx model, or from the code class generated from the database.
Looking at your data, I assume that RequestTime is the only non-nullable column in your view. EF will now assume this is the "substitute" primary key" for this view. When EF goes to read the data, it will read the first line ("Type de stationnement") and create an object for that.
When EF reads the second line, the RequestTime is identical and therefore the substitute "primary key" (the RequestTime) is the same as before - so it doesn't bother creating a new object with those values read, but the primary key is the same, it hence must be the same object as it has already read before, so it uses that object instead.
So the problem really is that you can't have explicit primary keys on a view.
Either you can tweak your EF model to make it clear to EF what the primary key is (you need to make sure those columns are non-nullable) - or you need to add something like a "artificial" primary key to your view:
CREATE VIEW dbo.HistoryListingView
AS
SELECT
(all the columns you already have in your view),
RowNum = ROW_NUMBER() OVER(ORDER BY SomeValue)
FROM
dbo.YourBaseTable
By adding this RowNum column to your view, which just numbers the rows 1, 2, ...., n, you get a new, non-nullable column which EF will include into the "substitute PK" and since those numbers are sequential, no two rows will have the same "PK" values and therefore none will erroneously be replaced by something that's been read from the database already.
Thanks for #marc_s answer but
This didn't work for me as RowNum is still nullable
however when I added ISNULL(ROW_NUMBER() OVER(ORDER BY SomeValue)) worked

Using proper relations for product and attribute

I am about to implement a database for simple ecommerce platform. I want to implement the following:
Each product belongs to one product category;
Each product category has its own attributes;
Each product has one value for each attribute of this products type.
What relations should I use to store this kind of information?
Here is the logical model -- the way I understood it; you should be able to tweak it.
From this you can derive the physical model and the SQL code. The word KEY here means UNIQUE NOT NULL and you may use them for primary keys. Should you choose to introduce integers as primary keys, make sure you keep these UNIQUE.
Note that everything should be NOT NULL, once you get to the SQL.
Category named (CAT) exists.
Category {CAT}
KEY {CAT}
Attribute named (ATR) exists.
Attribute {ATR}
KEY {ATR}
Category (CAT) has attribute (ATR).
Each category has more than one attribute, it is possible for the same attribute to belong to more than one category.
CategoryAttribute {CAT, ATR}
KEY {CAT, ATR}
Product named (PRD) belongs to category (CAT).
Each product belongs to exactly one category, each category may have more than one product.
ProductCategory {PRD, CAT}
KEY {PRD}
KEY {PRD, CAT} -- seems redundant here, but is
-- needed for the FK from the next table
FOREIGN KEY {CAT} REFERENCES Category {CAT}
Product (PRD) from category (CAT) has attribute (ATR) that belongs to that category.
For each attribute that belongs to a category, that attribute may belong to more than one product from that category.
ProductCategoryAttribute {PRD, CAT, ATR}
KEY {PRD, CAT, ATR}
FOREIGN KEY {PRD, CAT} REFERENCES ProductCategory {PRD, CAT}
FOREIGN KEY {CAT, ATR} REFERENCES CategoryAttribute {CAT, ATR}
I don't know what database platform you are using, but for small numbers of products, and for queries that do not depend on the value of the per-category attributes, I'd use the following strategy:
CREATE TABLE "Category" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT
);
CREATE TABLE "Product" (
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
"categoryId" INTEGER NOT NULL REFERENCES "Category" ("id"),
"attributes" TEXT NOT NULL
);
In this example, the categories are used mainly to enforce referential integrity and to provide a list of categories for navigation.
The attributes are stored inside the attributes column as JSON (most modern databases tend to support this natively).
If there are any attributes common to all types of products, we'd create specific columns in Product. For example, you could add creationDate, deletionDate, price, or whatnot.
This allows you to perform the typical Select * From Product Where id = #Id to get a specific product and Select * From Product Where categoryId = #CategoryId to get all products in a category.
A creationDate could be useful to sort the products by creation date and take the top N, if necessary, when filtering by category. However with small quantities like thousands of products you might as well get all products by category and do this in code.
Regarding the code aspect, products like Dapper have specific extensions helping you deal with these discriminated unions, but writing code to support it is fairly easy. Here's an how. I'll write pseudo-C#, but I'm sure you can adapt.
We have an abstract class taking care of the Product table rows
public abstract class ProductBase
{
// only the fields in the Product table here
public int CategoryId { get; set; }
protected string Attributes { get; set; }
// serialize extra fields to JSON in Attributes
protected abstract void Prepare();
// load the common fields from a data row
protected static ProductBase(DataRow dr)
{
CategoryId = int.Parse(dr["categoryId"]);
Attributes = dr["attributes"] as string;
}
// save to DB
public void Save()
{
Prepare();
// save to SQL
}
}
We also have specific classes per category which have the extra attributes and handle serialization and deserialization.
public class FooProduct: ProductBase
{
public string Color { get; set; }
protected override void Prepare()
{
Attributes = Json.Serialize(new { Color });
}
public FooProduct(DataRow dr): base(dr)
{
// we can only create foo products if the category is foo
if (CategoryId != 23) throw new InvalidOperationException();
var attr = Json.Deserialize(Attributes);
Color = attr.Color;
}
}
This idea works great while you don't need to get the "foo" products by Color. If you can afford to get all "foo" products and filter in code, great. If your database understands JSON and lets you query inside the Attributes field, good it will get slow with large numbers unless the server allows indexes to reference JSON-serialized values.
If all else fails, you'll need to create an index table which contains the color values and the ids of the products which have that color. This is relatively painful and you don't want to do it unless you need it (and you don't right now).

Retrieving data from referenced key table - Laravel-4

The structure of concerning tables is as follows (MySQL):
//Table Name : team
tid PK
team_name (varchar)
//Table Name : fixture
fid PK
home_team_id FK |_ both referenced to 'tid' from 'team' table
away_team_id FK |
My aim is to retrieve the team names. Considering this structure, I think I'll have to retrieve home_team_id and away_team_id and then do something like
Fixture::where('tid','=',$home_team_id)->get();
My question is, is this the correct way to accomplish what I aim to do?
and
should this be done from the controller? (if so, then I'll have to do two queries from same function)
First, rather than having your primary keys be tid and fid, just keep them both as id. This is not only best practice, but will allow you to more easily use Laravel's Eloquent ORM as it by default assumes your primary key column is named id.
Second thing, make sure your table names are in plural form. Although this is not necessary, the example I'm about to give is using Laravel defaults, and Laravel assumes they are in plural form.
Anyway, once you've 'Laravelized' your database, you can use an Eloquent model to setup awesome relationships with very minimal work. Here's what I think you'd want to do.
app/models/Team.php
class Team extends Eloquent {
// Yes, this can be empty. It just needs to be declared.
}
app/models/Fixture.php
class Fixture extends Eloquent {
public function homeTeam()
{
return $this->belongsTo('Team', 'home_team_id');
}
public function awayTeam()
{
return $this->belongsTo('Team', 'away_team_id');
}
}
Above, we created a simple model Team which Laravel will automatically look for in the teams database table.
Second, we created model Fixture which again, Laravel will use the fixtures table for. In this model, we specified two relationships. The belongsTo relationship takes two parameters, what model it is related to, in both cases here they are teams, and what the column name is.
Laravel will automatically take the value in away_team_id and search it against the id column in your teams table.
With just this minimal amount of code, you can then do things like this.
$fixture = Fixture::find(1); // Retrieves the fixture with and id of 1.
$awayTeam = $fixture->awayTeam()->first(); // var_dump this to see what you get.
$homeTeam = $fixutre->homeTeam()->first();
Then you can proceed as normal and access the column names for the tables. So say you have a 'name' column in the teams table. You can echo out the the home team name from the fixture like so.
$fixture = Fixture::find(1); // Get the fixture.
echo $fixture->homeTeam->name;
It's nearly 2AM, so there might be an error or two above, but it should work.
Make sure you check the docs for Eloquent, especially the bits relating to relationships. Remember to name your columns and tables in the way Laravel wants you to. If you don't, there are ways to specify your custom names.
If you want to get even more fancy, you can define the inverse relationship like this on your Team model.
app/models/Team.php
class Team extends Eloquent {
public function fixturesAtHome()
{
return $this->hasMany('Fixture', 'home_team_id');
}
public function fixturesAway()
{
return $this->hasMany('Fixture', 'away_team_id');
}
}
Then to get all of a particular team's home fixtures...
$team = Team::find(1); // Retreive team with id of 1;
$homeFixtures = $team->fixturesAtHome();

One To Many and Duplicate entry

I use JPA->Hibernate. PlayFramework. I want to have relationship.
Category - 1:n -> Tag
Every category can have many tags, but tags do not know about it.
So, i do like this:
#Entity
public class Category ... {
#OneToMany
public List<Tag> tags = new LinkedList<Tag>();
}
I have test:
#Test
public void playWithTags() {
Tag tag1 = new Tag("tag1").save(); // managed by playframework
Category cat1 = new Category("cat1");
cat1.tags.add(tag1);
cat1.save();
// check if tag1 and cat1 were saved
assertEquals(1, Tag.count());
assertEquals(1, Category.count());
Category cat2 = new Category("cat2");
cat2.tags.add(tag1);
cat2.save();
}
The result is:
16:18:01,555 ERROR ~ Duplicate entry '1' for key 'tags_id'
16:18:01,555 ERROR ~ Could not synchronize database state with session
org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:96)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelp
....
java:908)
at java.lang.Thread.run(Thread.java:619)
Caused by: java.sql.BatchUpdateException: Duplicate entry '1' for key 'tags_id'
at com.mysql.jdbc.PreparedStatement.executeBatchSerially(PreparedStatement.java:2020)
It seems that cat2.save() try to do more then it should
If if use merge() instead of save() it works good:
cat2.merge();
BUT WHY?
I have fixed the problem. The problem was in that, that I used NOT THAT annotation. So i just changed #OneToMany to #ManyToMany and voilà - No any restrictions anymore.
But if saying about the OneToMany then it seems there was a unique-restriction on database-level which prevented us to put not-unique values to tags_id. Therefore we could not put same tag to One category. I.e. it wanted One category for Many tags, but if tags were already 'used' - no way.. I tried to put unique=true/false in #JoinTable -> #JoinColumn - but it does not help. For me it's still strange, but at least current problem was fixed.
You're mixing up two concepts: Primary key and foreign key.
There can be only one PK but FK just means "there must be an element with this ID in some other table". FK doesn't constrain uniqueness.
[EDIT] Your problem is that you're mixing entities. How did you get the tag1 which is returned by save()?
This entity must be one which you get from Hibernate, not the result from new. Even if it looks insane, you must do this in save():
session.save(tag);
return session.load(tag.getId());
This way, you get an entity that is managed by Hibernate. Only when the entity is managed by Hibernate, Hibernate knows when it has to save the entity and when it has already been saved.
So when you do cat2.tags.add(tag1); in your example above, Hibernate thinks "oh, I don't know anything about this tag, it must be a new one".
And tries to save the tag again.

Resources