Querying many to many table in EF Core/LINQ [duplicate] - linq

This question already has answers here:
Many-to-many query in Entity Framework 7
(4 answers)
Closed 2 years ago.
I have three tables: Posts, Tags and PostTags (link table between Post and Tag). How can I write a query to get all Posts by a TagId?
DB structure:
public class Post {
public string Id {get;set;}
public string Content {get;set;}
public List<PostTag> PostTags {get;set;}
}
public class Tag {
public string Id {get;set;}
public string Name {get;set;}
public List<PostTag> PostTags {get;set;}
}
public class PostTag
{
public string PostId { get; set; }
public Post Post { get; set; }
public string TagId { get; set; }
public Tag Tag { get; set; }
}
Relationships:
builder.Entity<PostTag>()
.HasKey(x => new { x.PostId, x.TagId });
builder.Entity<PostTag>()
.HasOne(st => st.Post)
.WithMany(s => s.PostTags)
.HasForeignKey(st => st.PostId);
builder.Entity<PostTag>()
.HasOne(st => st.Tag)
.WithMany(s => s.PostTags)
.HasForeignKey(st => st.TagId);

If you've followed the entity framework code first conventions, there are two methods to query "Posts with their Tags"
The easy way: Use the virtual ICollection<Tag> to get the tags of each post.
Do the (group-)join yourself.
Use the irtual ICollection
Your classes will be similar to the following:
class Post
{
public int Id {get; set;}
... // other properties
// every Post has zero or more Tags (many-to-many)
public virtual ICollection<Tag> Tags {get; set;}
}
class Tag
{
public int Id {get; set;}
... // other properties
// every Tag is used by zero or more Posts (many-to-many)
public virtual ICollection<Post> Posts {get; set;}
}
This is all that entity framework needs to know the many-to-many relation between Posts and Tags. You even don't have to mention the junction table, entity framework will create a standard table for you, and use it whenever needed. Only if you want non-standard names for tables and or columns, you need Attributes or fluent API.
In entity framework, the columns of the tables are represented by the non-virtual properties; the virtual properties represent the relations between the tables (one-to-many, many-to-many, ...)
To get all (or some) Posts, each with all (or some of) their Tables, you can use the virtual ICollection:
var postsWithTheirTags = dbContext.Posts
// only if you don't want all Posts:
.Where(post => ...)
.Select(post => new
{
// Select only the Post properties that you plan to use:
Id = post.Id,
Author = post.Author,
...
Tags = post.Tags.Select(tag => new
{
// again: only the properties that you plan to use
Id = tag.Id,
Text = tag.Text,
...
})
.ToList(),
});
Entity framework knows your relation and will automatically create a Group-join for you using the proper junction table.
This solutions seems to me the most natural one.
Do the GroupJoin yourself
For this you need to have access to the junction table, you'll have to mention it in your dbContext, and use fluent API to tell entity framework that this is the junction table for the many-to-many relation between Posts and Tags.
var postsWithTheirTags = dbContext.Posts.GroupJoin(dbContext.PostTags,
post => post.Id, // from every Post take the primary key
postTag => postTag.PostId // from every PostTag take the foreign key to Post
(post, postTagsOfThisPost) => new
{
// Post properties:
Id = post.Id,
Title = post.Title,
...
Tags = dbContext.Tags.Join(postTagsOfThisPost,
tag => tag.Id // from every Tag take the primary key
postTag => postTag.TagId // from every postTagOfThisPost take the foreign key
(tag, postTagfThisPostAndThisTag) => new
{
Id = tag.Id,
Text = tag.Text,
...
})
.ToList(),
});

You can try this:
public List<Posts> GetPosts(string needTagID)
{
var dataQuery = from tags in _db.Tags
where needTagID == tags.Id
join postTags in _db.PostTags on tags.Id equals postTags.TagId
join posts in _db.Posts on postTags.PostId equals posts.Id
select posts;
var data = dataQuery.ToList();
}

Related

NHibernate Many-To-Many Performance Issue

