I have 2 lists that contain different data but have similar columns.
Basically I want to join these lists but then merge the similar columns into 1.
var listCombo= List1.Where(a=>DataIds.Contains(a.dataId)).DefaultIfEmpty()
.Join(List2,
list1 => list1.key,
list2 => list2.key,
(L1,L2) => new
{
L2.key,
L2.dataId,
L2.dataValue,
L2.date,
L1.secId,
L1.dataId,
L1.dataValue
});
I'd like to combine the dataId and dataValue columns together. How do I do that?
So I can just say listCombo.dataId or listCombo.dataValue rather than having to uniquely name them.
If I understand your question, you're trying to combine the two fields into one. Assuming that you have a Class
public class List1 {
public int dataId {get;set;}
public string dataValue {get;set;}
public string combinedValue {get;set;}
}
No you can use like,
var listCombo= List1.Where(a=>DataIds.Contains(a.dataId)).DefaultIfEmpty()
.Join(List2,
list1 => list1.key,
list2 => list2.key,
(L1,L2) => new List1
{
dataId = L2.dataId,
dataValue = L2.dataValue
combinedValue = L2.dataId + " - " L2.dataValue
});
I would use POCO methods for a list1, list2, and a third list which is a combo of both. I would be sure that if you are combining unlike types you do appropriate casts or if you want complex types you can define them as their own properties or what not.
static void Main(string[] args)
{
List<A> lisA = new List<A> {new A {AId = 1, AName = "Brett"}, new A {AId = 2, AName = "John"}};
List<B> lisB = new List<B> {new B { BId = 1, BName = "Doe" }, new B { BId = 2, BName = "Howard" } };
List<C> lisC = lisA.Join(lisB,
list1 => list1.AId,
list2 => list2.BId,
(L1, L2) => new C
{
CId = L1.AId,
CName = L1.AName + " " + L2.BName
}).ToList();
lisC.ForEach(
n => Console.WriteLine(n.CName + "\n")
);
}
public class A
{
public int AId { get; set; }
public string AName { get; set; }
}
public class B
{
public int BId { get; set; }
public string BName { get; set; }
}
public class C
{
public int CId { get; set; }
public string CName { get; set; }
}
Related
I'm trying find all items in my database that have at least one value in an array that matches any value in an array that I have in my code (the intersection of the two arrays should not be empty).
Basically, I'm trying to achieve this :
public List<Book> ListBooks(string partitionKey, List<string> categories)
{
return _client.CreateDocumentQuery<Book>(GetCollectionUri(), new FeedOptions
{
PartitionKey = new PartitionKey(partitionKey)
})
.Where(b => b.Categories.Any(c => categories.Contains(c))
.ToList();
}
With the Book class looking like this :
public class Book
{
public string id {get;set;}
public string Title {get;set;}
public string AuthorName {get;set;}
public List<string> Categories {get;set;}
}
However the SDK throws an exception saying that Method 'Any' is not supported when executing this code.
This doesn't work either :
return _client.CreateDocumentQuery<Book>(GetCollectionUri(), new FeedOptions
{
PartitionKey = new PartitionKey(partitionKey)
})
.Where(b => categories.Any(c => b.Categories.Contains(c))
.ToList();
The following code works because there's only one category to find :
public List<Book> ListBooksAsync(string category)
{
return _client.CreateDocumentQuery<Book>(GetCollectionUri())
.Where(b => b.Categories.Contains(category))
.ToList();
}
In plain SQL, I can queue multiple ARRAY_CONTAINS with several OR the query executes correctly.
SELECT * FROM root
WHERE ARRAY_CONTAINS(root["Categories"], 'Humor')
OR ARRAY_CONTAINS(root["Categories"], 'Fantasy')
OR ARRAY_CONTAINS(root["Categories"], 'Legend')
I'm trying to find the best way to achieve this with LINQ, but I'm not even sure it's possible.
In this situation I've used a helper method to combine expressions in a way that evaluates to SQL like in your final example. The helper method 'MakeOrExpression' below lets you pass a number of predicates (in your case the individual checks for b.Categories.Contains(category)) and produces a single expression you can put in the argument to .Where(expression) on your document query.
class Program
{
private class Book
{
public string id { get; set; }
public string Title { get; set; }
public string AuthorName { get; set; }
public List<string> Categories { get; set; }
}
static void Main(string[] args)
{
var comparison = new[] { "a", "b", "c" };
var target = new Book[] {
new Book { id = "book1", Categories = new List<string> { "b", "z" } },
new Book { id = "book2", Categories = new List<string> { "s", "t" } },
new Book { id = "book3", Categories = new List<string> { "z", "a" } } };
var results = target.AsQueryable()
.Where(MakeOrExpression(comparison.Select(x => (Expression<Func<Book, bool>>)(y => y.Categories.Contains(x))).ToArray()));
foreach (var result in results)
{
// Should be book1 and book3
Console.WriteLine(result.id);
}
Console.ReadLine();
}
private static Expression<Func<T,bool>> MakeOrExpression<T>(params Expression<Func<T,bool>>[] inputExpressions)
{
var combinedExpression = inputExpressions.Skip(1).Aggregate(
inputExpressions[0].Body,
(agg, x) => Expression.OrElse(agg, x.Body));
var parameterExpression = Expression.Parameter(typeof(T));
var replaceParameterVisitor = new ReplaceParameterVisitor(parameterExpression,
Enumerable.SelectMany(inputExpressions, ((Expression<Func<T, bool>> x) => x.Parameters)));
var mergedExpression = replaceParameterVisitor.Visit(combinedExpression);
var result = Expression.Lambda<Func<T, bool>>(mergedExpression, parameterExpression);
return result;
}
private class ReplaceParameterVisitor : ExpressionVisitor
{
private readonly IEnumerable<ParameterExpression> targetParameterExpressions;
private readonly ParameterExpression parameterExpression;
public ReplaceParameterVisitor(ParameterExpression parameterExpressionParam, IEnumerable<ParameterExpression> targetParameterExpressionsParam)
{
this.parameterExpression = parameterExpressionParam;
this.targetParameterExpressions = targetParameterExpressionsParam;
}
public override Expression Visit(Expression node)
=> targetParameterExpressions.Contains(node) ? this.parameterExpression : base.Visit(node);
}
}
Using EF code first, I have the following 4 entities
public class Item {
public int Id { get; set; }
public string Name { get; set; }
}
public class Location {
public int Id { get; set; }
public string Name { get; set; }
}
public class InventoryAdjustment {
public int Id { get; set; }
public virtual Location Location { get; set; }
public virtual ICollection<AdjustmentLine> Lines { get; set; }
}
public class AdjustmentLine {
public int Id { get; set; }
public virtual Item Item { get; set; }
public int Quantity { get; set; }
}
What I am trying to do is to get the sum of all inventory adjustments for each item at each location using minimal database round-trips.
The best I achieved so far is:
using (var db = new InventoryContext()) {
var items = db.Items.ToList();
var locations = db.Locations.ToList();
foreach (var item in items) {
Console.WriteLine(item.Name+":");
foreach (var location in locations) {
Console.Write("\t" + location.Name + ": ");
var qty = db.InventoryAdjustments
.Where(p => p.Location.Id == location.Id)
.SelectMany(p => p.Lines)
.Where(p => p.Item.Id == item.Id)
.Select(p => (int?)p.Quantity)
.Sum();
Console.WriteLine(qty ?? 0);
}
}
Console.Read();
}
The above outputs:
Item1:
Location1: 2
Location2: 12
Location3: 21
Item2:
Location1: 4
Location2: 0
Location3: 0
Item3:
Location1: 1
Location2: 17
Location3: 0
But with 3 items and 3 locations in the database, the above code causes 11 calls to the database. 2 for getting items and locations, and 9 for calculating the sum of quantity.
Is there a better way to get the sum with the least amount of round-trips?
This should probably work:
using (var db = new InventoryContext())
{
var items = db.Items.ToList();
var locations = db.Locations.ToList();
items
.Select(item =>
{
Console.WriteLine(item.Name + ":");
return item;
})
.SelectMany(item => locations.Select(location => new { item, location }))
.GroupJoin(
db.InventoryAdjustments
.SelectMany(
inventoryAdjustment => inventoryAdjustment.Lines.Select(
adjustmentLine => new { key = new { locationId = inventoryAdjustment.Location.Id, itemId = adjustmentLine.Item.Id }, adjustmentLine.Quantity }
)
),
x => new { locationId = x.location.Id, itemId = x.item.Id },
y => y.key,
(x, y) =>
{
Console.WriteLine("\t {0}: {1}", x.location.Name, y.Sum(a => a.Quantity));
return 0;
}
).ToList();
Console.Write("\nPress any key...");
Console.ReadKey();
}
I have a Complex Situation now and i am terribly stuck. Kindly Let me know if you can share some light to it.
I have a
List Which will have the Following properties
public class Categories
{
public string DisplayName { get; set; }
public string ValueCode { get; set; }
public string Count { get; set; }
}
This will have Values like
Category1/SubCategory1
cat1/sc1
5
Category1/SubCategory2
cat1/sc2
4
Category 2/Subcategory1
cat2/sc1
5
Category 2/Subcategory2
cat2/sc2
23
I created a Custom Class to fill in the values
public class JobCateogry
{
public string DisplayName { get; set; }
public string ValueCode { get; set; }
public string Count { get; set; }
public List<JobCateogry> SubCategories { get; set; }
}
I have to Split the String in the Code Value and assign it to the SubCategory.
Like My Final out of jobCategory would be
Category1
Cat1
9
SubCategory1
sub1
5
SubCateogry2
sub2
4
I tried to Split the string and assign it to the new class in two step first by splitting and then by assiging. But i am sure i am doing it the wrong way, because the moment i split, i loose the count .
var lstCategory = Categories
.Where(i => i.count > 0)
.Select(item => item.valueCode.Split('/')
.Select(k =>(k)).ToList();
List<JobCategories> jobcategories = lstCategory
.Select(item => item.Split(QueryStringConstants.CAT_SEPERATOR.ToCharArray()[0]))
.GroupBy(tokens => tokens[0].Trim(), tokens => tokens[1])
.Select(g => new JobCategories(g.Key, g.DisplayName,g.ToList(),)).ToList();
Can you please help?
A bit weird task
It might not be the best solution and it only works with the two layers :-), and i tried keeping a lot of linq for the fun of it
anyway hope it can get you moving forward.
full code snippet https://gist.github.com/cbpetersen/db698def9a04ebb2abbc
static void Main(string[] args)
{
var cats = new[]
{
new Categories { Count = "5", ValueCode = "cat1/sc1", DisplayName = "Category1/SubCategory1" },
new Categories { Count = "4", ValueCode = "cat1/sc2", DisplayName = "Category1/SubCategory2" },
new Categories { Count = "5", ValueCode = "cat2/sc1", DisplayName = "Category2/Subcategory1" },
new Categories { Count = "23", ValueCode = "cat2/sc2", DisplayName = "Category2/Subcategory2" }
};
var categories = cats.Select(x => x.DisplayName.Split('/')[0]).Distinct();
var list = new List<JobCateogries>();
foreach (var category in categories)
{
var a = new JobCateogries
{
ValueCode = cats.Where(x => x.DisplayName.Split('/')[0] == category)
.Select(x => x.ValueCode.Split('/')[0]).FirstOrDefault(),
DisplayName = category,
SubCategories = cats.Where(x => x.DisplayName.Split('/')[0] == category)
.Select(x => new JobCateogries
{
SubCategories = new List<JobCateogries>(),
Count = x.Count,
DisplayName = x.DisplayName.Split('/')[1],
ValueCode = x.ValueCode.Split('/')[1]
}).ToList(),
};
a.Count = a.SubCategories.Select(x => int.Parse(x.Count)).Sum().ToString();
list.Add(a);
}
list.ForEach(x => Print(x));
Console.ReadKey();
}
public static void Print(JobCateogries category, int indent = 0)
{
var prefix = string.Empty.PadLeft(indent);
Console.WriteLine(prefix + category.DisplayName);
Console.WriteLine(prefix + category.ValueCode);
Console.WriteLine(prefix + category.Count);
category.SubCategories.ForEach(x => Print(x, indent + 4));
}
Problem is with the addresses not being outputted
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace LinqToObjects
{
class Program
{
static void Main(string[] args)
{
var customers = Customer.GetAllCustomers();
var addresses = Address.GetAllAddresses();
var addressRelations = AddressRelation.GetAllAddressRelations();
var results = customers
.Join(addressRelations,
c => c.CustomerID,
ar => ar.CustomerID,
(c, ar) => new
{
CustomerName = c.FirstName + " " + c.LastName,
CustomerID = c.CustomerID,
AddressRelID = ar.AddressID
});
var resultsJoined = results
.GroupJoin(addresses,
ar => ar.AddressRelID,
a => a.AddressID,
(ar, a) => new
{
CustomerName = ar.CustomerName,
AddressLine = addresses.Select(b => b.StreetAddress).FirstOrDefault()
});
foreach(var item in resultsJoined)
{
Console.WriteLine(item.CustomerName);
Console.WriteLine(item.AddressLine);
Console.WriteLine("-----------------");
}
}
}
public class AddressRelation
{
public int AddressRelationID { get; set; }
public int CustomerID { get; set; }
public int AddressID { get; set; }
public AddressRelation(int id, int customerId, int addressId)
{
AddressRelationID = id; CustomerID = customerId; AddressID = addressId;
}
public static List<AddressRelation> GetAllAddressRelations()
{
var AllAddressRelations = new List<AddressRelation>();//simulate data returned from db
var addressRelation1 = new AddressRelation(1, 1, 1);
var addressRelation2 = new AddressRelation(2, 3, 3);
var addressRelation3 = new AddressRelation(3, 2, 2);
AllAddressRelations.Add(addressRelation1);
AllAddressRelations.Add(addressRelation2);
AllAddressRelations.Add(addressRelation3);
return AllAddressRelations;
}
}
public class Address
{
public int AddressID { get; set; }
public string StreetAddress { get; set; }
public Address(int id, string streetAddress)
{
AddressID = id; StreetAddress = streetAddress;
}
public static List<Address> GetAllAddresses()
{
var AllAddresses = new List<Address>();
Address customer1Address = new Address(1, "Elm St");
Address customer2Address = new Address(2, "Willow Way");
Address customer3Address = new Address(3, "Linq Ln");
AllAddresses.Add(customer1Address);
AllAddresses.Add(customer2Address);
AllAddresses.Add(customer3Address);
return AllAddresses;
}
}
public class Customer
{
public int CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public Customer(int id,string firstName, string lastName)
{
CustomerID = id; FirstName = firstName; LastName = lastName;
}
public static List<Customer> GetAllCustomers()
{
var AllCustomers = new List<Customer>();
var customer1 = new Customer(1, "James", "T");
var customer2 = new Customer(2, "Donnie", "H");
var customer3 = new Customer(3, "Sarah", "H");
AllCustomers.Add(customer1);
AllCustomers.Add(customer2);
AllCustomers.Add(customer3);
return AllCustomers;
}
}
}
The query isn't very expressive. If I was going to join three lists using LinqToObjects, I'd do this:
var query =
from c in customers
join xr in addressRelations on c.CustomerId equals xr.CustomerId
join a in addresses on xr.AddressId equals a.AddressId
select new {Customer = c, Address = a};
Looks like another mistake. I bet that AddressRelId is the key to the AddressRelation table, and not what you want use to connect to the Address table.
.GroupJoin(addresses,
ar => ar.Address**Rel**ID,
a => a.AddressID,
In response to comment:
var query = customers
.Join(addressRelations,
c => c.CustomerId,
xr => xr.CustomerId,
(c, xr) => new {c, xr})
.Join(addresses,
x => x.xr.AddressId,
a => a.AddressId,
(x, a) => new {c = x.c, xr = x.xr, a = a})
.Select(x => new {Customer = x.c, Address = x.a});
It's returning the first customer address because you've told it to:
AddressLine = addresses.Select(b => b.AddressLine1).FirstOrDefault()
Here, addresses is all addresses. I suspect you just want:
AddressLine = a.Select(b => b.AddressLine1).FirstOrDefault()
i have a list of generic class which consists of 2 string property and 1 List as a property
code snipnets is as follows:
public Class abc
{
public int ID { get; set; }
public String Name { get; set; }
List<String> myList;
public List<String> Subjects
{
get
{
if (myList == null)
{
myList = new List<string>();
}
return myList;
}
}
public abc()
{
}
public abc(int id, String name, params string[] subjects)
{
Subjects.AddRange(subjects.AsEnumerable<String>());
ID = id;
Name = name;
}
}
List<abc> myList = new List<abc>();
myList.Add(new abc(1, "p1", "Maths", "Science"));
myList.Add(new abc(2, "p2", "Maths", "Art"));
myList.Add(new abc(3, "p3", "Art", "Science"));
myList.Add(new abc(4, "p4", "Geometry", "Maths"));
I need the output as
Subject Count Person
Maths 3 p1,p2,p4
Science 2 p1,p3
Art 2 p2,p3
Geometry 1 p4
Looks like you want something like:
var query = from item in myList
from subject in item.Subjects
group item.Name by subject into g
select new { Subject = g.Key,
Count = g.Count(),
Person = string.Join(",", g) };
(Change g into g.ToArray() in the string.Join call if you're using .NET 3.5.)
var result =myList.SelectMany(p => p.Subjects
.Select(q => new{Person = p.Name, Subject = q, ID = p.ID}))
.GroupBy(p => p.Subject)
.Select(p => new {Name = p.Key, Count = p.Count(), Persons = p
.Aggregate("", (a, b) => a + b.Person
+ ",").TrimEnd(',')}).OrderBy( p => p.Count);
Iterate over this collection, and print result as needed - properties of a result are Name, Count, Persons