I have a LINQ class of this format in C#:
class Vehicle
{
int _VehicleID
int _ModelID
EntetySet<Cars> _AllCars
EntetySet<Bus> _AllBus
EntetyRef<Driver> _Person
}
Cars table
CarsID | Manufacturer | Type
1000 | Honda | Diesel
1001 | Mitsubishi | Petrol
1002 | Maruti | Diesel
Bus table
BusID | Manufacturer | Type
2000 | Volvo | Diesel
2001 | TATA | Petrol
2002 | layland | Petrol
From the UI, I will get a Vehicle ID as a parameter. Based on that, all the cars, Bus and its associated tables will get copied to a variable. That is:
Vehicle v1 = new Vehicle();
v1 = dc.vehicle.where(v => v.vehicleID == param.vehicleID).findfirst();
My v1 has all the table and its contents which satisfies the above condition.
Now, I want to filter some more based on the table content present in table cars and bus; i.e based on type of vehicle petrol or diesel.
If I want to copy all the petrol cars and Buses using a query statement in a single line, please tell me the way.
Thanks in advance.
** Edit based upon comments **
The logic of your question somehow slipped by me, because I was taking a logical Human approach on vehicle. Whenever I look at this Vehicle class I instantly see cars and busses as vehicles to.
This indicates more that the naming is poorly chosen.
But no more judging from me.
I understand you want one vehicle by id and you want the result to only contain cars and busses riding on let's say Petrol, in a single line.
If it is translated to SQL than I don't know how far the tree can go within a select, but if it is in memory than you can go a long way inside a subselect.
You could use a 'Model' class to achieve that or you could go with an anonymous type, but I will give an example for a 'Model' class:
Example:
public class VehicleModel
{
public int VehicleID { get; set; }
public int ModelID { get; set; }
public List<Cars> Cars { get; set; }
public List<Bus> Buses { get; set; }
}
var vehicleID = 1; // just for the example of course.
var fuelType = "Petrol";
var vehicle = (from v in dc.vehicle
where v._VehicleID == vehicleID
select new VehicleModel
{
VehicleID = v._VehicleID,
ModelID = v._ModelID,
Cars = v._AllCars.Where(car => car.Type == fuelType).ToList(),
Buses = v._AllBus.Where(bus => bus.Type == fuelType).ToList()
}).SingleOrDefault();
// use FirstOrDefault() when _VehicleID is NOT unique to get TOP 1
Do not modify the EntitySets within the Entity itself, always use a model for these things, because if you would do that and call save changes on your EntityContainer all sorts of things could go wrong.
If you want to know about anonymous types take a look here:
http://msdn.microsoft.com/en-us/library/bb397696(v=vs.100).aspx
Also take a look here for Linq examples:
http://code.msdn.microsoft.com/101-LINQ-Samples-3fb9811b
- before edit
Do you mean this:
var query = dc.vehicle.Where(v =>
v._AllCars.Any(c => c.Manufacturer == "Honda") ||
v._AllBuss.Any(b => b.Manufacturer == "Volvo"));
This will give you all vehicles where the cars are Honda Or the Busses are Volvo.
Which will be:
1000 | Honda | Diesel
2000 | Volvo | Diesel
the result is an IEnumerable containing all or no items satisfying the criteria.
If you only want the first hit you can do:
//if it must exist, otherwise this throws an exception
var firstVehicle = query.First();
//if it may exist, otherwise null
var firstVehicle = query.FirstOrDefault();
It seems that you want something like this:
var v1 = dc.vehicle.Where(v => v.vehicleID == param.vehicleID
&& v.Cars.All(c => c.Type == "Petrol")
&& v.Buses.All(c => c.Type == "Petrol")).First();
)
to get a "Vehicle" of which all cars and buses have petrol. (Note that I use "Cars" and not the unconventional name "_AllCars", etc.).
Edit
Or:
var v1 = dc.vehicle.Where(v => v.vehicleID == param.vehicleID)
.Select(v => new {
Id = v.vehicleID,
Cars = v.Cars.Where(c => c.Type == "Petrol"),
Buses = v.Buses.Where(c => c.Type == "Petrol")
}).First();
to get an anonymous type with filtered collections.
Related
I have a system where I enter in other, employee, and dealer information as such (there is more fields but trying to keep it simple).
+----+---------+--------------+------------+-------+
| ID | itemId | memberTypeId | MemberType | price |
+----+---------+--------------+------------+-------+
| 1 | 202 | 2 | employee | 2.00 |
| 2 | 202 | 3 | dealer | 3.00 |
| 3 | 101 | 1 | other | 4.00 |
+----+---------+--------------+------------+-------+
I’m trying to make the Update functionality on my site. When I make a DB call based on the ItemId to get everything.
Here is such code from helper class:
public class Item {
public static List<ItemOption> GetItemOpttions(long itemID)
{
using (DBContext context = new DBContext())
{
List<ItemOptionDTO> ItemOptionsDto = (
from I in context.ItemOptions
where I.ItemID == itemID
select new ItemOptionDTO
{
ItemID = I.ItemID,
MemberTypeId = I.MemberTypeId
MemberType = I.MemberType,
Price = I.Price,
}).ToList();
return ItemOptionsDto;
}
}
}
So, when I get the data and store it in a list. It’s either one row of data or two rows based on how it was saved. The option “Other” will always be saved into the DB as one row of data as where “employee and dealer” will be saved as two separate row sharing the same ItemId. When called the number of rows will vary based on each item created at an earlier date/time.
I am trying to get this info back to the controller and send it to the View showing the correct values that might get updated. Here is what I have in the controller. I'm not sure if this is the correct way of doing this or if there is a better way.
Also to add that the if the item was saved as an employee and dealer it will leave the other list empty/null. I don't think I can do this because it will cause a null exception error
This is what I have so far in the controller:
public ActionResult UpdateItem(long itemID) {
List<ItemOptionDTO> itemOptionDto = Item.GetItemOpttions(itemID);
var other = itemOptionDto.Where(x => x.MemberTypeId == 1).FirstOrDefault();
var employee = itemOptionDto.Where(x => x.MemberTypeId == 2).FirstOrDefault();
var dealer = itemOptionDto.Where(x => x.MemberTypeId == 3).FirstOrDefault();
UpdateItemViewModel viewModel = new UpdateItemViewModel(itemID, employee, dealer, option)
{
ItemId = option.ItemId,
MemberType = option.MemberType,
Price = other.price
ItemId = employee.ItemId,
MemberType = employee.MemberType,
Price = employee.price
ItemId = dealer.ItemId,
MemberType = dealer.MemberType,
Price = dealer.price
};
return View(viewModel);
}
Let me know if there is anything else I can provide.
I'm making a leaderboard for the leveling system my Discord bot has. The list will make a list of the people with most XP and order them from highest to lowest amount. I have already achieved this goal, but I can only show to user's ID next to their XP amount in the leaderboard. How can I turn this user ID into a username?
foreach (ulong n in DbContext.Experiences.OrderByDescending(x =>
x.XP).Select(x => x.ID))
{
Context.Guild.GetUser(n).ToString()
}
var leaderboard = string.Concat(DbContext.Experiences.OrderByDescending(x =>
x.XP).Select(x => $"Level {x.LevelNumber} with {x.XP} xp
{//username must be here}\n"));
await ReplyAsync(leaderboard.ToString());
What I would expect to see (Never looked at Discord if that's where the database structure comes from) would be a table for:
Guild
Experiences
Users
I would Expect dbContext.Users to have something like:
UserId | UserName
and then a Guilds table to have something like:
GuildId | GuildName | UserId
and Experiences to look something like:
ExperienceId | UserId
I'll continue to make some assumptions here:
Context.Guild.GetUser(n).ToString();
This to me looks like the an EF Core query which translates into SQL:
select UserId from Guild
Working from that, I see a few potential issues:
First, the Guild method returning only a string or long is weird. Return an object if that's your implementation.
More importantly, you can do this likely in 1 query:
in Sql:
Select g.GuildId, e.Experiece, u.UserId, u.UserName from Guild g
left join Users u on g.UserId = u.UserId
left join Experiences e on u.UserId = e.UserId
where g.GuildId = #myGuildId
order by e.Experience Desc
this would give you back rows like:
1 | 1500 | 10 | BitesSpoons
1 | 1450 | 51 | LostElbows
1 | 1121 | 98 | EarthSkyFire
1 | 990 | 15 | GoldenGoose
What I would do: Create a view class, and map it either with something like AutoMapper from a query, or when you materialize the instance:
Make a class:
public class LeaderView
{
public string UserName {get; set;}
public long UserId {get; set;}
public long GuildId {get; set; }
public int Experience { get; set; }
}
And then a linq to Sql query like:
var leaders = from g in context.Guilds
join u in context.Users on g.UserId = u.UserId
join e in context.Experience on u.UserId = e.UserId
select new LeaderView
{
UserName = u.UserName,
UserId = u.UserId,
GuildId = g.UserId,
Experience = e.Experience
};
leaders = leaders.OrderByDescending(o => o.Experience);
return leaders.ToList();
See comments in code:
//What exactly is this for loop meant to do?
//You appear to be getting a user based on ID and doing nothing with that value.
//This should be removed
foreach (ulong n in DbContext.Experiences.OrderByDescending(x =>
x.XP).Select(x => x.ID))
{
//While this is how to get a user by ID, you aren't actually doing anything with this once it's retrieved.
Context.Guild.GetUser(n).ToString()
}
//You can simply fetch the username here, given that you have access to the ID
var leaderboard = string.Concat(DbContext.Experiences.OrderByDescending(x =>
x.XP).Select(x => $"Level {x.LevelNumber} with {x.XP} xp
{//username must be here}\n"));
await ReplyAsync(leaderboard.ToString());
Your modified code should look something like:
var leaderboard = string.Join("\n", DbContext.Experiences
.OrderByDescending(x => x.XP)
.Select(x => $"Level {x.LevelNumber} with {x.XP} xp {Context.Guild.GetUser(x.ID).ToString()}"));
await ReplyAsync(leaderboard);
NOTE: I've replaced string.Concat with string.Join as it's a more efficient String building method.
Assuming that you already have the desired User ID
Context.Guild.GetUser(THE_USER_ID_HERE).Username
This return the user's username if exists.
If your leaderboard is global (where the user might not be in the server in which the command is executed) you can use client.GetUser(THE_USER_ID_HERE).Username instead, where client is your bot's current socket client.
(Or alternatively, access the Nickname property if you wish to display the user's name tied to the server instead)
Below is my class :
public partial class Ads
{
public int Id { get; set; }
public int RegionId { get; set; }
public string Name { get; set; }
public int Group { get; set; }
}
Records :
Id Name Group
1 abc 1
2 xyz 1
3 lmn 1
4 xxx 2
5 ppp 2
6 ttt 3
7 ggg 3
Now I want to remove all records/only that record with particular id of same group for some ids.
Code :
public void Delete(int[] ids,bool flag = false)
{
using (var context = new MyEntities())
{
context.Ads.RemoveRange(
context.Ads.Where(t => (flag ?
(context.Ads.Any(x => ids.Contains(x.Id) && x.Group == t.Group)) : false)));
context.SaveChanges();
}
}
What I am trying to do is something like below :
If flag is false with ids=3,5 then
I want to delete only records with Id=3,5
Else if flag is true with ids=3,5 then
I want to delete records with Id=3,5 but all other records too of the group to which ids=3,5 belong to.
Here id=3 belongs to group 1 so I want to delete all records of group1 i.e id=1,2 like wise ids=5 belongs to
group 2 so I want to delete all records of group 2 i.e id=4.
Expected output for this last case(flag=true) :
Id Name Group
6 ttt 3
7 ggg 3
But I think that I haven't done this is a proper way, and there is some source of improvement in the query.
Note : ids[] will always contains ids from different group and that too highest ids from different group.
How can I to improve my query for both the cases(flag=true and false)?
What about
var removeRecs=context.Ads.where(t => ids.contains(t.id))
if(flag)
removeRecs.AddRange(context.Ads.where(t=> removeRecs.Any(r =>t.groupId==r.Id)))
Ads.RemoveRange(removeRecs);
Do not make it too hard for your self, not everything must/can be done in the where statement of a query. Also a general rule of thumb in a loop try to factor out all the constant values and checks. So try this:
public static void Delete(int[] ids, bool flag = false)
{
using (var context = new MyEntities())
{
var query = context.Ads.AsQueryable();
query = flag
? query.Where(x => context.Ads
.Where(i => ids.Contains(i.Id))
.Select(i => i.Group)
.Contains(x.Group))
: query.Where(x => ids.Contains(x.Id));
context.Ads.RemoveRange(query);
context.SaveChanges();
}
}
public void Delete(int[] ids, bool flag = false)
{
using (var context = new MyEntities())
{
var items = context.Ads.Where(x => ids.Any(a => x.Id == a));
if (!flag)
{
//flag=false --> delete items with Id in ids[]
context.Ads.RemoveRange(items);
}
else
{
var groups = items.GroupBy(a => a.Group).Select(a => a.Key);
//flag=true --> delete all items in selected groups
context.Ads.RemoveRange(context.Ads.Where(x => groups.Any(a => x.Group == a)));
}
context.SaveChanges();
}
You should separate your tasks...
if (flag)
{
groupIds = db.Ads.Where(x => ids.Contains(x.Id)).Select(x => x.Group).ToList();
db.Ads.RemoveRange(db.Ads.Where(x => groupIds.Contains(x.Group)).ToList());
}
else
{
db.Ads.RemoveRange(db.Ads.Where(x => ids.Contains(x.Id)).ToList());
}
To me it looks like you have two different deletes here.
In first case you are only deleting the ads with given ID and this is pretty straight forward.
In second case you are deleting the ads with given ID and all other ads that contain the group of the recently deleted Ads. So in this case instead of deleting the ads with given Id first why not actualy get distinct groups for these ID-s and than just delete the groups.
EDIT
You can do it like this.
using (var context = new TestEntities())
{
if (!flag)
context.Ads.RemoveRange(context.Ads.Where(a => ids.Contains(a.Id)));
else
context.Ads.RemoveRange(context.Ads.Where(a => context.Ads.Where(g => ids.Contains(g.Id)).Select(x => x.Group).Distinct().Contains(a.Group)));
context.SaveChanges();
}
For the more complicated case I am trying to get distinct groups for given id-s. So for ID-s 3 and 5 I am selecting the groups and than I am doing distinct on the groups since it might happen that the id-s have the same group. Than I am fetching all the ads that have these groups. So for passed values of 3 and 5 I would get groups 1 and 2 which I would than use to get all the ads that have that group. That in turn would yield id-s 1, 2, 3, 4, and 5 which I would than delete.
EDIT 2
If the complexity of second Linq query bothers you than write a SQL query.
context.Database.ExecuteSqlCommand(
"DELETE Ads WHERE Group IN (SELECT Group FROM Ads WHERE Id IN(#p1, #p2))", new SqlParameter("#p1", ids[0]), new SqlParameter("#p2", ids[1]));
This should be extra performant rather than rely on EF which will delete it one by one.
I have this code:
ClassroomList.Where(x => x.StudentList.Any(y => y.IsMale) == true);
this code returns ClassroomList only if all student in StudentList IsMale == true but returns none if there is at least 1 IsMale == false.
How do I always return ClassroomList that only includes those Students with IsMale == true.
Here is ClassroomList's Class:
public partial class ClassroomList
{
public ClassroomList(){}
public list<Student> StudentList {get; set;}
}
Here is Student Class:
public partial class Student
{
public Student (){}
public bool IsMale {get; set;}
}
Here's a sample of the expected output
Classroomlist Count | Studentlist Count | Male | Female |
2 5 3 2
2 10 10 0
2 8 0 8
Expected output:
1. ClassroomList[1].Studentlist[2]
2. ClassroomList[1].Studentlist[9]
3. ClassroomList[1].Studentlist == null
Add an inner where filter.
ClassroomList.Where(x => x.StudentList.All(y => y.IsMale) );
Update:
So you need like this?
ClassroomList.Select(x => new ClassroomList{
StudentList = x.StudentList.Where(y => y.IsMale).ToList()
}
);
Based on the edited expected output, you can use this:
var results = (from r in ClassroomList
let grp = r.StudentList.GroupBy(x => x.IsMale)
select new
{
ClassRoomsCount = ClassroomList.Count(),
StudentsCount = r.StudentList.Count,
Male = grp.Where(x => x.Key).Count(),
Female = grp.Where(x => !x.Key).Count()
}).ToList();
Side Note : you can remove this line public ClassroomList(){} the compiler already creates a hidden default constructor, so no need to do it yourself.
You are all most there, just use All extension.
ClassroomList.Where(x => x.StudentList.All(y => y.IsMale));
After you update the question, seems you need something like this.
var result = ClassroomList
.Select(x=> new
{
StudentListCount = x.StudentList.Count(),
MaleCount = x.StudentList.Count(c=>c.IsMale),
FemaleCount = x.StudentList.Count(c=>!c.IsMale),
};
I have two tables like this
Table 1 : animal
Country Lion Tiger State
india 4 5 Madhya Pradesh
india 10 2 Utrakhand
Russia 0 10 Primorsky krai
Russia 1 20 Khabarovsk Krai
and Table 2: Project_Tiger
Country No
India 10
Russia 5
I have created inumerable class like this
public animal animal {get;set;};
public project_tiger project_tiger {get;set;};
now I want to show result something like this in view
Country NO lion tiger
india 10 14 7
Russia 5 1 30
here lion and tiger is sum of both the record in table 1
10+4= 15, 5+2 =7, for india and same for russia
now I am lacking of knowledge how to query these data as sum from database using linq and how to show this in razor view
I can write query in sql like this but can't translate it to linq
select animal.country, No, sum(lion), sum(tiger) from animal
inner join project_tiger ON animal.country equals project_tiger.country
Any help regarding this will be appreciated.
You basically need to join both the tables and group the results by the country name and generate the result out of that.
var groupd = (from a in dbContext.Animals
join b in dbContext.ProjectTigers on a.Country equals b.Country
select new { Country = a.Country,
No = b.No,
Lion = a.Lion,
Tiger = a.Tiger }
) // We have the join results. Let's group by now
.GroupBy(f => f.Country, d => d,
(key, val) => new { Country = key,
No = val.First().No,
Lion = val.Sum(s => s.Lion),
Tiger = val.Sum(g => g.Tiger) });
This will give you a collection of anonymous objects. If you have a view model/dto to represent your expected data items, you may use that in the projection part of this linq expression.
Also, like others mentioned in the comments, you might want to look into a better way of building your db schema.
You can still acheive it using EntityFramework, and still use the power of the SQL server to preform the lifting for you.
By using directly with the generic method SqlQuery<> this can be done pretty easily.
Create a class that will fit your need
public class AnimalsCount
{
public int No { get; set; }
public int Lion { get; set; }
public int Tiger { get; set; }
}
Now use the Generic method of SqlQuery<AnimalsCount>
var animalsCount = ctx.Database
.SqlQuery<AnimalsCount>("SELECT Country,(SELECT [No] FROM ProjectTiger WHERE Country = a.Country) as [No], sum(lion) as Lion, sum(tiger) as Tiger FROM [Animal] as a GROUP BY Country")
.ToList();