LINQ - select from db and compare two lists - c#

I am new to linq and I am writing a process which I believe can be improved.
To make it simple :
I get a list of objects.
I check in a db table which object has a row in the database.
I return a list of objects with aditional boolean exists/not exists.
I have the following simple POCO
public class Project
{
public Guid? Id {get; set;}
public string name {get; set;}
}
Notice not always I have an id and therefor I should skip this object.
(strange but this is only very close to my requirement in reallife)
Here is my Code - need improvement:
// Get List OF Project Guids
List<Project> ProjectList = GetProjects()
IEnumerable<Guid?> projectsIDs = from package in packages select package.Key;
List<Guid?> prjGuidsList = projectsIDs.ToList();
// Sends the list of Guids and return only the one that exists in the db
// will be implemented with select.. where.. in..
List<Guid?> dbProjects = FilterSharedVersions(prjGuidsList);
// create a new object that will contain the true false value
List<ProjectsToken> tokens = packages.Select(subject => new ProjectsToken
{
Id = subject.id
MetaKey = subject.Name,
exists = dbProjects.contains(subjecy.id)
}
).ToList();
return tokens;

The Contains method of the List class will perform very poorly in O(n). You need to build a HashSet instead, then the method Contains will perform in O(1).
var dbProjects = new HashSet<Guid?>(FilterSharedVersions(prjGuidsList));
var tokens = packages.Select(subject => new ProjectsToken
{
Id = subject.id
MetaKey = subject.Name,
exists = dbProjects.Contains(subjecy.id)
}
).ToList();

Related

Multiple implementations of the query pattern were found for source type 'DbSet<>'. Ambiguous call to 'Join'

public List<ClassParticipantViewModel> GetClassParticipants(string studentId)
{
List<ClassParticipantViewModel> list = new List<ClassParticipantViewModel>();
var results = (from cp in _DbContext.ClassParticipants
join c in _DbContext.Classes
on cp.ClassId equals c.ClassId
where cp.StudentId.Equals(studentId)
select new ClassParticipantViewModel
{
//ClassParticipant
ClassParticipantId = cp.ClassParticipantId,
StudentId = cp.StudentId,
ClassParticipantCreatedOn = cp.CreatedOn,
//Class
ClassId = c.ClassId,
ClassCode = c.ClassCode,
Section = c.Section,
CourseCode = c.CourseCode,
Description = c.Description,
Units = c.Units,
CreatedBy = c.CreatedBy,
ClassCreatedOn = c.CreatedOn,
})
.ToList();
list = list.Concat(results).ToList();
return list.ToList();
}
Join error:
Models:
ViewModel:
I'm developing a Web API using .NET Core 3.0 then I got an error stating that Ambiguous call to 'Join', this code is worked in my other projects. I'm confused if what is wrong, does the naming convention of my models have an effect?
Quick intro to navigation props in EF:
If your classes looked like:
class Class {
ICollection<ClassParticipant> ClassParticipants {get; set;}
...
}
class ClassParticipant {
Class Class{get;set;}
Participant Participant{get;set;}
...
}
class Participant{
ICollection<ClassParticipant> ParticipantClasses {get; set;}
...
}
EF would be able to work out that this is a typical 1:M:1 relationship between classes:classparticipants:participants - many classes have many participants and the middle table breaks it down.
Once you've done that property mapping, you can write queries like:
_DbContext.ClassParticipants.Select(cp =>
new ClassParticipantViewModel
{
//ClassParticipant
ClassParticipantId = cp.ClassParticipantId,
StudentId = cp.StudentId,
ClassParticipantCreatedOn = cp.CreatedOn,
//Class
ClassId = cp.Class.ClassId,
ClassCode = cp.Class.ClassCode,
Section = cp.Class.Section,
CourseCode = cp.Class.CourseCode,
Description = cp.Class.Description,
Units = cp.Class.Units,
CreatedBy = cp.Class.CreatedBy,
ClassCreatedOn = cp.Class.CreatedOn,
}
).ToList();
EF will see you navigating around in the select (cp.Class.ClassId), and know it has to join Classes in to get the data you want. You can also make direct navigational references in a similar way in a Where clause to load the related data, or, if you know you won't refer to the related data in the Select or Where, but you will want the data to be loaded so you can later refer to it in C#, you can use Include:
_DbContext.ClassParticipants.Include(cp => cp.Class).ToList();
footnote, you might want to have e.g. ICollection<ClassParticipant> ClassParticipants {get; set;} = new HashSet<ClassParticipant>(); because it makes creating new entities easier, not having to remember to make the collection first:
var c = new Class(...);
var p = new Participant(...);
//can do this without having to remember to make a new collection
c.ClassParticipants.Add(new ClassParticipant { Class = c, Participant = p });

