Using Linq to select multiple items per iteration? - c#

Per iteration, this query creates an EmailRecipient for each populated address. Can it be done without the multiple iteration?
var addedRecipients = (from oldRecip in oldEmailRecipients
where !string.IsNullOrWhiteSpace(oldRecip.EmailAddress1)
select new EmailRecipient
{
UserName = oldRecip.UserName,
EmailAddress = oldRecip.EmailAddress1
}
).Union(from oldRecip in oldEmailRecipients
where !string.IsNullOrWhiteSpace(oldRecip.EmailAddress2)
select new EmailRecipient
{
UserName = oldRecip.UserName,
EmailAddress = oldRecip.EmailAddress2
});

You can use SelectMany extension method:
var addedRecipients = oldEmailRecipients.SelectMany(e=>
{
var result= new List<EmailRecipient>();
if(!string.IsNullOrWhiteSpace(e.EmailAddress1))
{
result.Add(new EmailRecipient
{
UserName = e.UserName,
EmailAddress = e.EmailAddress1
});
}
if(!string.IsNullOrWhiteSpace(e.EmailAddress2))
{
result.Add(new EmailRecipient
{
UserName = e.UserName,
EmailAddress = e.EmailAddress2
});
}
return result;
});
Update
The solution that I show above only works in Linq to Objects. Your comment suggest me you are using EF. A simple solution could be call AsEnumerable method before SelectMany to make the switch to Linq to Objects, but that could end harming your performance if you are not filtering your recipients first.
Another solution could be selecting only data that you need first from you server before of call SelectMany to not load other columns you don't need in this case:
...Where(...)
.Select(r=>new{UserName=r.UserName,
EmailAddress1=r.EmailAddress1,
EmailAddress2=r.EmailAddress2 })
.AsEnumerable()
.SelectMany(...);

Sticking with query syntax, and making sure to only process oldEmailRecipients items who have either a non-null/whitespace EmailAddress1 or a non-null/whitespace EmailAddress2:
var addedRecipients =
from oldEmail in oldEmailRecipients
let hasEmail1 = !string.IsNullOrWhiteSpace(oldEmail.EmailAddress1)
let hasEmail2 = !string.IsNullOrWhiteSpace(oldEmail.EmailAddress2)
where hasEmail1 || hasEmail2
let emailUserNameCombos = hasEmail1 && hasEmail2
? new[]
{
new {Email = oldEmail.EmailAddress1, oldEmail.UserName},
new {Email = oldEmail.EmailAddress2, oldEmail.UserName}
}
: hasEmail1
? new[] {new {Email = oldEmail.EmailAddress1, oldEmail.UserName}}
: new[] {new {Email = oldEmail.EmailAddress2, oldEmail.UserName}}
from emailUsername in emailUserNameCombos
select new EmailRecipient
{
UserName = emailUsername.UserName,
EmailAddress = emailUsername.Email
};

You can build an inline array to add both emails and flatten them out using SelectMany.
var addedRecipients = from oldRecip in oldEmailRecipients
let emails =
new[] {oldRecip.EmailAddress1, oldRecip.EmailAddress2}.Where(e => !string.IsNullOrWhiteSpace(e))
from email in emails
where emails.Any()
select new EmailRecipient
{
UserName = oldRecip.UserName,
EmailAddress = email
};

When your EmailRecipient has more than two email address then you could do this:
// Just building a pseudo dataclass
List<Recipient> oldEmailRecipients = Enumerable.Range(1, 10).Select(item => new Recipient()
{
Name = "Recipient" + item,
EmailAddress1 = "pseudo" + item + "#gmail.com",
EmailAddress2 = "pseudo" + (item + 1) + "#gmail.com",
//EmailAddress3 = "pseudo" + (item + 2) + "#gmail.com",
EmailAddress3 = "",
EmailAddress4 = "pseudo" + (item + 3) + "#gmail.com",
} ).ToList( )
// create anonymous object for each recipient and a list of valid adresses
var query = from mailRecipients in oldEmailRecipients
select new
{
Name = mailRecipients.Name,
Addresses = new List<string>()
{
mailRecipients.EmailAddress1,
mailRecipients.EmailAddress2,
mailRecipients.EmailAddress3,
mailRecipients.EmailAddress4
}.Where(item => string.IsNullOrEmpty( item ) == false )
};
// create an EmailRecipient for each valid combination
var final = from item in query
from address in item.Addresses
select new EmailRecipient
{
Name = item.Name,
Address = address
};

Related

Checking for existing records on database wen i call web api

