LINQ IQueryable - c#

I have a Menu class that has a IQueryable property called WebPages. In the following statement I am returning Menu items based on a match but I need to include the Webpages property. Here is what I have at the moment.
var allCategories = Menu.All().Where(x => x.CategoryID == 4 && x.Visible)
I need to extend it to check a property in the WebPage class, something like this..
var allCategories = Menu.All().Where(x => x.CategoryID == 4 && x.Visible && x.WebPages.Roles.Contains(User.Identity.Name))
That won't compile but I hope you get the jist of what I am trying to do.
NOTE: The Webpage property is filled by the PageID not CategoryID but not sure if that makes a difference??
Here are a brief outline of my classes.
public partial class Menu: IActiveRecord
{
public int ID {get; set;}
public int CategoryID {get;set;}
public bool Visible {get;set;}
public int PageID {get;set;}
public IQueryable<WebPage> WebPages
{
get
{
var repo=NorthCadburyWebsite.Models.WebPage.GetRepo();
return from items in repo.GetAll()
where items.ID == _PageID
select items;
}
}
}
public partial class WebPage: IActiveRecord
{
public int ID {get;set;}
public string Roles {get;set;}
}

If I understand the problem correctly, you want something like this:
var menuItems =
from menuItem in Menu.All()
where menuItem.Visible
and (
menuItem.WebPages.Contains(
webPage => webPage.Roles.Contains(
"role"
)
)
or menuItem.PageIsNull
)
select menuItem;
This should select only menu items joined to pages with the appropriate role.

