Dapper - What is the most efficient way to query Many-To-Many relationship? - sql-server

I am new to Dapper and trying to figure out how to query a Many-To-Many relationship.
I've looked around SO and Google but could not find an example.
I have a simple Many-To-Many scenario with 3 tables:
Albums table:
Artists table:
and the Many-To-Many table:
These are my POCOS:
public class Artist
{
public long Id { get; set; }
public string Name { get; set; }
}
public class Album
{
public long Id { get; set; }
public string Name { get; set; }
public List<Artist> Artists { get; set; }
}
Can someone provide the "correct" and efficient way to get a list of albums, and each album contains it's artists (the artists should have their Name property filled also) ?

I'm sure you must have found the solution by now. This could be helpful, may not be the neatest way to code it.
Is it possible to replicate Album and Artist classes?
If so, this is what i would do.
public List<Artist> GetAll()
{
using (SqlConnection conn = new SqlConnection(Conn.String))
{
conn.Open();
using (var multi = conn.QueryMultiple(StoredProcs.Artists.GetAll, commandType: CommandType.StoredProcedure))
{
var artists = multi.Read<Artist, AlbumArtist, Artist>((artist, albumArtist) =>
{
artist.albumArtist = albumArtist;
return artist;
}).ToList();
var albums = multi.Read<Album, AlbumArtist, Album>(
(album, albumArtist, album) =>
{
album.albumArtist = album;
return albums;
}).ToList();
conn.Close();
return artists;
}
}
}
Here's how the proc would look like
CREATE PROCEDURE [dbo].[YourProcName]
AS
BEGIN
SET NOCOUNT ON;
SELECT * from Artist a
left join AlbumArtist aa
on a.ArtistId = aa.ArtistId;
EXEC dbo.Album_GetAll
WITH RESULT SETS UNDEFINED;
END
Hope this helps.

Related

How to write a query to get data from two tables in Entity Framework

I have these tables with these relations :
https://i.stack.imgur.com/xUbeu.png
And I wrote these codes :
public class TestData
{
public int EmployeeId { get; set; }
public string Email { get; set; }
public string Password { get; set; }
public string FullName { get; set; }
public string Avatar { get; set; }
public bool IsActive { get; set; }
public List<int> Roles { get; set; }
}
public TestData GetData(string email)
{
var employee = _CarRentalContext.Employees.SingleOrDefault(w => w.Email == email);
List<int> Roles = _CarRentalContext.EmployeesRoles
.Where(w => w.EmployeeId == employee.EmployeeId)
.Select(s => s.RoleId).ToList();
return new TestData()
{
EmployeeId = employee.EmployeeId,
FullName=employee.FullName,
Email=employee.Email,
Password=employee.Password,
IsActive=employee.IsActive,
Avatar=employee.Avatar,
Roles = Roles,
};
}
Now what is the best way to write this function?
And if I want to get a list of RoleName instead of RoleId, what should this function look like?
EF defines entities for tables. Your schema has a many-to-many EmployeeRoles table for the association between Employees and Roles, so the entities should look something like this:
public class Employee
{
public int EmployeeId { get; set; }
// ...
public virtual ICollection<Role> Roles { get; set; } = new List<Role>();
// or
// public virtual ICollection<EmployeeRole> EmployeeRoles { get; set; } = new List<EmployeeRole>();
}
If Employee doesn't expose a collection/list of either Role or EmployeeRole then your team needs to read up on using Navigation Properties for relationships. For nearly all relationships like this there is no need to have DbSets in the DbContext for the joining EmployeeRole entity. To populate a TestData DTO you just would need:
var testdata = _CarRentalContext.Employees
.Where(w => w.Email == email)
.Select(w => new TestData
{
EmployeeId = w.EmployeeId,
FullName=w.FullName,
Email=w.Email,
Password=w.Password,
IsActive=w.IsActive,
Avatar=w.Avatar,
Roles = w.Roles.Select(r => new RoleData
{
RoleId = r.RoleId,
Name = r.Name
}).ToList()
}).SingleOrDefault();
If instead the Employee has a collection of EmployeeRoles then it's a little
uglier, replacing the inner Roles= with :
Roles = w.EmployeeRoles.Select(er => new RoleData
{
RoleId = er.Role.RoleId,
Name = er.Role.Name
}).ToList()
... to dive through the EmployeeRole to the Role.
Using Select like that is known as Projection and will let EF build a query to retrieve just the fields about the Employee and associated roles that you need to populate the details. Assuming you want both the ID and Name for each associated role, you would create a simple DTO (RoleData) and use Select within the Employee.Roles to populate from the Role entity/table.
And if I want to get a list of RoleName instead of RoleId, what should this function look like?
.Select(s => s.Role.RoleName)

