Fluent API Many to Many Mapping Error - orm

I have the following mapping to support a many to many table (User_Role)
modelBuilder.Entity<Role>()
.HasMany<User>(u => u.users)
.WithMany(r => r.roles)
.Map(m =>
m.MapLeftKey("role_id")
m.MapRightKey("user_id")
m.ToTable("User_Role"));
This works great from mapping many roles to a user and many users to a role. The problem I am having is when I need to add a new row to the table User_Role via an entity as follows:
public class User_Role
{
[Key Column(Order=1)]
public int role_id {get;set;)
[Key Column(Order=1)]
public int user_id {get;set;)
}
Whenever I try to access this entity as follows:
dbContext.User_Role.Add(new User_Role {user_id ....
EF looks for a non existent table called User_Role1 ... its adding a '1' to the table name.
I then tried to add:
[Table("User_Role")]
This takes care of the adding a '1' but I now get this error:
"The EntitySet 'RoleUser' with schema 'dbo' and table 'User_Role' was already defined. Each EntitySet must refer to a unique schema and table"
I was able to confirm that the following lines together are causing the problem but I kind of need them both
m.ToTable("User_Role") and public class User_Role..
Any suggestions would be great...I am stumped.

You cannot represent the join table in a many-to-many relationship by an entity class in your model. This join table is managed by EF and you cannot directly access this table. If you want to create a relationship between an existing user and an existing role you must do this using these two entities:
var user = dbContext.Users.Single(u => u.id == user_id);
var role = dbContext.Roles.Single(r => r.id == role_id);
// if you don't have lazy loading and don't instantiate the collection
// in the constructor, add this: user.roles = new List<Role>();
user.roles.Add(role);
dbContext.SaveChanges();
This will write an entry for the new relationship (the (user_id,role_id) pair) into the join table.
Edit
If you only have the two key properties at hand and don't want to load the user and role from the database you can work with attached "stub entities":
var user = new User { id = user_id, roles = new List<Role>() };
var role = new Role { id = role_id };
dbContext.Users.Attach(user);
dbContext.Roles.Attach(role);
user.roles.Add(role);
dbContext.SaveChanges();

Related

Yii2 - hasMany relation with multiple columns

I have a table message_thread:
id
sender_id
recipient_id
I want to declare a relation in my User model that will fetch all message threads as follows:
SELECT *
FROM message_thread
WHERE sender_id = {user.id}
OR recipent_id = {user.id}
I have tried the following:
public function getMessageThreads()
{
return $this->hasMany(MessageThread::className(), ['sender_id' => 'id'])
->orWhere(['recipient_id' => 'id']);
}
But it generates an AND query. Does anyone know how to do this?
You cannot create regular relation in this way - Yii will not be able to map related records for eager loading, so it not supporting this. You can find some explanation int this answer and related issue on GitHub.
Depending on use case you may try two approach to get something similar:
1. Two regular relations and getter to simplify access
public function getSenderThreads() {
return $this->hasMany(MessageThread::className(), ['sender_id' => 'id']);
}
public function getRecipientThreads() {
return $this->hasMany(MessageThread::className(), ['recipient_id' => 'id']);
}
public function getMessageThreads() {
return array_merge($this->senderThreads, $this->recipientThreads);
}
In this way you have two separate relations for sender and recipient threads, so you can use them directly with joins or eager loading. But you also have getter which will return result ofboth relations, so you can access all threads by $model->messageThreads.
2. Fake relation
public function getMessageThreads()
{
$query = MessageThread::find()
->andWhere([
'or',
['sender_id' => $this->id],
['recipient_id' => $this->id],
]);
$query->multiple = true;
return $query;
}
This is not real relation. You will not be able to use it with eager loading or for joins, but it will fetch all user threads in one query and you still will be able to use it as regular active record relation - $model->getMessageThreads() will return ActiveQuery and $model->messageThreads array of models.
Why orOnCondition() will not work
orOnCondition() and andOnCondition() are for additional ON conditions which will always be appended to base relation condition using AND. So if you have relation defined like this:
$this->hasMany(MessageThread::className(), ['sender_id' => 'id'])
->orOnCondition(['recipient_id' => new Expression('id')])
->orOnCondition(['shared' => 1]);
It will generate condition like this:
sender_id = id AND (recipent_id = id OR shared = 1)
As you can see conditions defined by orOnCondition() are separated from condition from relation defined in hasMany() and they're always joined using AND.
For this query
SELECT *
FROM message_thread
WHERE sender_id = {user.id}
OR recipent_id = {user.id}
You Can use these
$query = (new \yii\db\Query)->from("message_thread")
$query->orFilterWhere(['sender_id'=>$user_id])->orFilterWhere(['recipent_id '=>$user_id]);

Returning related fields with Lamba LINQ entity

I have seen many questions related to how you can return a limited set of fields from a single EF entity with anonymous types. My issue is about the opposite of that. I want to return values from related tables along with all of the fields in my entity table:
IQueryable<EntityModels.TBLEFFORT> query = db.TBLEFFORT.AsQueryable();
query = query.Where(a => a.EFFDELETE == "0");
if (!string.IsNullOrEmpty(sGuid))
{
query = query.Where(a => a.TBLEFFORTLINK.Any(b => b.TBLSHS.TBLTACT.Any(c => c.CGUID == sGuid)));
}
query.Select(x => new EffortSearchResult()
{
Efguid = x.EFGUID,
Efstatus = x.EFSTATUS
});
Here my base entity is TBLEFFORT and i query directly against it on the EFFDELETE field and then i query against it with a related table TBLSHS.
However, in my anon return object i only have fields from TBLEFFORT (EFGUID and EFSTATUS). How can i include fields from the related TBLSHS entity in my anonymous return object?
The related tables are lookups so they will be 1:1. I'm not looking to return complex sets based on a FK field in TBLEFFORT
I figured it out. you just extend out in your anon type and dig for what you need from the related tables:
Shguid = x.TBLEFFORTLINK.Select(b => b.TBLSHS.SOMEFIELD).FirstOrDefault(),

Double Join Select

Three tables Project, Users, Issues.
Project table columns: p_id,name,...
Users table columns: u_id username...
Issues table columns: i_id i_name...
Relations:
Project has many Users - 1..*
Project has many Users - 1..*
Project has many Issues - 1..*
Users has many Issues - 1..*
What I want to do:
In Yii framework logic: Select Project with all it's users, these users has to have only Issues of the selected Project.
In tables logic: Select Issues of certain project AND user.
What sql code I want to mimic:
SELECT Issue.i_name FROM Issue Join Project on Issue.i_id =
Project.p_id Join User on Issue.i_id User.u_id
What I want to do in Yii:
//get Project
$model = Project::model()->findByPk( $p_id );
//get Project's users
$users = $model->users;
//get each of users issues of selected project
foreach( $users as $user )
$issues = $user->issues;
To solve this you have to use through in your ralations method.
Project model relations method should look like this:
public function relations()
{
return array(
'users' => array(self::MANY_MANY, 'User', 'tbl_project_user_assignment(project_id, user_id)'),
//'issues' => array(self::HAS_MANY, 'Issue', 'project_id'),
'issues' => array(self::HAS_MANY,'Issue',array('id'=>'owner_id'),'through'=>'users'),
'columns' => array(self::MANY_MANY, 'Column', 'tbl_project_rel_column(p_id,c_id)'),
);
}
Now in action select project, it's users and users's posts(or in my case issues) of selected project:
$project = Project::model()->with('users','issues')->findByPk(1);
$users = $project->users;
foreach($users as $user) {
echo $user->username."<br/>";
}
$issues = $project->issues;
foreach($issues as $issue) {
echo $issue->name."<br/>";
}

Fluent NHibernate Parent-child cascade SaveOrUpdate fails

I have a parent-child relationship that I've put a test case together between Users and Groups. I did this to replicate a failure in
a Parent-Child relationship when trying to perform a cacade insert using thes relationship.
The two SQL tables are as follows:
CREATE TABLE [dbo].[User]
(
[Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[Name] [varchar](50) NOT NULL,
)
CREATE TABLE [dbo].[Group]
(
[Id] [int] IDENTITY(1,1) NOT NULL PRIMARY KEY,
[GroupName] [varchar](50) NOT NULL,
[UserId] [int] NOT NULL,
)
ALTER TABLE [dbo].[Group] WITH CHECK ADD CONSTRAINT [FK_Group_User] FOREIGN KEY([UserId])
REFERENCES [dbo].[User] ([Id])
The objects represent these two tables with the following mappings:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("[User]");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.Name).Not.Nullable();
HasMany(x => x.Groups).KeyColumn("UserId").Cascade.SaveUpdate();
}
}
public class GroupMap : ClassMap<Group>
{
public GroupMap()
{
Table("[Group]");
Id(x => x.Id).GeneratedBy.Identity();
Map(x => x.GroupName).Not.Nullable();
References(x => x.User).Column("UserId").Not.Nullable();
}
}
The code to created the objects is simply:
User u = new User() { Name = "test" };
Group g = new Group() { GroupName = "Test Group" };
u.Groups.Add(g);
using (var session = factory.OpenSession())
{
session.SaveOrUpdate(u);
}
However it fails with exception "Cannot insert the value NULL into column 'UserId', table 'test.dbo.Group'; column does not allow nulls. INSERT fails.
The statement has been terminated". I suspect that this is dude to the parent object's Id (an identity column) being passed through as NULL and not the new values. Is this a bug or is there a way to fix these mappings so that this cascade relationship succeeds?
I recently had this exact type of mapping working fine in a project. My advice is:
Learn how the Inverse attribute of a HasMany relationship works. Great explanation here
You need a two way association between the parent and child object. This is explained at the bottom of the article linked to above.
Another good advice is to encapsulate your collections better
- Don't access your collections modification methods directly. The collection properties should be read-only and the parent (User class in your case) should have AddGroup() and RemoveGroup() methods that changes the private collection. In order for this to work you have to let NHibernate access the private collection member by using the .Access.CamelCaseField(Prefix.Underscore) or similar mapping attribute. Good discussion about it here
I can post an example mapping and class files if needed.
You will have to save the user first then assign the group to the user and save that:
using (var session = factory.OpenSession())
{
User u = new User() { Name = "test"};
session.SaveOrUpdate(u);
Group g = new Group() { GroupName = "Test Group", User = u };
session.SaveOrUpdate(g)
}
I have found that you cannot cascade save child /parent related objects which have only just been created.

