I am new to Linq to Entity and here is my test scenario:
There are Users
Users have Photo Albums. An album belongs to only one User
Albums have Photos. A Photo belongs to only one Album
Photos have Tags. A Tag may belong to many Photos
I want to write a method which displays Count and Name of Tags used in Albums of a particular User using Linq.
Here is the method that I wrote and it works fine
public static void TestStatistics(int userId)
{
// select AlbumTitle, TagName, TagCount where UserId = userId
var results = from photo in dbSet.PhotoSet
join album in dbSet.AlbumSet on photo.AlbumId equals album.Id into albumSet
from alb in albumSet
where alb.UserId == userId
join photoTag in dbSet.PhotoTagSet on photo.Id equals photoTag.PhotoId into photoTagSet
from pt in photoTagSet
join tag in dbSet.TagSet on pt.TagId equals tag.Id
group new { alb, tag } by new { alb.Title, tag.Name }
into resultSet
orderby resultSet.Key.Name
select new
{
AlbumTitle = resultSet.Key.Title,
TagName = resultSet.Key.Name,
TagCount = resultSet.Count()
};
foreach (var item in results)
{
Console.WriteLine(item.AlbumTitle + "\t" + item.TagName + "\t" + item.TagCount);
}
}
And this is the standart T-SQL query which does the same
SELECT a.Title AS AlbumTitle, t.Name AS TagName , COUNT(t.Name) AS TagCount
FROM TblPhoto p, TblAlbum a, TblTag t, TblPhotoTag pt
WHERE p.Id = pt.PhotoId AND t.Id = pt.TagId AND p.AlbumId = a.Id AND a.UserId = 1
GROUP BY a.Title, t.Name
ORDER BY t.Name
It is pretty obvious that standard T-SQL query is much simpler than the Linq query.
I know Linq does not supposed to be simpler than T-SQL but this complexity difference makes me think that I am doing something terribly wrong. Besides the SQL query generated by Linq is extremly complex.
Is there any way to make the Linq query simpler?
UPDATE 1:
I made it a little simpler without using joins but using a approach like used in T-SQL. Actually it is now as simple as T-SQL. Still no navigation properties and no relations on db.
var results = from photo in dbSet.PhotoSet
from album in dbSet.AlbumSet
from photoTag in dbSet.PhotoTagSet
from tag in dbSet.TagSet
where photo.AlbumId == album.Id && photo.Id == photoTag.PhotoId &&
tag.Id == photoTag.TagId && album.UserId == userId
group new { album, tag } by new { album.Title, tag.Name } into resultSet
orderby resultSet.Key.Name
select new {
AlbumTitle = resultSet.Key.Title,
TagName = resultSet.Key.Name,
TagCount = resultSet.Count()
};
If every photo has at least one tag , then try
var results = (from r in PhotoTag
where r.Photo.Album.UserID == userId
group r by new { r.Photo.Album.Title, r.Tag.Name } into resultsSet
orderby resultsSet.Key.Name
select new
{
AlbumTitle = resultsSet.Key.Title ,
TagName = resultsSet.Key.Name ,
TagCount = resultsSet.Count()
}
);
First things first, you need to setup foreignkeys in your database then rebuild EF and it will 'know' (i.e. navigation properties) about the relationships, which then allows you to omit all of your joins and use something along the lines of the following:
List<AlbumTag> query = (from ps in dbSet.PhotoSet
where ps.Album.UserId = userId
group new { album, tag } by new { ps.Album.Title, ps.PhotoTag.Tag.Name } into resultSet
orderby resultSet.Key.Name
select new AlbumTag()
{
AlbumTitle = resultSet.Key.Title,
TagName = resultSet.Key.Name,
TagCount = resultSet.Count()
}).ToList();
Related
I have C# MVC API URL localhost/api/APIValues?Name=Nick. All working but only issue is when I typed Name=nick it won't display result. because my database table name field store Nick. Also my Database table name field has some data example Nick, ANN, tristan, Abbott,BUD. How do I remove string(Name) case sensitive MVC API values?
Example, how do I setup both way work localhost/api/APIValues?Name=Nick and localhost/api/APIValues?Name=nick.
This is my C# code.
public IEnumerable<NameDTO> Get(string Name = "")
{
var nameList = (from o in db.People.AsEnumerable()
where o.name == Name
join s in db.Employee on
o.empID equals s.empID
select new
{
s.empID,
o.Id
}).ToList();
}
My finally out put should work both name "Nick or nick"
localhost/api/APIValues?Name=Nick
localhost/api/APIValues?Name=nick
You can use Equals with StringComparison:
public IEnumerable<NameDTO> Get(string Name = "")
{
var nameList = (from o in db.People.AsEnumerable()
where o.name.Equals(Name, StringComparison.OrdinalIgnoreCase)
join s in db.Employee on
o.empID equals s.empID
select new
{
s.empID,
o.Id
}).ToList();
}
Try this, I think it may help you:
// Use if you want same records with name you provide
public List<NameDTO> Get(string Name = "")
{
var nameList = (from o in db.People.AsEnumerable()
where o.name.Trim().ToLower() == Name.Trim().ToLower()
join s in db.Employee on
o.empID equals s.empID
select new NameDTO()
{
EmpId = s.empID,
Id = o.Id
}).ToList();
}
//use this if you want similar records from database
public IEnumerable<NameDTO> Get(string Name = "")
{
var nameList = (from o in db.People.AsEnumerable()
where o.name.Trim().ToLower().Contains(Name.Trim().ToLower())
join s in db.Employee on
o.empID equals s.empID
select new NameDTO()
{
EmpId = s.empID,
Id = o.Id
}).ToList();
}
}
Made it as simple as it can get. Whenever my query's don't work in a single line it's my preference to break it down into several components. Feel happy to write a one liner though.
var nameList= db.People.AsEnumerable();
People people = new People();
foreach (var x in nameList)
{
var result = x.name.ToLower() == Name.ToLower();
if (result)
{
people = x;
}
}
var Employee = db.Employee.FirstOrDefault(e => e.EmpId == people.EmpId);
NameDTO nameDTO = new NameDTO()
{
EmpId = Employee.EmpId,
Id = People.Id
};
SQL is not case sensitive. And as long as yo're using a library that converts your code to SQL (such as EF) this shouldn't be an issue.
var nameList = (from o in db.People
where o.name == Name
join s in db.Employee on
o.empID equals s.empID
select new
{
s.empID,
o.Id
}).ToList();
The problem is that you're using AsEnumerable() which actually executes the query and then compares the objects in memory instead of comparing in DB. Watch it in the SQL Profiler and you will see the difference.
I have a SQL Query that I'm having trouble converting to LINQ query:
SELECT DISTINCT Nodes.NodeName, NodeConfig.IPAddresses, NodeConfig.InSCOM, NodeConfig.InOrion, NodeConfig.OrionCustomerName, NodeConfig.OrionApplication, NodeConfig.NodeID
FROM Tags INNER JOIN
TagToNode ON Tags.TagID = TagToNode.TagID RIGHT OUTER JOIN
NodeConfig INNER JOIN
Nodes ON NodeConfig.NodeID = Nodes.NodeID ON TagToNode.NodeID = NodeConfig.NodeID
WHERE (NodeConfig.Session = '7/3/2014 1:46:33 PM') AND (NodeConfig.InSCOM = 0)
That returns 1076 rows.
I tried to write the LINQ equivalent:
var list1 = (from t in mldb.Tags
join tn in mldb.TagToNodes on t.TagID equals tn.TagID into tagJoin
from tj in tagJoin.DefaultIfEmpty()
join nc in mldb.NodeConfigs on tj.NodeID equals nc.NodeID
join n in mldb.Nodes on nc.NodeID equals n.NodeID
where (nc.Session == #"7/3/2014 1:46:33 PM") && (nc.InSCOM == 0)
select new { Customer = nc.OrionCustomerName, DeviceName = n.NodeName, DeviceType = nc.OrionApplication, IPAddress = nc.IPAddresses, NodeID = n.NodeID }).Distinct().ToList();
That returns 183 rows.
I have tried converting the query to inner joins as suggested by some when I searched for solutions on this site. The original query implements a SQL "RIGHT OUTER JOIN" which from what I've read left/right isn't supported in LINQ but joins can be done.
The tables that I'm pulling from have primary keys as follows:
[DataServiceKey(new string[] { "NodeID", "TagID" })]
public partial class TagToNode { }
[DataServiceKey(new string[] { "NodeID" })]
public partial class Node { }
[DataServiceKey(new string[] { "TagID" })]
public partial class Tag { }
[DataServiceKey(new string[] { "ConfigID" })]
public partial class NodeConfig { }
The relationship is that Nodes have many NodeConfigs, and many Nodes are Tagged with many tags.
Can someone help me with the query logic?
OK so I took a step back and restructured the query. I created an inner join first, and then left-joined that to the main query.
I have a list of computers in a data base that are "tagged" in my application. I wanted to be able to search the tag names in the database via the many-to-many relationship between a tag and a device, where I have a join table in the middle named "TagToNode".
The distinct selection just weeds out the dupes in the end, the idea is to get ALL of the computers (nodes) even if they're not tagged with anything.
LINQ
var tags = (from tn in mldb.TagToNodes
join t in mldb.Tags on tn.TagID equals t.TagID
select new { tn.TagID, tn.NodeID, t.TagName, t.AssocUser });
return (from nc in mldb.NodeConfigs
join n in mldb.Nodes on nc.NodeID equals n.NodeID
join t in tags on n.NodeID equals t.NodeID into nj
from tg in nj.DefaultIfEmpty()
where nc.Session == sc.SessionName && n.NodeActive == 1 && ((tg.TagName.Contains(sc.SearchTerm) && (tg.AssocUser.Contains(windowsId) || (tg.AssocUser == null || tg.AssocUser == ""))) || (n.NodeName.Contains(sc.SearchTerm)) || (nc.OrionCustomerName.Contains(sc.SearchTerm)) || (nc.IPAddresses.Contains(sc.SearchTerm)))
select new NodeInfo { Customer = nc.OrionCustomerName, DeviceName = n.NodeName, DeviceType = nc.OrionApplication, IPAddress = nc.IPAddresses, NodeID = n.NodeID }).Distinct().ToList();
I have a rather complex linq to entity query that I'm performing, in the end, I have a result set. I loop through that result set, build business objects and return that list of business objects. it's pretty quick, the problem is that 2 of the child properties are complex objects with their own child objects. for every business object in my loop, I then have to make 2 DB calls to fill its child object. Those 2 calls slow down the overall process, is there a better way to do this? noob to EF here. (EF 4,SQL Server 2008,c#)
Get a result set:
var newresult = from r in result // result is another complex query
join subedit in
(from sa in context.Security_Access
join g in context.Security_UserGroup on sa.EntityID equals g.GroupID
where (sa.PrivledgeID == xx) && g.UserID == userId
select new { user = g.UserID, linkid = sa.LinkID }).Distinct() on new { aid = r.AssetId } equals new { aid = subedit.linkid } into theSubEdit
from subEditAccess in theSubEdit.DefaultIfEmpty()
join subdownload in
(from sa in context.Security_Access
join g in context.Security_UserGroup on sa.EntityID equals g.GroupID
where (sa.PrivledgeID == xx|| sa.PrivledgeID == yy) && g.UserID == userId
select new { user = g.UserID, linkid = sa.LinkID }).Distinct() on new { aid = r.AssetId } equals new { aid = subdownload.linkid } into theSubDownload
from subDownloadAccess in theSubDownload.DefaultIfEmpty()
join subView in
(from sa in context.Security_Access
join g in context.Security_UserGroup on sa.EntityID equals g.GroupID
where (sa.PrivledgeID == xx|| sa.PrivledgeID == yy|| sa.PrivledgeID == 101) && g.UserID == userId
select new { user = g.UserID, linkid = sa.LinkID }).Distinct() on new { aid = r.AssetId } equals new { aid = subView.linkid } into theSubView
from subViewAccess in theSubView.DefaultIfEmpty()
select new { r, EditAccess = (int?)subEditAccess.user, DownloadAccess = (int?)subDownloadAccess.user, ViewAccess = (int?)subViewAccess.user };
I then loop through that result set:
foreach (var asset in newresult)
{
// and build a new business object, set its properties
BoAsset boAsset = new BoAsset();
boAsset.HasEditRights = (asset.EditAccess > 0);
boAsset.HasDownloadRights = (asset.DownloadAccess > 0);
boAsset.HasViewRights = (asset.ViewAccess > 0);
boAsset.Description = asset.r.Description;
boAsset.DetailedDescription = asset.r.DetailedDescription;
boAsset.Keywords = asset.r.Keywords;
boAsset.Notes = asset.r.Notes;
boAsset.Photographer = asset.r.Photographer;
boAsset.PhotographerEmail = asset.r.PhotographerEmail;
boAsset.Notes = asset.r.Notes;
boAsset.Author = asset.r.Author;
// these 2 properties i've commented out are
// complex objects/entities, setting them the way I am
// requires me to call 2 separate methods which make 2 DB trips
// per business object.
//boAsset.Domains = GetAssetDomains(asset.r.AssetId);
//boAsset.DomainEntries = GetAssetCustomDomains(asset.r.AssetId);
myListofObjects.Add(boAsset);
}
return myListofObjects;
Is there a better way?
Just add this .Include("Domains").Include("DomainEntries") to your Linq in in context.Security_Access That should get rows from those tables all in one go.
So your "inner" queries would look like:
from sa in context.Security_Access.Include("Domains").Include("DomainEntries")
join g in context.Security_UserGroup on sa.EntityID equals g.GroupID
where (sa.PrivledgeID == xx) && g.UserID == userId
select new { ...
Here is the documentation from MS: http://msdn.microsoft.com/en-us/library/bb738708.aspx
If you want to improve your performance use compile queries !
You can check the example here.
static readonly Func<AdventureWorksEntities, Decimal,
IQueryable<SalesOrderHeader>> s_compiledQuery2 =
CompiledQuery.Compile<AdventureWorksEntities, Decimal, IQueryable<SalesOrderHeader>>((ctx, total) =>
from order in ctx.SalesOrderHeaders.Include("Orders") where order.TotalDue >= total select order);
MSDN
AND
You can Introduce Include suppose to select all the employees along with their departments . If you have a navigational property, you won't need a join at all. You can use Include like this:
List<Employee> employeesWithDepartments = CreateObjectSet<Employee>().
Include(e => e.Department).
ToList();
i currently have the following LINQ statement:
using (MYEntities ctx = CommonMY.GetMYContext())
{
List<datUser> lstC = (from cObj in ctx.datUser
join fs in ctx.datFS on cObj.UserID equals fs.datUser.UserID
where userOrg.Contains(fs.userOrg.OrgName)
select cObj).ToList();
foreach (datUser c in lstC)
{
Claim x = new Claim
{
UserID= c.userID,
FirstName = c.FirstName,
LastName = c.LastName,
MiddleName = c.MiddleName,
};
}
}
right now it returns all users, but it duplicates them if they have more then 1 org associated with them.
how can i ensure that it only returns distinct UserIDs?
each user can have multiple orgs, but i really just need to return users that have at least 1 org from the userOrg list.
Right before your ToList, put in .Distinct().
In response to #DJ BURB, you should probably use the Distinct overload that takes in an IEqualityComparer to best be sure that you're doing it based off of the unique id of each record.
Look at this blog post for an example.
use group by.
syntax:
var result= from p in <any collection> group p by p.<property/attribute> into grps
select new
{
Key=grps.Key,
Value=grps
}
You will have to call Distinct(), there is no linq query equivalent of that command.
I have two tables in my database, Building and Town. They look like this:
Building:
buildingid
buildingname
Town:
id
userid
buildingid
In Town there is one entry for each building a user has.
What i want is to populate a GridView for a user with a given userid. This GridView should include the buildingname and the number of buildings.
Building. I have tried this:
var buildings = (from Town in dc.Towns
join Building in dc.Buildings
on Town.buildingid equals Building.buildingid
select Building.buildingname);
gvBuildings.DataSource = buildings;
gvBuildings.DataBind();
But I don't know how to get the numbers for each building.
I have now been working on this for a while and a couple of your answers work. I have used this code:
var buildings = dc.Towns
.Where(t => t.userid == userid)
.GroupJoin(dc.Buildings,
t => t.buildingid,
b => b.buildingid,
(Town, Buildings) => new
{
BuildningName = Buildings.First().buildingname,
Count = Buildings.Count()
});
gvBuildings.DataSource = buildings.ToList();
gvBuildings.DataBind();
When i run this code my GridView ends up looking like this:
I need the buildings to be shown in groups, grouped by the buildingname. I have tried all of the suggestions but i cant get it to work.
Try grouping:
var buildings = dc.Towns
.Where(t => t.UserId == userId)
.GroupJoin(dc.Buildings,
t => t.BuildingId,
b => b.BuildingId,
(town, buildings) => new
{
BuildingName = buildings.First().BuildingName,
Count = buildings.Count
});
Keep in mind that when binding to a control you must supply a collection of type (or implementing) IList. This can be accomplished by calling ToList() on the buildings collection:
gvBuildings.DataSource = buildings.ToList();
gvBuildings.DataBind();
check linq differed execution
and than try the blow code might work for you
var buildings =
(from j in dc.Town
join i in dc.Buildings
on j.buildingId equals i.buildingId
where j.Userid = varUSerid
group new {i, j}
by new
{ i.BuildingID }
into
g
select new {
BuildingName = g.First<k=>k.BuildingName)
, count = g.Count() } ).ToList();
gvBuildings.DataSource = buildings;
gvBuildings.DataBind();
var buildings = (from Town in dc.Towns
join Building in dc.Buildings
on Town.buildingid equals Building.buildingid
into results
from r in results.DefaultIfEmpty()
group Town by new
{
r.BuildingId
} into groupedResults
where Town.UserID == parameteruserId
select new
{
BuildingName = Building.buildingname,
BuildingCount = groupedResults.Count()
});
Try this.. it should work.. i have a similar requirement..
manDbDataContext db = new DbDataContext();
var estimatedTotal = ( from est in db.AssignmentEstimatedMaterials
where est.assignment_id == Convert.ToInt32(Label_assignmentId.Text)
join materialdetail in db.Materials on est.material_id equals materialdetail.material_id
select new { est.qty,est.total_amount, materialdetail.material_name}).ToList();
GridView_estiamte_material.DataSource = estimatedTotal;
GridView_estiamte_material.DataBind();
Note, you should select individual data and it works.