My application has the following entities (with a many-to-many relationship between Product and Model):
public class TopProduct {
public virtual int Id { get; set; }
public virtual Product Product { get; set; }
public virtual int Order { get; set; }
}
public class Product {
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Model> Models { get; set; }
}
public class Model {
public virtual string ModelNumber { get; set; }
public virtual IList<Product> Products { get; set; }
}
Note: A product could have 1000s of models.
I need to display a list of TopProducts and the first 5 models (ordered alphabetically) against each one.
For example say I have the following query:
var topProducts = session.Query<TopProduct>()
.Cacheable()
.Fetch(tp => tp.Product).ThenFetchMany(p => p.Models)
.OrderBy(tp => tp.Order)
.ToList();
If I now say:
foreach (var topProduct in topProducts) {
var models = topProduct.Product.Models.Take(5).ToList();
...
}
This executes extremely slowly as it retrieves an item from the second level cache for each model. Since there could be 1000s of models against a product, it would need to retrieve 1000s of items from the cache the second time it is executed.
I have been racking my brain trying to think of a better way of doing this but so far I am out of ideas. Unfortunately my model and database cannot be modified at this stage.
I'd appreciate the help. Thanks
The key to your problem is understanding how entity and query caching work.
Entity caching stores, essentially, the POID of an entity and its property values.
When you want to get/initialize an instance, NH will first check the cache to see if the values are there, in order to avoid a db query.
Query caching, on the other hand, stores a query as the key (to simplify, let's say it's the command text and the parameter values), and a list of entity ids as the value (this is assuming your result is a list of entities, and not a projection)
When NH executes a cacheable query, it will see if the results are cached. If they are, it will load the proxies from those ids. Then, as you use them, it will initialize them one by one, either from the entity cache or from the db.
Collection cache is similar.
Usually, getting many second-level cache hits for those entity loads is a good thing. Unless, of course, you are using a distributed cache located in a separate machine, in which case this is almost as bad as getting them from the db.
If that is the case, I suggest you skip caching the query.

Map to view on custom field name - one-to-one relationship