Entity Framework auto assign FK to reference Entity during insertion?

Could you please explain for me Why and How EF auto assign FK to reference entity when i insert entities into Database? I got these simple Entities like this:
First one is Catalogue
public class Catalogue
{
public int CatalogueId { get; set; }
public string Name { get; set; }
public ICollection<Page> Pages { get; set; }
}
Second one is Page which reference to Catalogue.
public class Page
{
public int PageId { get; set; }
public string Name { get; set; }
public int CatalogueId { get; set; }
public Catalogue Catalogue { get; set; }
}
The relationship in this case is one to many. So in the code i am using this:
using (var context = new MyDbContext())
{
var catalogue = new Catalogue
{
Name = "catalogue 1"
};
var page = new Page
{
Name = "page 1",
CatalogueId = 0
};
context.Catalogues.Add(catalogue);
context.Pages.Add(page);
context.SaveChanges();
}
The MyDbContext is simple nothing special.
When i run this code i am expecting it will generate an error because CatalogueId = 0 is not valid, but it working fine,.
It is interesting me and hopefully someone can clarify that :).
Thanks in advance
This is how EF work under the hood. The context will go and execute the INSERT and generate the update for the FK value in the table. Later, will populate the tracked entity with the real key value.
You can experiment with unattached entities and will notice that no FK value is updated.

How do I return one-to-many records in a specific order with Dapper and multi-mapping?

From Github:
Dapper allows you to map a single row to multiple objects. This is a
key feature if you want to avoid extraneous querying and eager load
associations.
Example:
Consider 2 classes: Post and User
> class Post {
> public int Id { get; set; }
> public string Title { get; set; }
> public string Content { get; set; }
> public User Owner { get; set; } }
>
> class User {
> public int Id { get; set; }
> public string Name { get; set; } }
Now let us say that we want to map a query that joins both the posts
and the users table. Until now
if we needed to combine the result of 2 queries, we'd need a new
object to express it but it makes more sense in this case to put the
User object inside the Post object.
When I do this (My classes are different names, but same construct), I get a Post and a User, a Post and a User. I'm using the Web API, so this is all JSON, if that matters. This is the way I'd see it if I did straight SQL in the Management Studio, you get the many rows and the corresponding User records
What if I want to send back the JSON that has the User once and all the posts in an array, then the next User, array of posts, etc.
id title content id name
1 Article1 Content1 55 Smith
2 Article2 Content2 55 Smith
3 Article3 Content3 55 Smith
I get the JSON back that has the User information over and over (as expected but not wanted). It's backwards.
What I want is a JSON object that has a format like this (I think this is correct):
{
"User": 55,
"Name": "Smith",
"Post": [
{
"id": 1,
"title": "title1",
"content":"MyContent1"
},
{
"id": 2,
"title": "title2",
"content":"MyContent2"
},
{
"id": 3,
"title": "title3",
"content":"MyContent2"
}
]
}
How do I do this? Right now I get the reverse. I thought I would simply change the classes around, but I did not because of the instructions on Github, the "makes more sense" part. I am using this,
(List<Post>)db.Query<Post, User, Paper>(sqlString, (post, user) => { post.user = user; return post; }, splitOn: "id");
I know I don't need the splitOn here, but in my real query the name is different than id.
This is pretty close:
https://www.tritac.com/developers-blog/dapper-net-by-example/
public class Shop {
public int? Id {get;set;}
public string Name {get;set;}
public string Url {get;set;}
public IList<Account> Accounts {get;set;}
}
public class Account {
public int? Id {get;set;}
public string Name {get;set;}
public string Address {get;set;}
public string Country {get;set;}
public int ShopId {get;set;}
}
var lookup = new Dictionary<int, Shop>()
conn.Query<Shop, Account, Shop>(#"
SELECT s.*, a.*
FROM Shop s
INNER JOIN Account a ON s.ShopId = a.ShopId
", (s, a) => {
Shop shop;
if (!lookup.TryGetValue(s.Id, out shop)) {
lookup.Add(s.Id, shop = s);
}
if (shop.Accounts == null)
shop.Accounts = new List<Account>();
shop.Accounts.Add(a);
return shop;
},
).AsQueryable();
var resultList = lookup.Values;
It makes the first object identifier. Not sure if I can use it like that or not. But this does do the array of books like I was asking, and I did not have to create a special object. Originally, it was supposed to be on Google Code, but I couldn't find this test on Github.
Since your SQL query is returning the flat records, i suggest you create a flat POCO and use dapper to map the result set to a collection of this. Once you have data in this collection, you can use LINQ GroupBy method to group it the way you want.
Assuming you have classes like
public class User
{
public int Id { set;get;}
public string Name { set;get;}
public IEnumerable<Post> Posts { set;get;}
}
public class Post
{
public int Id { set;get;}
public string Title{ set;get;}
public string Content { set;get;}
}
Now create the POCO for the flat result set row
public class UserPost
{
public int Id { set; get; }
public string Title { set; get; }
public string Content { set; get; }
public int UserId { set; get; }
public string Name { set; get; }
}
Now update your SQL query to return a result set with column name matching the above properties.
Now use Dapper to get the flat records
var userposts= new List<UserPost>();
using (var conn = new SqlConnection("YourConnectionString"))
{
userposts = conn.Query<UserPost>(query).ToList();
}
Now apply GroupBy
var groupedPosts = userposts.GroupBy(f => f.UserId, posts => posts, (k, v) =>
new User()
{
UserId = k,
Name = v.FirstOrDefault().Name,
Posts = v.Select(f => new Post() { Id = f.Id,
Title= f.Title,
Content = f.Content})
}).ToList();
Another option is to use .QueryMultiple
[Test]
public void TestQueryMultiple()
{
const string sql = #"select UserId = 55, Name = 'John Doe'
select PostId = 1, Content = 'hello'
union all select PostId = 2, Content = 'world'";
var multi = _sqlConnection.QueryMultiple(sql);
var user = multi.Read<User>().Single();
user.Posts = multi.Read<Post>().ToList();
Assert.That(user.Posts.Count, Is.EqualTo(2));
Assert.That(user.Posts.First().Content, Is.EqualTo("hello"));
Assert.That(user.Posts.Last().Content, Is.EqualTo("world"));
}
Update:
To return multiple users and their posts:
[Test]
public void TestQueryMultiple2()
{
const string sql = #"select UserId = 55, Name = 'John Doe'
select UserId = 55, PostId = 1, Content = 'hello'
union all select UserId = 55, PostId = 2, Content = 'world'";
var multi = _sqlConnection.QueryMultiple(sql);
var users = multi.Read<User>().ToList();
var posts = multi.Read<Post>().ToList();
foreach (var user in users)
{
user.Posts.AddRange(posts.Where(x => x.UserId == user.UserId));
}
Assert.That(users.Count, Is.EqualTo(1));
Assert.That(users.First().Posts.First().Content, Is.EqualTo("hello"));
Assert.That(users.First().Posts.Last().Content, Is.EqualTo("world"));
}