This should do it for you mate. You just need to say WebPages.Any, this will return true if any menus contain a webpage with your specified role.
var allCategories = menus.Where(menu => menu.CategoryID == 1 && menu.Visible && menu.WebPages.Any(webPage => webPage.Roles.Contains(roleToSearchFor)));
So the key bit that you need to add is this.
menu.WebPages.Any(webPage => webPage.Roles.Contains(roleToSearchFor))
Using the Any() function is very efficient as it will stop looking as soon as it finds a match.
If you used Where() and then Count() it would iterate through all the Webpages to find all matches and then iterate through the results to count them, so that would be much less efficient.
Below is a full source example for you to try.
namespace DoctaJonez.TestingBrace
{
public partial class Menu //: IActiveRecord
{
public int ID { get; set; }
public int CategoryID { get; set; }
public bool Visible { get; set; }
public int PageID { get; set; }
public IQueryable<WebPage> WebPages { get; set; }
}
public partial class WebPage //: IActiveRecord
{ public int ID { get; set; } public string Roles { get; set; } }
public static class Launcher
{
/// <summary>
/// The Main entry point of the program.
/// </summary>
static void Main(string[] args)
{
Menu myMenu1 = new Menu
{
ID = 1,
CategoryID = 1,
PageID = 1,
Visible = true,
WebPages = new List<WebPage>()
{
new WebPage { ID = 1, Roles = "Role1" },
new WebPage { ID = 1, Roles = "Role2" },
new WebPage { ID = 1, Roles = "Role3" },
}.AsQueryable()
};
Menu myMenu2 = new Menu
{
ID = 1,
CategoryID = 1,
PageID = 1,
Visible = true,
WebPages = new List<WebPage>()
{
new WebPage { ID = 1, Roles = "Role3" },
new WebPage { ID = 1, Roles = "Role4" },
new WebPage { ID = 1, Roles = "Role5" },
}.AsQueryable()
};
Menu myMenu3 = new Menu
{
ID = 1,
CategoryID = 1,
PageID = 1,
Visible = true,
WebPages = new List<WebPage>()
{
new WebPage { ID = 1, Roles = "Role5" },
new WebPage { ID = 1, Roles = "Role6" },
new WebPage { ID = 1, Roles = "Role7" },
}.AsQueryable()
};
List<Menu> menus = new List<Menu>() { myMenu1, myMenu2, myMenu3 };
string roleToSearchFor = "Role3";
var allCategories = menus.Where(menu => menu.CategoryID == 1 && menu.Visible && menu.WebPages.Any(webPage => webPage.Roles.Contains(roleToSearchFor))).ToList();
return;
}
}

Try changing the
public IQueryable<WebPage> WebPages
to
public IEnumerable<WebPage> WebPages
I think LINQ queries return IEnumerable...

DoctorJonez thanks!!!
I'm putting this here as I have more space. I have 11 records in the Menu table, only 1 with a PageID set however if I use
var test = Menu.All().Where(x => x.WebPages.Any(pages => pages.Roles.Contains(Roles.GetRolesForUser()[0])
I get 11 records as the SQL run is this
SELECT [t0].[CategoryID], [t0].[CreatedBy], [t0].[CreatedOn], [t0].[ID], [t0].[ImageID], [t0].[ImageIDHover], [t0].[Locale], [t0].[ModifiedBy], [t0].[ModifiedOn], [t0].[OrderID], [t0].[PageID], [t0].[ParentID], [t0].[Title], [t0].[URL], [t0].[Visible]
FROM [dbo].[Menu] AS t0
WHERE EXISTS(
SELECT NULL
FROM [dbo].[WebPage] AS t1
WHERE ([t1].[Roles] LIKE '%' + 'User' + '%')
)
If I run this I get the 1 record
var test = Menu.All().Where(x => x.WebPages.Any(pages => pages.Roles.Contains(Roles.GetRolesForUser()[0]) && pages.ID == x.PageID));
The SQL for this is
SELECT [t0].[CategoryID], [t0].[CreatedBy], [t0].[CreatedOn], [t0].[ID], [t0].[ImageID], [t0].[ImageIDHover], [t0].[Locale], [t0].[ModifiedBy], [t0].[ModifiedOn], [t0].[OrderID], [t0].[PageID], [t0].[ParentID], [t0].[Title], [t0].[URL], [t0].[Visible]
FROM [dbo].[Menu] AS t0
WHERE EXISTS(
SELECT NULL
FROM [dbo].[WebPage] AS t1
WHERE (([t1].[Roles] LIKE '%' + 'User' + '%') AND ([t1].[ID] = [t0].[PageID]))
)
Is this a bug in Subsonic or am I not understanding it correctly?
The problem with Any() is that in the SQL as long as one record exits, doesn't matter which record it will return data.
I think effectively I am wanting an UNION SQL like below but I don't know how I re-engineer that into C#/Subsonic
select m.* from menu m where pageid is null
union
select m.* from menu m
join webpage p
on p.id = m.pageid
where p.roles like '%User%'
I want to return all menu records and for those with a PageID set that the corresponding WebPage has the user's role in it. If the user's role is not in the WebPage then I don't want to see it in my results.

Related

Select Only Specific Fields From an Optional Navigation Property

I have two entities - User and Plan
class Plan
{
public int Id { get; set; }
public string Name { get; set; }
public string FieldA { get; set; }
public string FieldB { get; set; }
...
}
class User
{
public int Id { get; set; }
public string Name { get; set; }
public int PlanId { get; set; }
public Plan Plan { get; set; }
}
I want to select a specific user and populate its Plan property, but only with specific fields (Id and Name).
I know it's supposed to be simple, I just need to write
db.Users
.Select(u => new User()
{
Id = u.Id,
Name = u.Name,
PlanId = u.PlanId,
Plan = new Plan()
{
Id = u.Plan.Id,
Name = u.Plan.Name
}
})
.SingleOrDefault(u => u.Id == 1);
The tricky part is that Plan is optional (not in the sense that PlanId can be null, but in the sense that PlanId can point to a Plan object that doesn't exist).
When Plan exists, everything works as expected, but when it doesn't, the Plan property is still populated, with an Id but with a null Name
Example:
{
"Id": 1,
"Name": "some name..",
"PlanId": 9, // There is no Plan object with Id = 9
"Plan": {
"Id": 9,
"Name": null
}
}
What I expect, is for Plan to be null.
How can I achieve it?
Thank you
Why not just put a condition on populating the Plan field?
db.Users
.Select(u => new User()
{
Id = u.Id,
Name = u.Name,
PlanId = u.PlanId,
Plan = u.Plan.Name == null ? null : new Plan()
{
Id = u.Plan.Id,
Name = u.Plan.Name
}
})
.SingleOrDefault(u => u.Id == 1);
After the discussion in the comments another approach would be:
db.Users
.Select(u => new User()
{
Id = u.Id,
Name = u.Name,
PlanId = u.PlanId,
Plan = db.Plans.Find(u.PlanId)
})
.SingleOrDefault(u => u.Id == 1);
Find should return null if the Plan with u.PlanId is not found (in the current context or store)
Use GroupJoin like this
var userPlanPair = db.Users
.GroupJoin(db.Plans, u => u.PlanId, p => p.Id, (user, plans) => new { user, plans })
.SelectMany(pair => pair.plans.DefaultIfEmpty(), (pair, plan) => new { pair.user, plan = plan != null ? new Plan() { Id = plan.Id, Name = plan.Name } : null })
.SingleOrDefault(pair => pair.user.Id == 1);
The combination of GroupJoin, SelectMany and DefaultIfEmpty translates to a left join in SQL form.
Inside the resultSelector of SelectMany you query the specific fields that you need from Plan.
To get the final result - A User object with a Plan property, you just select the user and plan from the pair.
var user = userPlanPair.user;
user.Plan = userPlanPair.plan;

Set the first Record Value of LINQ

I have a DBSet which is db.Company and it contains 3 records like
id name is_active
1 company1 1
2 company2 1
3 company3 1
My Created class to transfer the records:
public class CompanyFinal
{
public int id { get; set; }
public string name { get; set; }
public string selected { get; set; }
}
My LINQ looks like this:
(from h in db.Company where h.is_active == 1 select new CompanyFinal { id = h.id, name = h.name, selected = "false" }).ToList();
No doubt this LINQ is working but i need to make the first record to be selected="true" which is the company 1 while doing it in LINQ.
How could i do that? Is it possible?
Thanks in advance
Since you're making a list, I think that computational the fasted method, which is also the best maintainable (understood by others) would be to just assign the first element:
(Using method syntax, showing an alternative select)
var resultingList = db.Company
.Where(company => company.is_active == 1)
.Select(company => new CompanyFinal()
{
id = company.id,
name = company.name,
selected = "false",
})
.ToList();
if (resultingList.Any())
resultingList[0].selected = true;
If you are not sure whether you want to convert it to a list, for instance because you want to concatenate other Linq functions after it, consider using this overload of Enumerable.Select
var result = db.Company
.Where(company => company.is_active == 1)
.Select( (company, index) => new CompanyFinal()
{
id = company.id,
name = company.name,
selected = (index == 0) ? "true" : "false",
});
By the way, consider changing your CompanyFinal class such that selected is a bool instead of a string, that would make your code less error prone. For instance, after construction Selected is neither "true" nor "false". Users are able to give it a value like "TRUE" or String.Empty
If your class has users that really need a string property selected instead of a Boolean property consider keeping a get property for it:
public class CompanyFinal
{
public int id { get; set; }
public string name { get; set; }
public bool Selected {get; set;}
// for old user compatability:
public string selected
{
get { return this.Selected ? "true" : "false"; }
}
}
The following will go wrong:
if (companyFinals[0].selected == "True") ...
versus:
if (companyFinals[0].Selected)
What if we define a local variable
var counter = 1;
(from h in db.Company where h.is_active == 1 select new CompanyFinal { id = h.id, name = h.name, selected = (counter++ == 1 ? "true" :"false") }).ToList();
From your result:
var companies = (from h in db.Company
where h.is_active == 1 select h).ToList();
var companyFinals = (from h in companies
select new CompanyFinal {
id = h.id,
name = h.name,
selected = "false"
}).ToList();
var firstCompany = companies.FirstOrDefault();
if (firstCompany != null) {
firstCompany.selected = true;
}
// save result back to database
There is no way to do it in 1 operation other than writing a stored procedure.

Linq returning different result than SQL

I'm running this Linq query:
var patientList = from p in db.Patients
where p.ClinicId==11
select p.Id;
var patientswithplan = from p in db.Plans
where patientList.Contains(p.PatientId)
select p;
It returns 1030 results.
But when I came up with this query I wrote it in sql first to test it out and this displays 956 results
select id from patients where clinicid=11
and id in(select patientid from plans)
order by id
I thought these queries would be the same, what is the difference, which one is correct?
I have written a little code then you could see the difference yourself
void Main()
{
var Plans = new List<Plan>();
Plans.Add(new Plan() {PatientId = 1, PlanName = "Good Plan"});
Plans.Add(new Plan() {PatientId = 2, PlanName = "Bad Plan"});
var Patients = new List<Patient>();
Patients.Add(new Patient() {ClinicId = 1, Name = "Frank"});
Patients.Add(new Patient() {ClinicId = 2, Name = "Fort"});
// This is your LINQ
var patientList = from p in Patients
where p.ClinicId == 1
select p.ClinicId;
var patientswithplan = from p in Plans
where patientList.Contains(p.PatientId)
select p;
Console.WriteLine(patientswithplan);
// We return a PLAN here
// Result
// IEnumerable<Plan> (1 item)
// PatientId 1
// PlanName Good Plan
// This is the equivalent Linq of your SQL
var myPatient = Patients.Where(
pa => pa.ClinicId == 1 &&
Plans.Any(pl => pl.PatientId == pa.ClinicId)
);
Console.WriteLine(myPatient);
// Look! We return a PATIENT here
// Result
// IEnumerable<Patient> (1 item)
// ClinicId 1
// Name Frank
}
// Define other methods and classes here
class Patient
{
public Patient() {}
public int ClinicId { get; set; }
public string Name { get; set; }
}
class Plan
{
public Plan() {}
public int PatientId { get; set; }
public string PlanName { get; set; }
}
The queries do two different things:
1) The first query is basically first getting a list of patients, and then it's fetching plans (you choose "from p in db.Plans") that have those selected patients in their list of patients.
2) The second query is filtering and fetching patients of given clinic making sure that those patients exist in some plans.
So of course the number of results will be different as you probably have a different number of rows in the patients and plans tables.

Create hyperlinks in MVC Action Method

I have an action method returning a JsonResult in my controller:
public JsonResult GetDetails()
{
var rows = //Linq-To-SQL
//Linq-To-Entities
var lifts = (from r in rows
group r by new { r.LiftID, r.LiftDate } into g
select new
{
ID = g.Key.LiftID,
Date = g.Key.LiftDate.ToShortDateString(),
Driver = g.Where(x => x.IsDriver)
.Select(x => x.p).Single().Name,
Passengers = g.Where(x => !x.IsDriver)
.Select(x => x.p.Name)
.Aggregate((x, y) => x + ", " + y)
}).ToList();
return Json(lifts);
}
I use the result in a jQuery script to write out a table.
The data looks like:
ID | Date | Driver | Passengers
1 | 20/06/2010 | David Neale | John Smith, Paul Jones
etc...
I would like the names to be hyperlinks to the route Person\{id} I.e. David Neale. The p property corresponds to a Person object containing both Name and ID.
I don't want to construct the URL manually. How would I construct the object to contain the names as hyperlinks using the MVC routing engine?
It's fairly easy.
Just use Url.Action("actionName", "controllerName", params)
It will create a string using the routing engine, so if you change the routes your code will keep workin just fine
First, I think you have a mistake in your model or in your Linq-To-SQL or Linq-To-Entities querys. Because you don't have an ID for your Persons (Drivers and Passagers), and you will definitly need it if you want a link with an Id of the Person. I think you need to split your Lifts from your Persons and have 2 separate entities (linked by its Id of course).
Second, you need to pass the ID of the Persons from the Controller to the View.
class Lift
{
public int LiftID { get; set; }
public DateTime LiftDate { get; set; }
public IEnumerable<Person> p { get; set; }
}
class Person
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsDriver { get; set; }
}
public JsonResult GetDetails()
{
var rows = new Lift[] { //Linq-To-SQL and Linq-To-Entities replaced by an example
new Lift{LiftID = 1, LiftDate= DateTime.Now, p = new Person[] {
new Person{IsDriver = true, Id = 1, Name = "David Neale"},
new Person{IsDriver = false, Id = 2, Name = "John Smith"},
new Person{IsDriver = false, Id = 3, Name = "Paul Jones"}
}},
new Lift{LiftID = 2, LiftDate= DateTime.Now, p = p = new Person[] {
new Person{IsDriver = true, Id = 4, Name = "Daniel Faraday"},
new Person{IsDriver = false, Id = 2, Name = "John Smith"}
}}
};
var lifts = (from r in rows
select new
{
ID = r.LiftID,
Date = r.LiftDate.ToShortDateString(),
Driver = r.p.Where(x => x.IsDriver)
.Select(x => x.Name).Single(),
Passengers = r.p.Where(x => !x.IsDriver)
.Select(x => x.Name)
.Aggregate((x, y) => x + ", " + y)
}).ToList();
return Json(lifts);
}
Then, once you have the ID on the View you use it to make the link using Html.ActionLink:
<%= Html.ActionLink("Person", "Index", new { id = p.Id })%>
If you know the action and controller beforehand, you can do something like:
var baseURL = '<%=Url.Action("MyAction","MyController") %>';
and then manually build your links from jQuery, with href set to something like baseUrl+"/"+personId

How do I match two identical database tables with LINQ?

I want to match 2 identical tables:
sourceProducts (productName, ProductionDate, ManID, shipper, distributer)
CommProducts (productName, ProductionDate, ManID, shipper, distributer)
but the number of rows and the record contents may differ. How do I select a certain record = raw from one table and get its clone record from the other table (e.g., check if the same record exists)? How do I do this using LinQ?
UPDATE: Here's the LINQ code:
protected void checkBtn_Click(object sender, EventArgs e)
{
MyProductsDataContext mySdb = new MyProductsDataContext();
Product mypro = new Product { ManId = int.Parse(TxtManI.Text), ProductName = TxtProN.Text, ProductionDate =DateTime .Parse ( TxtProDat.Text), Shipper = TxtShipI.Text, Distributer = TxtDistI.Text };
var spro = (from p in mySdb.Products
select new { p.ManId, p.ProductName, p.ProductionDate, p.Shipper, p.Distributer }).
Intersect(from s in mySdb.SourceProducts select new { s.ManId, s.ProductName, s.ProductionDate, s.Shipper, s.Distributer });
if (spro != null)
{
LblMessage.Text = "Acceptable product Data Inserted Sucessfully";
InsertData();
}
else
{
LblMessage.Text = "Invalid Product or bad Entry Please retype";
}
}
I would join on ManId and then compare the rest of the values in a where clause:
bool productExists = (
from p in mySdb.Products
join s in mySdb.SourceProducts
on p.ManId equals s.ManId
where p.ProductName == s.ProductName
&& p.ProductionDate == s.ProductionDate
&& p.Shipper == s.Shipper
&& p.Distributer = s.Distributer
select new { p.ManId, p.ProductName, p.ProductionDate, p.Shipper, p.Distributer }
).Any();
if (productExists)
{
LblMessage.Text = "Acceptable product Data Inserted Sucessfully";
InsertData();
}
else
{
LblMessage.Text = "Invalid Product or bad Entry Please retype";
}
I've used Any() to produce an efficient EXISTS SQL query. You could use SingleOrDefault() or FirstOrDefault() instead if you actually need to use the product returned.
I also don't see anywhere that you're using your new Product's ID - you might need to add that filter to the query as well:
Product mypro = new Product { ... };
bool productExists = (
from p in mySdb.Products
where p.ManId equals mypro.ManId
join s in mySdb.SourceProducts
on p.ManId equals s.ManId
...
You can probably do this using a join but I've hobbled together a unit test which shows one way to this
public class TestProduct
{
public int ManId { get; set; }
public string ProductName { get; set; }
public DateTime ProductionDate { get; set; }
public string Shipper { get; set; }
public string Distributor { get; set; }
}
[TestMethod]
public void TestSourceTable()
{
// Set up a test list
var list = new List<TestProduct>();
for (int i=0;i<5;i++)
{
var p = new TestProduct
{
Distributor = "D" + i,
ManId = i,
ProductionDate = DateTime.Now,
ProductName = "P" + i,
Shipper = "S" + i
};
list.Add(p);
}
// Get an existing product
var existingProduct = list[4];
// Get an unknown product
var unknownProduct = new TestProduct()
{
ManId = -1,
Distributor = "",
ProductionDate = DateTime.Now.AddDays(-1),
ProductName = "",
Shipper = ""
};
// product found
Assert.True(list.Any(p => p == existingProduct));
// product not found
Assert.False(list.Any(p => p == unknownProduct));
}

Categories

Resources