I am unable to get NHibernate to map the AccountCode column of the Beneficiary table AccountCode column in this one-to-one relationship (each Account has a single Beneficiary, each Beneficiary has a single Account).
Classes:
public class Account
{
...
public virtual string Name { get; protected set; }
public virtual string Code { get; protected set; }
}
public class Beneficiary
{
...
public virtual int Id { get; set; }
public virtual string Name { get; protected set; }
public virtual Account Account { get; protected set; }
public virtual BeneficiaryGroup Group { get; protected set; }
}
SQL:
CREATE VIEW dbo.Account AS
SELECT DISTINCT RTRIM(LTRIM(ACCNT_NAME)) AS Name,
RTRIM(LTRIM(ACCNT_CODE)) AS Code
FROM myremoteserver.schema.tablename
WHERE ACCNT_TYPE NOT IN ('B', 'P')
CREATE TABLE dbo.Beneficiary
(
Id INT IDENTITY(1,1) NOT NULL,
BeneficiaryGroupId INT NOT NULL CONSTRAINT FK_Beneficiaries_BeneficiaryGroup FOREIGN KEY REFERENCES dbo.BeneficiaryGroup (Id),
Name VARCHAR(100) NOT NULL,
AccountCode VARCHAR(100) NOT NULL,
CONSTRAINT PK_Beneficiary PRIMARY KEY (Id)
)
When trying to use HasMany and different variants, NHibernate tries to join on the Beneficiary.Id column.
I had tried different variations of Map, References, Join (which tells me that the join already exists) and HasMany (which fails, as the relationship is indeed one-to-one).
How can I get NHibernate to map these two classes correctly to their columns?
When trying the different fluent mappings, in my IAutoMappingOverride<Beneficiary>, the following happens:
mapping.HasOne(b => b.Account);
mapping.HasOne(b => b.Account).PropertyRef(sa => sa.Code);
mapping.HasOne(b => b.Account).PropertyRef(sa => sa.Code).ForeignKey("none");
The generated SQL uses the Beneficiary.Id field instead of the Beneficiary.AccountCode. (before you ask, I am using "none" since Account is a view, it can't have a key).
mapping.Join("AccountCode", x => x.References(y => y.Account));
mapping.Join("Account", b => b.KeyColumn("AccountCode"));
Result in Tried to add join to table 'Account' when already added..
And:
mapping.Map(b => b.Account, "AccountCode");
mapping.Map(b => b.Account).ReadOnly().Column("AccountCode");
Result in:
Could not determine type for: .Account, , Version=1.0.0.0, Culture=neutral, PublicKeyToken=null, for columns:
NHibernate.Mapping.Column(AccountCode)
mapping.References(b => b.Account).Column("Code");
Results in Invalid column name 'Code'..
And:
mapping.References(b => b.Account).Column("AccountCode");
mapping.References(b => b.Account).Column("AccountCode").Access.Property();
Overrides all my IReferenceConvention overrides (mapping some classes to have a <class name>Code key column).
When trying HasMany:
mapping.HasMany<Account>(b => b.Account).KeyColumn("AccountCode");
Custom type does not implement UserCollectionType: .Account
// in BeneficiaryMapping
mapping.References(b => b.Account)
.Column("AccountCode" /* of Beneficiary table*/)
.PropertyRef(a => a.Code); // use the Column of Code as the joincolumn in Account table
This approach is similar to Firo's but in a simpler mapping. It uses References like he suggest because that's how NH allows you select a property other than the entity primary key property to reference another type. The PropertyRef is not necessary since NH "knows" its going to use the AccountCode value as the the "key" into the Account view.
Here is how to map Account:
public class AccountMap : ClassMap<Account>
{
public AccountMap()
{
// Code as a psuedo primary key in the view:
Id(acc => acc.Code)
.GeneratedBy.Assigned();
Map(acc => acc.Name);
// Add other mappings here...
// Ensure NH doesn't try to update the lookup view:
ReadOnly();
}
}
Here is how Beneficiary looks mapped:
public class BeneficiaryMap : ClassMap<Beneficiary>
{
public BeneficiaryMap()
{
Id(b => b.Id)
.GeneratedBy.Identity()
.UnsavedValue(0);
Map(b => b.Name);
// Other mapped properties...
References<BeneficiaryGroup>(b => b.Group, "BeneficiaryGroupId");
References<Account>(b => b.Account, "AccountCode");
}
}

How would I sort and display data in a tabular way with entities gathered from a Linq request?

How would I display data from the following database entity in a format that looks like a table:
public class Attendance
{
public int AttendanceID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
public int AttendanceDay { get; set; }
public bool Present { get; set; }
public virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
I would want to find all rows in the Attendance db entry that had CourseID == x; So I would use something like:
AttendanceData = Attendance.Where(s => s.CourseID == x); // I think
Then I would need to be able to sort this information in my view to display it in a way that makes sense. I would want to have a data on screen with all of the present/not present values sorted in a table with StudentIDs listed on the left and AttendanceDays listed accross the top.
How would I sort and display this information?
UPDATE:
Using the following code (along with Mvc WebGrid) - I can get a grid of some sort to appear in my view.
Controller:
IEnumerable<Attendance> model = db.Attendance.Where(s => s.CourseID == 4);
return View(model);
View:
#model IEnumerable<MyProject.Models.Attendance>
<div>
#{
var grid = new WebGrid(Model, defaultSort: "Name");
}
#grid.GetHtml()
</div>
However, the grid is not organized in a manner that is useful for my needs.
I want the top of my displayed table to read:
Day 1 | Day 2 | Day 3 | etc until the max value of "Attendance Day" (which is dictated at the creation of a Course that a student signs up for).
I want the left side of the displayed table to read:
Student ID 1
Student ID 5
Student ID 6
Student ID etc . . until all of the students within the data set have been displayed.
I think I need to use something along the lines of this in my controller:
var model = from s in db.Attendance
where s.CourseID == 4
group s.AttendanceDay by s.StudentID into t
select new
{
StudentID = t.Key,
Days = t.OrderBy(x => x)
};
return View(model);
But I need an IEnumerable<> returned to my view using Mvc WebGrid -- I am getting somewhere, just still a little lost along the way. Can I get a nudge in the right direction?
For a fairly typical set of requirements, it sounds like this would be a good candidate for the ASP.NET WebGrid. It's flexible, allows for paging, sorting, formatting, etc. I've used it for a few projects and works just like any of the other HTML helpers that you're probably used to in ASP.NET MVC.
Here's a good starting place.

Two models in one view in ASP MVC 3

I have 2 models:
public class Person
{
public int PersonID { get; set; }
public string PersonName { get; set; }
}
public class Order
{
public int OrderID { get; set; }
public int TotalSum { get; set; }
}
I want edit objects of BOTH classes in SINGLE view, so I need something like:
#model _try2models.Models.Person
#model _try2models.Models.Order
#using(Html.BeginForm())
{
#Html.EditorFor(x => x.PersonID)
#Html.EditorFor(x => x.PersonName)
#Html.EditorFor(x=>x.OrderID)
#Html.EditorFor(x => x.TotalSum)
}
This, of course, don't work: Only one 'model' statement is allowed in a .cshtml file. May be there is some workaround?
Create a parent view model that contains both models.
public class MainPageModel{
public Model1 Model1{get; set;}
public Model2 Model2{get; set;}
}
This way you can add additional models at a later date with very minimum effort.
To use the tuple you need to do the following, in the view change the model to:
#model Tuple<Person,Order>
to use #html methods you need to do the following i.e:
#Html.DisplayNameFor(tuple => tuple.Item1.PersonId)
or
#Html.ActionLink("Edit", "Edit", new { id=Model.Item1.Id }) |
Item1 indicates the first parameter passed to the Tuple method and you can use Item2 to access the second model and so on.
in your controller you need to create a variable of type Tuple and then pass it to the view:
public ActionResult Details(int id = 0)
{
Person person = db.Persons.Find(id);
if (person == null)
{
return HttpNotFound();
}
var tuple = new Tuple<Person, Order>(person,new Order());
return View(tuple);
}
Another example : Multiple models in a view
Another option which doesn't have the need to create a custom Model is to use a Tuple<>.
#model Tuple<Person,Order>
It's not as clean as creating a new class which contains both, as per Andi's answer, but it is viable.
If you are a fan of having very flat models, just to support the view, you should create a model specific to this particular view...
public class EditViewModel
public int PersonID { get; set; }
public string PersonName { get; set; }
public int OrderID { get; set; }
public int TotalSum { get; set; }
}
Many people use AutoMapper to map from their domain objects to their flat views.
The idea of the view model is that it just supports the view - nothing else. You have one per view to ensure that it only contains what is required for that view - not loads of properties that you want for other views.
ok, everyone is making sense and I took all the pieces and put them here to help newbies like myself that need beginning to end explanation.
You make your big class that holds 2 classes, as per #Andrew's answer.
public class teamBoards{
public Boards Boards{get; set;}
public Team Team{get; set;}
}
Then in your controller you fill the 2 models. Sometimes you only need to fill one. Then in the return, you reference the big model and it will take the 2 inside with it to the View.
TeamBoards teamBoards = new TeamBoards();
teamBoards.Boards = (from b in db.Boards
where b.TeamId == id
select b).ToList();
teamBoards.Team = (from t in db.Teams
where t.TeamId == id
select t).FirstOrDefault();
return View(teamBoards);
At the top of the View
#model yourNamespace.Models.teamBoards
Then load your inputs or displays referencing the big Models contents:
#Html.EditorFor(m => Model.Board.yourField)
#Html.ValidationMessageFor(m => Model.Board.yourField, "", new { #class = "text-danger-yellow" })
#Html.EditorFor(m => Model.Team.yourField)
#Html.ValidationMessageFor(m => Model.Team.yourField, "", new { #class = "text-danger-yellow" })
And. . . .back at the ranch, when the Post comes in, reference the Big Class:
public ActionResult ContactNewspaper(teamBoards teamboards)
and make use of what the model(s) returned:
string yourVariable = teamboards.Team.yourField;
Probably have some DataAnnotation Validation stuff in the class and probably put if(ModelState.IsValid) at the top of the save/edit block. . .
In fact there is a way to use two or more models on one view without wrapping them in a class that contains both.
Using Employee as an example model:
#model Employee
Is actually treated like.
#{ var Model = ViewBag.model as Employee; }
So the View(employee) method is setting your model to the ViewBag and then the ViewEngine is casting it.
This means that,
ViewBag.departments = GetListOfDepartments();
return View(employee);
Can be used like,
#model Employee
#{
var DepartmentModel = ViewBag.departments as List<Department>;
}
Essentially, you can use whatever is in your ViewBag as a "Model" because that's how it works anyway. I'm not saying that this is architecturally ideal, but it is possible.
Just create a single view Model with all the needed information in it, normaly what I do is create a model for every view so I can be specific on every view, either that or make a parent model and inherit it. OR make a model which includes both the views.
Personally I would just add them into one model but thats the way I do it:
public class xViewModel
{
public int PersonID { get; set; }
public string PersonName { get; set; }
public int OrderID { get; set; }
public int TotalSum { get; set; }
}
#model project.Models.Home.xViewModel
#using(Html.BeginForm())
{
#Html.EditorFor(x => x.PersonID)
#Html.EditorFor(x => x.PersonName)
#Html.EditorFor(x => x.OrderID)
#Html.EditorFor(x => x.TotalSum)
}
You can use the presentation pattern http://martinfowler.com/eaaDev/PresentationModel.html
This presentation "View" model can contain both Person and Order, this new
class can be the model your view references.
Another way that is never talked about is
Create a view in MSSQL with all the data you want to present. Then use LINQ to SQL or whatever to map it. In your controller return it to the view. Done.
you can't declare two model on one view, try to use Html.Action("Person", "[YourController]") & Html.Action("Order", "[YourController]").
Good luck.
Beside of one view model in asp.net you can also make multiple partial views and assign different model view to every view, for example:
#{
Layout = null;
}
#model Person;
<input type="text" asp-for="PersonID" />
<input type="text" asp-for="PersonName" />
then another partial view Model for order model
#{
Layout = null;
}
#model Order;
<input type="text" asp-for="OrderID" />
<input type="text" asp-for="TotalSum" />
then in your main view load both partial view by
<partial name="PersonPartialView" />
<partial name="OrderPartialView" />
I hope you find it helpfull !!
i use ViewBag For Project and Model for task so in this way i am using two model in single view and in controller i defined viewbag's value or data
List<tblproject> Plist = new List<tblproject>();
Plist = ps.getmanagerproject(c, id);
ViewBag.projectList = Plist.Select(x => new SelectListItem
{
Value = x.ProjectId.ToString(),
Text = x.Title
});
and in view tbltask and projectlist are my two diff models
#{
IEnumerable<SelectListItem> plist = ViewBag.projectList;
}
#model List

