Updating Collection from other collection for matching items - linq

I have two collections as mentioned below. I have update two properties of "trade" from the other collection "refData" if those values exists in "refData".
Model:
class Trade
{
public int Id { get; set; }
public string PayIndex { get; set; }
public string RecIndex { get; set; }
public string PayCurrency { get; set; }
public string RecCurrency { get; set; }
}
class RefData
{
public string IndexLabel { get; set; }
public string Symbol { get; set; }
}
Sample Date:
var refData = new List<RefData>
{
new RefData { IndexLabel = "A1", Symbol="ABC1"},
new RefData { IndexLabel = "A2", Symbol="ABC2"},
new RefData { IndexLabel = "B1", Symbol="BCD1"},
new RefData { IndexLabel = "B2", Symbol="BCD2"},
};
var trades = new List<Trade>
{
new Trade { Id = 1, PayIndex = "A1", RecIndex = "B1"},
new Trade { Id = 2, PayIndex = "A2", RecIndex = ""},
new Trade { Id = 3, PayIndex = "", RecIndex = "B2"},
new Trade { Id = 4, PayIndex = "A3", RecIndex = "B3"}
};
I want to update PayCurrency and RecCurrency of "trades" with Symbol property of "refData" if trade's PayIndex and RecCurrency exists in "refData".
Output:
var Output = new List<Trade>
{
new Trade { Id = 1, PayIndex = "A1", RecIndex = "B1", PayCurrency = "ABC1", RecCurrency="BCD1"},
new Trade { Id = 2, PayIndex = "A2", RecIndex = "", PayCurrency = "ABC2", RecCurrency=""},
new Trade { Id = 3, PayIndex = "", RecIndex = "B2", PayCurrency = "", RecCurrency="BCD2"},
new Trade { Id = 4, PayIndex = "A3", RecIndex = "B3", PayCurrency = "", RecCurrency=""}
};

For c#6 and above you can do like below
var result = trades.Select(t => new Trade() {
Id= t.Id,
PayIndex = t.PayIndex,
RecIndex = t.RecIndex,
PayCurrency = refData.SingleOrDefault(r => r.IndexLabel.ToLower().Equals(t.PayIndex.ToLower()))?.Symbol ?? "",
RecCurrency = refData.SingleOrDefault(r => r.IndexLabel.ToLower().Equals(t.RecIndex.ToLower()))?.Symbol ?? ""
}).ToList();
For Older versions
var result = trades.Select(t => new Trade() {
Id= t.Id,
PayIndex = t.PayIndex,
RecIndex = t.RecIndex,
PayCurrency = refData.SingleOrDefault(r => r.IndexLabel.ToLower().Equals(t.PayIndex.ToLower())) != null ? refData.SingleOrDefault(r => r.IndexLabel.ToLower().Equals(t.PayIndex.ToLower())).Symbol : "",
RecCurrency = refData.SingleOrDefault(r => r.IndexLabel.ToLower().Equals(t.RecIndex.ToLower())) != null ? refData.SingleOrDefault(r => r.IndexLabel.ToLower().Equals(t.RecIndex.ToLower())).Symbol : ""
}).ToList();
Here is working example
Update using #SAJ answer
var output = (from r in trades
join p in refData on r.PayIndex equals p.IndexLabel
into g1
from s in g1.DefaultIfEmpty()
join t in refData on r.RecIndex equals t.IndexLabel into g2
from a in g2.DefaultIfEmpty()
select Trade { Id=r.Id,PayIndex=r.PayIndex,RecIndex=r.RecIndex, RecCurrency = a != null ? a.Symbol : "", PayCurrency = s != null ? s.Symbol : ""}).ToList();

You can try this
var output = (from r in trades
join p in refData on r.PayIndex equals p.IndexLabel
into g1
from s in g1.DefaultIfEmpty()
join t in refData on r.RecIndex equals t.IndexLabel into g2
from a in g2.DefaultIfEmpty()
select new { r, RecSymbol = a?.Symbol, PaySymbol = s?.Symbol }).ToList();
output.ForEach(o =>
{
o.r.PayCurrency = o.PaySymbol;
o.r.RecCurrency = o.RecSymbol;
});

Related

LINQ - Group By child property