Sorting List Array based on an index of array

I want to sort a List Array on the basis of an array item.
I have a List Array of Strings as below:
List<String>[] MyProjects = new List<String>[20];
Through a loop, I have added five strings
(Id, Name, StartDate, EndDate, Status)
to each of the 20 projects from another detailed List source.
for(int i = 0; i<20; i++){
MyProjects[i].Add(DetailedProjectList.Id.ToString());
MyProjects[i].Add(DetailedProjectList.Name);
MyProjects[i].Add(DetailedProjectList.StartDate);
MyProjects[i].Add(DetailedProjectList.EndDate);
MyProjects[i].Add(DetailedProjectList.Status)}
The Status values are
"Slow", "Normal", "Fast", "Suspended" and "" for unknown status.
Based on Status, I want to sort MyProject List Array.
What I have done is that I have created another List as below
List<string> sortProjectsBy = new List<string>(){"Slow", "Normal", "Fast", "", "Suspended"};
I tried as below to sort, however unsuccessful.
MyProjects = MyProjects.OrderBy(x => sortProjectsBy.IndexOf(4));
Can anyone hint in the right direction. Thanks.
I suggest you to create class Project and then add all the fields inside it you need. It's much nicer and scalable in the future. Then create a List or an Array of projects and use the OrderBy() function to sort based on the field you want.
List<Project> projects = new List<>();
// Fill the list...
projects.OrderBy(project => project.Status);
The field Status has to be a primitive type or needs to implement the interface IComparable in order for the sorting to work. I suggest you add an enum for Status with int values.
First consider maybe to use Enum for status and put it in a different file lite (utils or something) - better to work like that.
enum Status {"Slow"=1, "Normal", "Fast", "", "Suspend"}
Now about the filtering you want to achieve do it like this (you need to tell which attribute of x you are referring to. In this case is status)
MyProjects = MyProjects.OrderBy(x => x.status == enum.Suspend);
Read about enums :
https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/enum
Read about lambda expressions :
https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions
First of all, storing project details as List is not adivisable. You need to create a Custom Class to represent them.
For example,
public class DetailedProjectList
{
public string Name {get;set;}
public eStatus Status {get;set;}
// rest of properties
}
Then You can use
var result = MyProjects.OrderBy(x=> sortProjectsBy.IndexOf(x.Status));
For example
List<string> sortProjectsBy = new List<string>(){"Slow", "Normal", "Fast", "", "Suspended"};
var MyProjects= new List<DetailedProjectList>{
new DetailedProjectList{Name="abc1", Status="Fast"},
new DetailedProjectList{Name="abc2", Status="Normal"},
new DetailedProjectList{Name="abc3", Status="Slow"},
};
var result = MyProjects.OrderBy(x=> sortProjectsBy.IndexOf(x.Status));
Output
abc3 Slow
abc2 Normal
abc1 Fast
A better approach thought would be to use Enum to represent Status.
public enum eStatus
{
Slow,
Normal,
Fast,
Unknown,
Suspended
}
Then your code can be simplified as
var MyProjects= new List<DetailedProjectList>{
new DetailedProjectList{Name="abc1", Status=eStatus.Fast},
new DetailedProjectList{Name="abc2", Status=eStatus.Normal},
new DetailedProjectList{Name="abc3", Status=eStatus.Slow},
};
var result = MyProjects.OrderBy(x=> x.Status);
Ok so you have a collection of 20 items. Based on them you need to create a list of strings(20 DetailedProjectList items).
What you can do to solve your problem is to SORT YOUR COLLECTION before you create your list of strings. In this way your list of strings will be sorted.
But your code is not optimal at all. So you should concider optimization on many levels.
Lets say you have ProjectDetail class as follow:
private class ProjectDetail
{
public int Id {get;set;}
public string Name {get;set;}
DateTime StartDate {get;set;} = DateTime.Now;
DateTime EndDate {get;set;} = DateTime.Now;
public string Status {get;set;}
public string toString => $"{Id} - {Name} - {StartDate} - {EndDate} - {Status}";
}
Notice that I have added a toString attribute to make things easier, and I also have added default values.
Then your program could be like:
static void Main(string[] args)
{
var projectDetails = MockProjectItems();
Console.WriteLine("Before sortig:");
foreach (var item in projectDetails)
{
Console.WriteLine(item.toString);
}
var myProjects = projectDetails.OrderBy(p => p.Status).Select(p => p.toString);
Console.WriteLine("\n\nAfter sorting:");
foreach (var item in myProjects)
{
Console.WriteLine(item);
}
}
where the helper method is
private static List<ProjectDetail> MockProjectItems()
{
var items = new List<ProjectDetail>(20);
for(int i = 0; i < 20 ; i += 4){
items.Add(new ProjectDetail{Id = i, Name = "RandomName "+i, Status = "Slow"});
items.Add(new ProjectDetail{Id = i+1, Name = "RandomName "+(i+1), Status = "Normal"});
items.Add(new ProjectDetail{Id = i+2, Name = "RandomName "+(i+2), Status = "Fast"});
items.Add(new ProjectDetail{Id = i+3, Name = "RandomName "+(i+3), Status = "Suspended"});
}
return items;
}
Then your program should print the following:

Update collection from DbSet object via Linq

i know it is not complicated but i struggle with it.
I have IList<Material> collection
public class Material
{
public string Number { get; set; }
public decimal? Value { get; set; }
}
materials = new List<Material>();
materials.Add(new Material { Number = 111 });
materials.Add(new Material { Number = 222 });
And i have DbSet<Material> collection
with columns Number and ValueColumn
I need to update IList<Material> Value property based on DbSet<Material> collection but with following conditions
Only one query request into database
The returned data from database has to be limited by Number identifier (do not load whole database table into memory)
I tried following (based on my previous question)
Working solution 1, but download whole table into memory (monitored in sql server profiler).
var result = (
from db_m in db.Material
join m in model.Materials
on db_m.Number.ToString() equals m.Number
select new
{
db_m.Number,
db_m.Value
}
).ToList();
model.Materials.ToList().ForEach(m => m.Value= result.SingleOrDefault(db_m => db_m.Number.ToString() == m.Number).Value);
Working solution 2, but it execute query for each item in the collection.
model.Materials.ToList().ForEach(m => m.Value= db.Material.FirstOrDefault(db_m => db_m.Number.ToString() == m.Number).Value);
Incompletely solution, where i tried to use contains method
// I am trying to get new filtered collection from database, which i will iterate after.
var result = db.Material
.Where(x=>
// here is the reasonable error: cannot convert int into Material class, but i do not know how to solve this.
model.Materials.Contains(x.Number)
)
.Select(material => new Material { Number = material.Number.ToString(), Value = material.Value});
Any idea ? For me it is much easier to execute stored procedure with comma separated id values as a parameter and get the data directly, but i want to master linq too.
I'd do something like this without trying to get too cute :
var numbersToFilterby = model.Materials.Select(m => m.Number).ToArray();
...
var result = from db_m in db.Material where numbersToFilterBy.Contains(db_m.Number) select new { ... }

Cannot implicitly convert type '.List<AnonymousType#1>' to '.List<WebApplication2.Customer>'