LINQ, how to specify to include items from List<>

Ok, I am trying to find get all subcategories from my database (using query result shaping) that belong to the category that I supply. My class SubCategory includes a List<> of Categories.
The problem is that in the linq statement, g is referring to SubCategory (which in the end contains Categories<>). So the statement below is not allowed.
How do I change the Linq statement to generate the correct SQL query to include all SubCategories that contain the matching Category.
public class SubCategory
{
public int SubCategoryId { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public List<Article> Articles { get; set; }
public List<Category> Categories { get; set; }
}
//incorrect code below:
var SubCategories = storeDB.SubCategories.Include("Categories").Single(g => g.Name == category);
This worked for me (maybe too simple):
var Category = storeDB.Categories.Include("SubCategories").Single(c => c.Name == category);
return Category.SubCategories;
I find it a bit confusing that each SubCategory can belong to more than one Category - have you got this relationship the right way around?
Regardless, I think its probably more readable if you change the select to work on Categories first - i.e. something like:
var subCatQuery = from cat in storeDB.Categories
where cat.Name == category
select cat.SubCategories;
which you can then execute to get your IEnumerable<>:
var subCategories = subCatQuery.ToList();
I find that much more readable/understandable.
(I also find the query syntax easier to read here than the fluent style)
my preferred answer would be to use a linq join. if there aren't but 2 data sets you can use the linq operations .join and .groupjoin vs a from statement. this would be my approach.
Dictionary<MainQuery, List<subQuery>> Query =
dbStore.List<MainQuery>.include("IncludeAllObjects")
.groupjoin(db.List<SubTable>.Include("moreSubQueryTables"),
mainQueryA=>mainQueryA.PropToJoinOn,
subQueryB => sunQueryB.PropToJoinOn,
((main, sub)=> new {main, sub})
.ToDictionary(x=>x.main, x=>x.sub.ToList());

Resources