I have a list of users, each user has string array property called Tags. I am trying to get a unique list of tags and a total count, any idea what I a missing here? I am using LinqPad to write my test query, please see the example code:
void Main()
{
List<User> users = new List<User>(){
new User {Id = 1, Tags = new string[]{"tag1", "tag2"}},
new User {Id = 2, Tags = new string[]{"tag3", "tag7"}},
new User {Id = 3, Tags = new string[]{"tag7", "tag8"}},
new User {Id = 4, Tags = new string[]{"tag1", "tag4"}},
new User {Id = 5 },
};
var uniqueTags = users.Where(m=>m.Tags != null).GroupBy(m=>m.Tags).Select(m=> new{TagName = m.Key, Count = m.Count()});
uniqueTags.Dump();
// RESULT should BE:
// tag1 - Count(2)
// tag2 - Count(1)
// tag3 - Count(1)
// tag4 - Count(1)
// tag7 - Count(2)
// tag8 - Count(1)
}
public class User{
public int Id {get;set;}
public string[] Tags {get;set;}
}
You can flatten to IEnumerable<string> before grouping:
var uniqueTags = users.SelectMany(u => u.Tags ?? new string[0])
.GroupBy(t => t)
.Select(g => new { TagName = g.Key, Count = g.Count() } );
LINQPad C# Expression version:
new[] {
new { Id = 1, Tags = new[] { "tag1", "tag2" } },
new { Id = 2, Tags = new[] { "tag3", "tag7" } },
new { Id = 3, Tags = new[] { "tag7", "tag8" } },
new { Id = 4, Tags = new[] { "tag1", "tag4" } },
new { Id = 5, Tags = (string[])null }
}
.SelectMany(u => u.Tags ?? Enumerable.Empty<string>())
.GroupBy(t => t)
.Select(g => new { TagName = g.Key, Count = g.Count() } )

Getting all elements concatenate in a string with LINQ