I am calling a web api and saving the records on the database through the controller, i want each time im calling the api to check if the record exists in the database if yes then dont save, if not then save.
var client = new WebClient();
var text = client.DownloadString("https://www.test.com/api/all-users?name=testusername%20&pass=334432");
var wclients = JsonConvert.DeserializeObject<dynamic>(text);
List<apicli> list1 = new List<apicli>();
var clie = new apicli();
if (wclients.message == "success")
{
var data = wclients.data;
//var account = wclients.account;
ViewBag.test = data;
foreach(var item in ViewBag.test)
{
clie.Email = item.email;
clie.Name = item.name;
clie.Aff = item.affiliated_id;
foreach(var item1 in #item.account.real)
{
clie.Login = item1.login;
clie.password = item1.pass;
}
list1.Add(clie);
db.apiclis.AddRange(list1);
db.SaveChanges();
};
}
I would assume you need something like this, although you need to check what is the unique id of each record:
foreach(var item in data){
var c = new apicli {
Email = item.email,
Name = item.name,
Aff = item.affiliated_id
Login = item.account.real.LastOrDefault()?login??"",
Login = item.account.real.LastOrDefault()?pass??""
}
if(!db.apiclis.Any(a => a.Email == c.Email && a.Name == c.Name && a.Aff == c.Aff)){
db.apiclis.Add(c);
}
}
Here I assume that email+name+aff = unique identificator.

Merge two Lists of same type with diff values and avoid duplicates

I have two lists of same type with different key value pairs,
List1 has "isPermanent = true" and List2 has false value and also
List1 has an extra key "nextVacationDate".
Im trying to do union of these as below but im afraid I will still get the duplicates because of different values. I need to merge both lists in to one list and order by List1 first (Permanent employees first)..is there a better way to do this using LINQ?
public newList1 List1(string abcd)
{
var result = serviceMethod1(abcd);
var newList1 = new List<emp>();
if (result == null) return null;
newList.AddRange(
result.Select(x => new Model
{
firstName = x.FName,
secondName = x.SName,
address = x.Address,
employeeId = x.EmpId,
isPermanent = true,
nextVacationDate =x.VacDt,
salary = x.Bsalary
}));
return newList1;
}
public newList2 List2(string defg)
{
var result = serviceMethod2(defg);
var newList2 = new List<emp>();
if (result == null) return null;
newList.AddRange(
result.Select(x => new Model
{
firstName = x.FName,
secondName = x.SName,
address = x.Address,
employeeId = x.EmpId,
isPermanent = false,
salary = x.Bsalary
}));
return newList2;
}
private List<emp> EmployyeList(List<emp> newList1, List<emp> newList2)
{
var sortedEmpList1 = newList1.OrderBy(i => i.Fname);
var sortedEmpList2 = newList2.OrderBy(i => i.Fname);
List<MeterModel> combinedList = newList1.Union(newList2) as List<emp>;
return combinedList;
}
You can filter the 2nd list to avoid duplicates:
newList1.Union(newList2.Where(emp2 => !newList1.Any(emp1 => emp1.employeeId == emp2.employeeId)))

cannot convert from 'void' to 'project.Models.tblUserTypes.tblUsersTypeC'

I want to create new object with if statement in lambda ForEach, what is this code problem?
List<tblUsersTypeC> usertype = new List<tblUsersTypeC>();
usertype.Add(userToInsertList.ForEach(o =>
{
if ((o.Counts.FollowedBy + o.Counts.Follows + o.Counts.Media) == 0)
new tblUsersTypeC { isPrivite = true, UserName = o.Username, WebsiteUrl = o.Website };
else
new tblUsersTypeC { isPrivite = false, UserName = o.Username, WebsiteUrl = o.Website };
}));
List<T>.ForEach is intended to iterate through the list items and do something, using their current state. Actually, your question is yet another sample, which shows, that ForEach method is unnatural, non-obvious replacement for foreach operator.
To get new list of items, based on items from the current list, use Select/ToList extensions:
var usertype = userToInsertList
.Select(o => new tblUsersTypeC
{
isPrivite = (o.Counts.FollowedBy + o.Counts.Follows + o.Counts.Media) == 0,
UserName = o.Username,
WebsiteUrl = o.Website
})
.ToList();
Try this:
List<tblUsersTypeC> usertype = userToInsertList.Select(o => new tblUsersTypeC()
{
isPrivite = ((o.Counts.FollowedBy + o.Counts.Follows + o.Counts.Media) == 0),
UserName = o.Username,
WebsiteUrl = o.Website
}).ToList();
Use
userToInsertList.Select( o=> (condition) ? value1: value2);
or
userToInsertList.Select( o=> { if(condition) { return value1} else {return value2;}});
List.ForEach doesn't return any thing.

Nested group by based on flatten record set

I have the following responses from the API. How can I group them into the following structure?
Student[]
- Name
- Classes[]
- ClassName
- ClassId
- ClassCategories[]
- CategoryName
- CategoryWeight
- Assignments[]
- AssignmentName
- Score
I was managed to group them until the "Classes" level but unable to get the ClassCategories for each of the classes
var data = (from result in results
group result by new { result.StudentId, result.FirstName, result.LastName, result.MiddleInitial }
into StudentGroup
select new GroupedStudent
{
StudentId = StudentGroup.Key.StudentId,
FullName = string.Format("{0} {1} {2}", StudentGroup.Key.FirstName, StudentGroup.Key.MiddleInitial, StudentGroup.Key.LastName).Replace(" ", " "),
Classes = from result in results
group result by new { result.ClassId, result.ClassName } into ClassGroup
select new groupedClass
{
ClassName = ClassGroup.Key.ClassName,
ClassId = ClassGroup.Key.ClassId,
ClassCategories = ...
})
}).ToList();
Can anyone please assists me? Thank you.
First, you have make ClassGroup from StudentGroup not from results.
Classes = from s in StudentGroup group result by new { s.ClassId, s.ClassName } into ClassGroup
The complete linq query is as follows:
var data =
(from result in results
group result by new { result.StudentId, result.FirstName, result.LastName, result.MiddleInitial } into StudentGroup
select new
{
StudentId = StudentGroup.Key.StudentId,
FullName = string.Format("{0} {1} {2}", StudentGroup.Key.FirstName, StudentGroup.Key.MiddleInitial, StudentGroup.Key.LastName).Replace(" ", " "),
Classes = (from s in StudentGroup
group s by new { s.ClassId, s.ClassName } into ClassGroup
select new
{
ClassId = ClassGroup.Key.ClassId,
ClassName = ClassGroup.Key.ClassName,
ClassCategories = (from c in ClassGroup
group c by new { c.CategoryName, c.CategoryWeight } into CategoryGroup
select new
{
CategoryName = CategoryGroup.Key.CategoryName,
CategoryWeight = CategoryGroup.Key.CategoryWeight,
Assignments = (from ct in CategoryGroup
group ct by new { ct.AssignmentName, ct.Score } into AssingnmentGroup
select new
{
AssignmentName = AssingnmentGroup.Key.AssignmentName,
Score = AssingnmentGroup.Key.Score
}).ToList()
}).ToList()
}).ToList()
}).ToList();
For example, if you want to access to the first Assignment's score, you can get it like this:
var student = data.FirstOrDefault();
var score = student.Classes[0].ClassCategories[0].Assignments[0].Score;
This is usually how I do It.
Create a class to store your data
Create a list of that class type
In your case instead of string dataRow maybe you can use a sub class
.
// get data from webservice
var json = webClient.DownloadString(url);
var values = JsonConvert.DeserializeObject<JArray>(json);
// create a list to save all the element
List<myClass> classList = new List<myClass>();
// process every row
foreach (string dataRow in values)
{
string[] dataField = dataRow.Split(',');
// have a constructor to assign each value to this element
myClass ctROW = new myClass(dataField);
classList.add(ctROW );

replacement for roundtrip serialize-deserialize

I've a table with over 100 column (including blobs) and I want to make a copy of object only with a few filled columns.
right now I'm doing it by selecting needed columns and doing a round-trip serialize and deserialize with Json.NET which is not efficient. what's the best way to handle this scenario?
BL.Case mCase;
BL.Case temp = db.Cases.Select(
xx => new
{
CaseID = xx.CaseID,
FirstName = xx.FirstName,
LastName = xx.LastName
}).FirstOrDefault(u => u.CaseID == CaseID);
mCase = Newtonsoft.Json.JsonConvert.DeserializeObject<BL.Case>(Newtonsoft.Json.JsonConvert.SerializeObject(temp));
Use AutoMapper.
Do something like this:
BL.Case mCase = null;
var temp = db.Cases.Select(
xx => new
{
CaseID = xx.CaseID,
FirstName = xx.FirstName,
LastName = xx.LastName
}).FirstOrDefault(u => u.CaseID == CaseID);
if (temp != null)
{
mCase = Mapper.DynamicMap<BL.Case>(temp);
}
Another solution that requires a bit more code (but might perform better) is to do the following:
In case you need a single item:
BL.Case mCase = null;
var temp = db.Cases.Select(
xx => new
{
CaseID = xx.CaseID,
FirstName = xx.FirstName,
LastName = xx.LastName
}).FirstOrDefault(u => u.CaseID == CaseID);
if (temp != null)
{
mCase = new Case()
{
CaseID = temp.CaseID,
FirstName = temp.FirstName,
LastName = temp.LastName,
};
}
If you need multiple items:
var temp = db.Cases.Select(
xx => new
{
CaseID = xx.CaseID,
FirstName = xx.FirstName,
LastName = xx.LastName
}); //Here you can filter your query if you want using Where
var result = temp
.ToList() //This will actually execute the query on the database
.Select(x => new Case() //Now you can do this since now we are working on in-memory data
{
CaseID = x.CaseID,
FirstName = x.FirstName,
LastName = x.LastName
});

Categories

Resources