In the following code that returns a list:
public List<Customer> GeAllCust()
{
var results = db.Customers
.Select(x => new { x.CustName, x.CustEmail, x.CustAddress, x.CustContactNo })
.ToList()
return results;
}
I get an error reporting that C# can't convert the list:
Error: Cannot implicitly convert type System.Collections.Generic.List<AnonymousType#1> to System.Collections.Generic.List<WebApplication2.Customer>
Why is that?
Here's a screenshot showing some additional information that Visual Studio provides in a tooltip for the error:
Is it right way to return some columns instead of whole table....?
public object GeAllCust()
{
var results = db.Customers.Select(x => new { x.CustName, x.CustEmail, x.CustAddress, x.CustContactNo }).ToList();
return results;
}
When you look the code:
x => new { ... }
This creates a new anonymous type. If you don't need to pull back only a particular set of columns, you can just do the following:
return db.Customers.ToList();
This assumes that Customers is an IEnumerable<Customer>, which should match up with what you are trying to return.
Edit
You have noted that you only want to return a certain subset of columns. If you want any sort of compiler help when coding this, you need to make a custom class to hold the values:
public class CustomerMinInfo
{
public string Name { get; set; }
public string Email { get; set; }
public string Address { get; set; }
public int? ContactNumber { get; set; }
}
Then change your function to the following:
public List<CustomerMinInfo> GetAllCust()
{
var results = db.Customers.Select(x => new CustomerMinInfo()
{
Name = x.CustName,
Email = x.Email,
Address = x.Address,
ContactNumber = x.CustContactNo
})
.ToList();
return results;
}
This will work, however, you will lose all relationship to the database context. This means if you update the returned values, it will not stick it back into the database.
Also, just to repeat my comment, returning more columns (with the exception of byte arrays) does not necessarily mean longer execution time. Returning a lot of rows means more execution time. Your function is returning every single customer in the database, which when your system grows, will start to hang your program, even with the reduced amount of columns.
You are selecting to an anonymous type, which is not a Customer.
If you want to do (sort of) this, you can write it like this:
return db.Customers.Select(x => new Customer { Name = x.CustName, Email = x.CustEmail, Address = x.CustAddress, ContactNo = x.ContactNo }).ToList();
This assumes the properties on your Customer object are what I called them.
** EDIT ** Per your comment,
If you want to return a subset of the table, you can do one of two things:
Return the translated form of Customer as I specified above, or:
Create a new class for your business layer that only has only those four fields, and change your method to return a List<ShrunkenCustomer> (assuming ShunkenCustomer is the name that you choose for your new class.)
GetAllCust() is supposed to return a List of Customer, Select New will create a list of Anonymous Types, you need to return a list of Customer from your query.
try:
var results = db.Customers.Select( new Customer{CustName = x.CustName}).ToList(); //include other fields
I guess Customer is a class you have defined yourself?
The my suggestion would be to do something like the following:
var results = db.Customers.Select(x => new Customer(x.Custname, x.CustEmail, x.CustAddress, x.CustContactNo)).ToList();
The reason is that you are trying to return a list of Customer but the results from your link is an anonymous class containing those four values.
This would of course require that you have a constructor that takes those four values.
Basically whatever u got in var type, loop on that and store it in list<> object then loop and achieve ur target.Here I m posting code for Master details.
List obj = new List();
var orderlist = (from a in db.Order_Master
join b in db.UserAccounts on a.User_Id equals b.Id into abc
from b in abc.DefaultIfEmpty()
select new
{
Order_Id = a.Order_Id,
User_Name = b.FirstName,
Order_Date = a.Order_Date,
Tot_Qty = a.Tot_Qty,
Tot_Price = a.Tot_Price,
Order_Status = a.Order_Status,
Payment_Mode = a.Payment_Mode,
Address_Id = a.Address_Id
});
List<MasterOrder> ob = new List<MasterOrder>();
foreach (var item in orderlist)
{
MasterOrder clr = new MasterOrder();
clr.Order_Id = item.Order_Id;
clr.User_Name = item.User_Name;
clr.Order_Date = item.Order_Date;
clr.Tot_Qty = item.Tot_Qty;
clr.Tot_Price = item.Tot_Price;
clr.Order_Status = item.Order_Status;
clr.Payment_Mode = item.Payment_Mode;
clr.Address_Id = item.Address_Id;
ob.Add(clr);
}
using(ecom_storeEntities en=new ecom_storeEntities())
{
var Masterlist = en.Order_Master.OrderByDescending(a => a.Order_Id).ToList();
foreach (var i in ob)
{
var Child = en.Order_Child.Where(a => a.Order_Id==i.Order_Id).ToList();
obj.Add(new OrderMasterChild
{
Master = i,
Childs = Child
});
}
}

LINQ Dictionary Non-Distinct Values

I am struggeling a little with trying to write the LINQ query to do the following,
public class MyClass
{
public int ID {get; set;}
public int Name {get; set;}
public IEnumerable<string> SomeIEnumerable {get; set;}
}
I am trying to create the object using LINQ from a DB query that would return something as follows,
ID Name SomeString
0 NameA "MyValue"
1 NameB "Value1"
1 NameB "Value2"
2 NameC "Value"
The LINQ is pretty easy to write as well,
from DataRow row in dataSet.Tables[0].Rows
select new MyClass
{
ID = (int)row["ID"],
Name = row["Name"].ToString(),
SomeIEnumerable = new List<string>{ row["SomeString"].ToString() }
};
The tricky part is how do I turn this into a Dictionary where
dictionary[1].SomeIEnumerable = {"Value1", "Value2"}
A simple ToDictionary would throw an ArgumentException
The main issue here, is how do I handle the fact that the keyis not distinct, and be able to lookup in the temporary dictionary the existing value to add to it the values I am interested in.
You can also your grouping, and then use IGrouping to retrieve the list of items in the group
Sample code
var simulatedData = Enumerable.Range(0,10).Select(x=> new {Key=x%3+1, Index=x});
var dict = simulatedData.GroupBy(x=>x.Key).ToDictionary(x=>x.Key, x=>x.Select(t=>t.Index));
Instead of calling ToDictionary, call ToLookup. :)
Answer thanks to #Tilak is,
from DataRow row in dataSet.Tables[0].Rows
group row by new
{
ID = (int) row["ID"],
Name = row["Name].ToString()
} into rowGroup
select new MyClass
{
ID = rowGroup.Key.ID,
Name = rowGroup.Key.Name,
SomeIEnumerable =
from row in rowGroup
select row["SomeString"].ToString()
};

Categories

Resources