I have the following classes:
public class People
{
public int ID { get; set; }
public List<Right> Rights { get; set; }
}
public class Right
{
public int ID { get; set; }
public int Val { get; set; }
}
With the following values:
People: ID: 1
Right:
ID: 1
Val: 5
Right:
ID: 2
Val: 4
I would like to retrieve all Val rights (in a single string) for the people.
So for people with ID 1 : getting "5,4".
List<People> list = ...;
string result = string.Join(",", (people.First(p => p.ID == 1)).Rights.Select(r => r.Val));
Example :
List<People> people = new List<People>()
{
new People()
{
ID = 1, Rights = new List<Right>()
{
new Right() { ID = 1, Val = 5 },
new Right() { ID = 2, Val = 10 },
}
},
new People()
{
ID = 2, Rights = new List<Right>()
{
new Right() { ID = 1, Val = 6 },
new Right() { ID = 2, Val = 11 },
}
}
};
string result = string.Join(",", (people.First(p => p.ID == 1)).Rights.Select(r => r.Val));
Console.WriteLine(result);
Output: 5, 10
You can use SelectMany to flatten a structure, so something like:
var vals = people.Where(p => p.ID == 1)
.SelectMany(p => p.Rights.Select(r => Val));
var str = String.Join(",", vals.Select(v => v.ToString());

How to fix issue with group join in linq / lambda expression?

I have the following Linq/Lambda expression to associate COMMENTS and ATTACHMENTS to RECORDS (these are the names of tables in database).
var comments = repositoryBase.Query<Comments>()
.Select(x => x);
var attachments = repositoryBase.Query<Attachment>()
.Where(x => x.DeletedFlag == false)
.Select(x => x);
var recordItems = repositoryBase.Query<Record>()
.Where(x => x.DepartmentId == departmentIdId && x.DeletedFlag == false);
recordItems = recordItems
.Where(x => x.Featured == true && (x.PhaseTypeId == 2 || x.PhaseTypeId == 3)); // filtering
var recordComments = recordItems
.GroupJoin(comments,
itm => new { a = 1, b = itm.RecordId },
c => new { a = c.TypeId, b = c.Id },
(itm, c) => new { c, itm })
.SelectMany(x => x.c.DefaultIfEmpty(), (x, y) => new
{
Comments = (y != null) ? true : false,
CommentsCount = x.c.Count(),
RecordId = x.itm.RecordId,
Featured = x.itm.Featured,
Id = x.itm.RecordId,
PhaseName = x.itm.PhaseType.PhaseName,
x.itm.ProductionDate,
x.itm.PublishedDate,
Title = x.itm.RecordTitle,
x.itm.UpdatedDate
}).Distinct();
where TypeId and Id in c => new { a = c.TypeId, b = c.Id } are fields in comments on which the group join(left outer join) is done.
var recordAttachments = recordComments
.GroupJoin(attachments,
itm => new { a = 1, b = itm.RecordId },
at => new { a = at.ContentType, b = at.ContentId },
(itm, at) => new { at, itm})
.SelectMany(x => x.at.DefaultIfEmpty(), (x, y) => new
{
Attachments = (y != null) ? true : false,
AttachmentsCount = x.at.Count(),
AttachmentTitle = y.FileName,
AttachmentId = (y != null) ? y.AttachmentId : 0,
TypeId = (y != null) ? y.ContentType : 0,
ItemId = (y != null) ? y.ContentId : 0,
Comments = x.itm.Comments,
CommentsCount = x.itm.CommentsCount,
Featured = x.itm.Featured,
Id = x.itm.RecordId,
PhaseName = x.itm.PhaseName,
x.itm.ProductionDate,
x.itm.PublishedDate,
Title = x.itm.Title,
x.itm.UpdatedDate
}).Distinct().ToList();
But with the last lambda expression there is an issue that if there are two attachment for the same record, the record with attachment gets duplicated (not in database but in view).
As shown here
"Data": [
{
"typeid": 1,
"typename": "Record Scan",
"id": 3071,
"title": "Late Outcomes",
"publishdate": "3/4/2013",
"featured": true,
"productiondate": "",
"phasename": "Board",
"updateddate": "4/29/2013",
"updateddateforsorting": "2013-04-29T19:44:29.47",
"comments": true,
"numofcomments": 4,
"attachment": true,
"numofattachments": 2,
"attachments": [
{
"attachmentid": 371,
"typeid": 1,
"id": 0,
"title": "Cardio_David.docx",
"name": null,
"createddate": "0001-01-01T00:00:00"
},
{
"attachmentid": 434,
"typeid": 1,
"id": 0,
"title": "blanks in password field.docx",
"name": null,
"createddate": "0001-01-01T00:00:00"
}
]
},
{
"typeid": 1,
"typename": "Record Scan",
"id": 3071,
"title": "Late Outcomes",
"publishdate": "3/4/2013",
"featured": true,
"productiondate": "",
"phasename": "Board",
"updateddate": "4/29/2013",
"updateddateforsorting": "2013-04-29T19:44:29.47",
"comments": true,
"numofcomments": 4,
"attachment": true,
"numofattachments": 2,
"attachments": [
{
"attachmentid": 371,
"typeid": 1,
"id": 0,
"title": "Cardio_David.docx",
"name": null,
"createddate": "0001-01-01T00:00:00"
},
{
"attachmentid": 434,
"typeid": 1,
"id": 0,
"title": "blanks in password field.docx",
"name": null,
"createddate": "0001-01-01T00:00:00"
}
]
}
]
NB- this is a sample data ignore the field names and values
I 've editted the last code recordAttachment as
var recordAttachment= from rc in recordComments
join at in attachments on rc.RecordId equals at.ContentId into ra
select new { Comments = rc.Comments, CommentsCount = rc.CommentsCount Featured = rc.Featured, Id = rc.RecordId, PhaseName = rc.PhaseName, rc.ProductionDate, jac.PublishedDate, Source = jac.Source, Title = rc.Title, rc.UpdatedDate, AttachmentCount = ra.Count(), Attachments = ra, IsAttachment = (ra.Count() != null) ? true : false };
This returns record and associated attachments. Now i need to Map this data to a view model..
public class FlaggedItemModel
{
public int typeid { get; set; }
public string typename { get; set; }
public int id { get; set; }
public string title { get; set; }
public string publishdate { get; set; }
public bool featured { get; set; }
public string productiondate { get; set; }
public string phasename { get; set; }
public string updateddate { get; set; }
public DateTime updateddateforsorting { get; set; }
public bool comments { get; set; }
public int numofcomments { get; set; }
public bool attachment { get; set; }
public int numofattachments { get; set; }
public IList<AttachmentModel> attachments { get; set; }
}
I tried this code but not working
var recordlist = journalArticleAttachments.Select(x => new FlaggedItemModel() { attachments = x.Attachments.Where(z => z.ContentId == x.Id).Select(jaa => new AttachmentModel() { attachmentid = jaa.AttachmentId, typeid = jaa.ContentType, title = jaa.FileName }).ToList(), numofcomments = x.CommentsCount, comments = x.Comments, featured = x.Featured, id = x.Id, phasename = x.PhaseName, productiondate = (x.ProductionDate.HasValue) ? x.ProductionDate.Value.ToShortDateString() : string.Empty, publishdate = (x.PublishedDate.HasValue) ? x.PublishedDate.Value.ToShortDateString() : string.Empty, title = x.Title, typeid = 1, typename = "Journal Scan", updateddate = x.UpdatedDate.ToShortDateString(), updateddateforsorting = x.UpdatedDate });
How to fix this issue?
You are getting this error because you are trying to get the database to convert a datetime to a formatted short date string, which it doesn't know how to do. I would recommend changing your model to have a production date be a datetime field, and formatting it appropriately in your view. Changing the datetime to a string in the controller (or DAL) isn't the appropriate place to do it.
Alternatively, if you insist on having the shortdate in your viewmodel, have the query initally return the field as date time, then call .ToList() on the query, and then project that result into your final view so that the result will be returned from the database as datetime, and then you can have it convert it to shortdate in C#. Something like this:
var recordlist = journalArticleAttachments.Select(x=>new { ..., publishdate, ...}).ToList().Select(x=>new { ..., publishdate = (publishdate==null) ? publishdate.ToShortDateString() : string.Empty, ...});

Guidance on Seeding Data Entity Framework

I'm having problems with seeding some constant data when dropcreatedatabase is initiated. I have looked over some similar questions but I'm having difficulties understanding why my code isn't working.
All help is greatly appreciated (c:
Here is my SampleData Model with Seed Data
public class SampleData : DropCreateDatabaseIfModelChanges<fotmEntities>
{
protected override void Seed(fotmEntities context)
{
var genres = new List<Genre>
{
new Genre { Name = "Specials" },
new Genre { Name = "Online" },
new Genre { Name = "Services" },
new Genre { Name = "Food" },
new Genre { Name = "Misc" },
new Genre { Name = "Auto" }
};var localities = new List<Locality>
{
new Locality { Name = "Location1" },
new Locality { Name = "Location2" },
new Locality { Name = "Location3" },
new Locality { Name = "Location4" },
};
new List<Discount>
{
new Discount { Title = "Title A", Genre = genres.SingleOrDefault(g => g.Name == "Specials"), Information = "8.99M", Locality = localities.Single(a => a.Name == "Location1"), DiscountArtUrl = "/Content/img/placeholder.gif" },
new Discount { Title = "Title B", Genre = genres.SingleOrDefault(g => g.Name == "Specials"), Information = "8.99M", Locality = localities.Single(a => a.Name == "Location2"), DiscountArtUrl = "/Content/img/placeholder.gif" },
new Discount { Title = "Title C", Genre = genres.SingleOrDefault(g => g.Name == "Services"), Information = "8.99M", Locality = localities.Single(a => a.Name == "Location3"), DiscountArtUrl = "/Content/img/placeholder.gif" },
new Discount { Title = "Title D", Genre = genres.SingleOrDefault(g => g.Name == "Food"), Information = "8.99M", Locality = localities.Single(a => a.Name == "Location4"), DiscountArtUrl = "/Content/img/placeholder.gif" },
new Discount { Title = "Title E", Genre = genres.SingleOrDefault(g => g.Name == "Misc"), Information = "8.99M", Locality = localities.Single(a => a.Name == "Location1"), DiscountArtUrl = "/Content/img/placeholder.gif" },
new Discount { Title = "Title F", Genre = genres.SingleOrDefault(g => g.Name == "Auto"), Information = "8.99M", Locality = localities.Single(a => a.Name == "Location2"), DiscountArtUrl = "/Content/img/placeholder.gif" },
}.ForEach(a => context.Discounts.Add(a));
Here is my context model:
using System.Data.Entity;
public class fotmEntities : DbContext
{
public DbSet<Discount> Discounts { get; set; }
public DbSet<Genre> Genres { get; set; }
public DbSet<Locality> Localities { get; set; }
}
and my Global.asx
protected void Application_Start()
{
System.Data.Entity.Database.SetInitializer(
new fotm.Models.SampleData());
AreaRegistration.RegisterAllAreas();
RegisterGlobalFilters(GlobalFilters.Filters);
RegisterRoutes(RouteTable.Routes);
}

Using the ALL operator in linq to filter child items of EntitySet

I have a two objects as follows:
public class Item
{
public int ItemId {get;set;}
public string ItemName {get;set;}
public List<Tag> ItemTags {get;set;}
public DateTime DateCreated {get;set;}
}
public class Tag
{
public int TagId {get;set;}
public string TagName {get;set;}
}
These are LINQ-to-SQL objects, so the ItemTags will be an EntitySet.
I am trying to perform a search query where a user can provide a comma delimited list of tags as a search filter.
How do I filter my list of items to those which contains all of the tags in the comma delimited list.
EDIT2
e.g.
Item1 has tags of Apple, Banana, Orange
Item2 has tags of Banana, Orange
Item3 has tags of Pineapple, Orange
If the tag filter is "Banana, Orange" I need the results to be Item1 and Item2.
/EDIT2
This is what I have tried thus far:
string tags = "Manchester United,European Cup,2008";
List<string> tagsList = tags.Trim().ToLower()
.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Distinct(StringComparer.CurrentCultureIgnoreCase)
.ToList();
List<Item> itemList = ItemRepository.FetchAll();
var query = itemList
.OrderByDescending(p => p.DateCreated)
.ToList();
if (tagsList.Count() > 0)
{
query = query
.Where(p => p.ItemTags
.Select(q => q.TagName.ToLower())
.All(r => tagsList.Contains(r)))
.ToList();
}
However, this doesn't seem to work. Any ideas on what I am doing wrong please?
EDIT1: tags are trimmed and are 'lowercased'.
That because you're puting the tags from the items to lowercase, but not the searched tags.
With this modification it should work:
List<string> tagsList = tags
.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => s.ToLower())
.Distinct()
.ToList();
EDIT: OK, I see what the problem is: you're doing it backwards. You're searching for items that have only the tags that you're looking for.
Try that instead:
query =
(from item in query
let itemTags = p.ItemTags.Select(it => it.TagName.ToLower())
where tags.All(t => itemTags.Contains(t))
select item).ToList();
UPDATE: here's a version with the lambda syntax. It's pretty ugly because of the temporary anonymous type, but that's how the let clause translates to lambda...
query =
query.Select(item => new { item, itemTags = item.ItemTags.Select(it => it.TagName.ToLower()) })
.Where(x => tagsList.All(t => x.itemTags.Contains(t)))
.Select(x => x.item)
.ToList();
I think you need to do something like this:
var query = itemList.OrderByDescending(p => p.DateCreated).ToList();
var results = query.Where(i => i.ItemTags
.All(it => tagsList.Contains(it.TagName.ToLower())));
Then results should then be a list of matching items.
PS. Your code shows you fetching itemList as a List from your repository and then sorting by date created. This means the sorting isn't being done in the database. Once you turn something into a List you give up the benefits of deferred execution as you will bring back the entire collection into memory.
EDIT: Here's the test code to prove it works in Linq to Objects:
public class Item
{
public int ItemId { get; set; }
public string ItemName { get; set; }
public List<Tag> ItemTags { get; set; }
public DateTime DateCreated { get; set; }
}
public class Tag
{
public int TagId { get; set; }
public string TagName { get; set; }
}
class Program
{
static void Main(string[] args)
{
RunTags();
}
private static void RunTags()
{
Item i1 = new Item()
{
ItemId = 1,
ItemName = "Item1",
ItemTags = new List<Tag>() { new Tag { TagId = 1, TagName = "2008" }, new Tag { TagId = 2, TagName = "Donkey" } }
};
Item i2 = new Item()
{
ItemId = 2,
ItemName = "Item2",
ItemTags = new List<Tag>() { new Tag { TagId = 4, TagName = "Cat" }, new Tag { TagId = 2, TagName = "Donkey" }, new Tag { TagId = 3, TagName = "Seattle" } }
};
Item i3 = new Item()
{
ItemId = 3,
ItemName = "Item3",
ItemTags = new List<Tag>() { new Tag { TagId = 523, TagName = "Manchester united" }, new Tag { TagId = 10, TagName = "European Cup" }, new Tag { TagId = 1, TagName = "2008" } }
};
Item i4 = new Item()
{
ItemId = 4,
ItemName = "Item4",
ItemTags = new List<Tag>() { new Tag { TagId = 05, TagName = "Banana" }, new Tag { TagId = 140, TagName = "Foo" }, new Tag { TagId = 4, TagName = "Cat" } }
};
Item i5 = new Item()
{
ItemId = 5,
ItemName = "Item5",
ItemTags = new List<Tag>() { new Tag { TagId = 05, TagName = "Banana" }, new Tag { TagId = 140, TagName = "Foo" } }
};
List<Item> itemList = new List<Item>() { i1, i2, i3, i4, i5 };
string tags = "Manchester United,European Cup,2008";
List<string> tagsList = tags.Trim().ToLower()
.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)
.Distinct(StringComparer.CurrentCultureIgnoreCase)
.ToList();
var query = itemList
.OrderByDescending(p => p.DateCreated).ToList();
var results = query.Where(i => i.ItemTags.All(it => tagsList.Contains(it.TagName.ToLower())));
foreach (var item in results)
{
Console.WriteLine(item.ItemName); // Should return "Item3"
}
Console.ReadLine();
}
If you want to match any of the tags in the Item's ItemTag list then just change All to Any i.e.
var results = query.Where(i => i.ItemTags.Any(it => tagsList.Contains(it.TagName.ToLower())));

Resources