Multi Mapping in Dapper. Receiving the error in SpiltOn

*Can You explain the Split on function in the multimap *
I am Trying to get the data from the Database using Dapper ORM. I have received the following error
System.ArgumentException : When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id
Parameter name: splitOn
public abstract class Domain
{
public Guid Id { get; set; }
}
public abstract class ItemBase : Domain
{
private IList<Image> images = new List<Image>();
public Guid? ParentId { get; set; }
public string Name { get; set; }
public IList<Image> Images { get { return images; } }
}
public class Meal : ItemBase
{
}
public class Item : ItemBase
{
private IList<Meal> meals = new List<Meal>();
public IList<Meal> Meals { get { return meals; } };
}
public class Image : Domain
{
public byte Img { get; set; }
public string Description { get; set; }
}
public class MealImageLink : Domain
{
public Guid ItemId { get; set; }
public Guid ImageId { get; set; }
}
/* search function to take dat from the table */
private List<Meal> SearchMeals(Guid id)
{
var query = #"SELECT meal.[Name],meal.[Description],meal.
[Price],mealImage.[Image] as Img
FROM [MealItems] as meal
LEFT JOIN [MealImageLink] mealImageLink
on meal.Id= mealImageLink.MealItemId
LEFT JOIN [Images] mealImage on
mealImageLink.ImageId=mealImage.Id
WHERE meal.[ParentId]=#Id";
List<Meal> meals = ( _connection.Query<Meal, MealImageLink, Image, Meal>
(query, (meal, mealLink, mealImage) =>
{
meal.Images.Add(mealImage);
return meal;
}, new { #Id = id })).ToList();
return meals;
}
The multi-map feature is really more intended for scenarios like:
select foo.Id, foo.whatever, ...,
bar.Id, bar.something, ...,
blap.Id, blap.yada, ...
from foo ...
inner join bar ...
left outer join blap ...
or the lazier but not uncommon:
select foo.*, bar.*, blap.*
from ...
inner join bar ...
left outer join blap ...
But in both of these cases, there is a clear and obvious way to split the horizontal range into partitions; basically, whenever you see a column called Id, it is the next block. The name Id is configurable for convenience, and can be a delimited list of columns for scenarios where each table has a different primary key name (so User might have UserId, etc).
Your scenario seems quite different to this. It looks like you're currently only selecting 4 columns with no particular way of splitting them apart. I would suggest that in this case, it is easier to populate your model via a different API - in particular, the dynamic API:
var meals = new List<Meal>();
foreach(var row in _connection.Query(sql, new { #Id = id }))
{
string name = row.Name, description = row.Description;
decimal price = row.Price;
// etc
Meal meal = // TODO: build a new Meal object from those pieces
meals.Add(meal);
}
The dynamic API is accessed simply by not specifying any <...>. With that done, columns are accessed by name, with their types implied by what they are being assigned to - hence things like:
decimal price = row.Price;
Note: if you want to consume the row data "inline", then just cast as soon as possible, i.e.
// bad: forces everything to use dynamic for too long
new Meal(row.Name, row.Description, row.Price);
// good: types are nailed down immediately
new Meal((string)row.Name, (string)row.Description, (decimal)row.Price);
Does that help?
Tl;dr: I just don't think multi-mapping is relevant to your query.
Edit: here's my best guess at what you intend to do - it simply isn't a good fit for multi-map:
var meals = new List<Meal>();
foreach (var row in _connection.Query(query, new { #Id = id })) {
meals.Add(new Meal {
Name = (string)row.Name,
Images = {
new Image {
Description = (string)row.Description,
Img = (byte)row.Img
}
}
});
}
return meals;

WCF RIA -joining two tables

I found a lot of explanations about this issue, but nothing that really helped me. The thing is simple. I have two tables on my dataModel: Events and TimeStamps, both have the field EntryID, which is the relation between them(the tables are in fact Views, I can't perform changes on DB, I can only query them).On my domainService, I have the created methods for getting data from each of the tables. So far, I am able to fill a dataGrid with data from only one of the tables, but what I really need is to display from both tables. In T-SQL it would be something like:
Select e.EntryID,t.closed_time
from Events e inner join TimeStamps t
on e.EntryID=t.EntryID
So I want to display on my dataGrid the Entry_ID and closed_time.I appreciate your help for solving my problem
I tried a new custom class
public class CustomTable
{
public string EntryId { get; set; }
public int closed_time { get; set; }
}
public IQueryable<CustomTable> GetJoined()
{
return (from i in this.ObjectContext.Events
join p in this.ObjectContext.TimeStamps p
on i.Entry_ID equals p.Entry_ID
select new CustomTable
{
EntryId = i.Entry_ID,
closed_Time = p.Closed_TIME
});
}
This is the additional code I added by myself, I'm pretty sure something is missing, this method and the class itself were added on my service.cs
This is the final code and procedures done, do not forget to build the project after each step:
1- Opened a new class under Myproject.Web(Add-->new item-->class)
namespace Myproject.Web
{
public class CustomTable
{
[Key]
public string EntryId { get; set; }
public int closed_Time { get; set; }
}
}
2-Added on IncidentService.cs:
public IQueryable<CustomTable> GetJoined()
{
return (from i in this.ObjectContext.Events
join p in this.ObjectContext.TimeStamps p
on i.Entry_ID equals p.Entry_ID
select new CustomTable
{
EntryId = i.Entry_ID,
closed_Time = p.Closed_TIME
});
}
3-Added on Mypage.xaml.cs
public MyPage()
{
InitializeComponent();
this.dataGrid1.ItemsSource = _IncidentContext.CustomTables;
_IncidentContext.Load(_IncidentContext.GetJoinedQuery());
DataGridTextColumn entry = new DataGridTextColumn();
entry.Binding = new System.Windows.Data.Binding("EntryId");
entry.Header = "Entry Id";
DataGridTextColumn closed = new DataGridTextColumn();
closed.Binding = new System.Windows.Data.Binding("closed_Time");
closed.Header = "Closed Time";
dataGrid1.Columns.Add(entry);
dataGrid1.Columns.Add(closed);
}
I hope this will help others with same issue, I spent 3 days working on this solution!!

Resources