Parent-child relationship with LINQ2SQL and POCO objects

I just started learning LINQ2SQL and one of the first things I wanted to try is a simple parent-child hierarchy, but I can't seem to find a good way to do it. I saw some examples here on SO and i've googled, but I couldn't apply them directly, so I'll explain exactly what i'm trying to accomplish.
Lets use the common example with tags.
Database tables: Post-- Post_Tags -- Tags
I've created a simple Post class so I avoid passing Linq2Sql classes around:
public class Post
{
public int Id {get; set;}
public int Title {get; set;}
public IEnumerable<string> Tags {get; set;}
}
I would like to select 5 latest records from the Posts table, get their related tags and return the IList where each Post has their Tags property filled.
Can you show me a concrete Linq2Sql code how could I do that?
I tried:
IList<Post> GetLatest()
{
return (from p in _db.Posts
orderby p.DateCreated descending
select new Post
{
Id = p.Id,
Title = p.Title,
Tags = p.Post_Tags.Select(pt => pt.Tag.Name)
}).Take(5).ToList();
}
This works but duplicates Post records for each Tag record and I have to duplicate property mapping (Id=p.Id, ...) in every method I user. I then tried this approach, but in this case, I have a roundtrip to DB for every tag:
IQueryable<Post> GetList()
{
return (from p in _db.Posts
select new Post
{
Id = p.Id,
Title = p.Title,
Tags = p.Post_Tags.Select(pt => pt.Tag.Name)
});
}
IList<Post> GetLatest()
{
return (from p in GetList()
orderby p.DateCreated descending
select p).Take(5).ToList();
}
If I were doing it in classic ADO.NET, I would create a stored procedure that returns two resultsets. One with Post records and second with related Tag records. I would then map them in the code (by hand, by DataRelation, ORM, etc.). Could I do the same with LINQ2SQL?
I'm really curious to see some code samples on how do you guys handle such simple hierarchies.
And yes, I would really like to return IList<> objects and my custom classes and not queryable Linq to Sql objects, because I would like to be flexible about the data access code if I for example decide to abandon Linq2Sql.
Thanks.
If you create a DataContext, the parent-child relationship is maintained automatically for you.
i.e. If you model the Posts and Tags and their relationship inside a Linq2Sql DataContext, you can then fetch posts like this:
var allPosts = from p in _db.Posts
orderby p.DateCreated descending
select p;
Then you won't have to worry about any tags at all, because they are accessible as a member of the variable p as in:
var allPostsList = allPosts.ToList();
var someTags = allPostsList[0].Post_Tags;
var moreTags = allPostsList[1].Post_Tags;
And then any repeated instance is then automatically updated across entire DataContext until you ask it to SubmitChanges();
IMO, That's the point of an ORM, you don't re-create the model class and maintain the mapping across many places because you want all those relationships managed for you by the ORM.
As for the roundtrip, if you refrain from any code that explicitly requests a trip to the database, all queries will be stored in an intermediate query representation and only when the data is actually needed to continue, is when the query will be translated to sql and dispatched to the database to fetch results.
i.e. the following code only access the database once
// these 3 variables are all in query form until otherwise needed
var allPosts = Posts.All();
var somePosts = allPosts.Where(p => p.Name.Contains("hello"));
var lesserPosts = somePosts.Where(p => p.Name.Contains("World"));
// calling "ToList" will force the query to be sent to the db
var result = lesserPosts.ToList();
How about if you set your DataLoadOptions first to explicitly load tags with posts? Something like:
IList<Post> GetLatest()
{
DataLoadOptions options = new DataLoadOptions();
options.LoadWith<Post>(post => post.Tags);
_db.LoadOptions = options;
return (from p in _db.Posts
orderby p.DateCreated descending)
Take(5).ToList();
}
List<Post> latestPosts = db.Posts
.OrderByDescending( p => p.DateCreated )
.Take(5)
.ToList();
// project the Posts to a List of IDs to send back in
List<int> postIDs = latestPosts
.Select(p => p.Id)
.ToList();
// fetch the strings and the ints used to connect
ILookup<int, string> tagNameLookup = db.Posts
.Where(p => postIDs.Contains(p.Id))
.SelectMany(p => p.Post_Tags)
.Select(pt => new {PostID = pt.PostID, TagName = pt.Tag.Name } )
.ToLookup(x => x.PostID, x => x.TagName);
//now form results
List<Post> results = latestPosts
.Select(p => new Post()
{
Id = p.Id,
Title = p.Title,
Tags = tagNameLookup[p.Id]
})
.ToList();