Adding select distinct to linq query - linq

My Web API Controller has a method that retrieves a specified number of decriptions from a database table. There are duplicate descriptions with different IDs, so sometimes the query returns duplicates when I use SELECT TOP. I also added random (ORDER BY NEWID) to lessen the chances of getting dups but duplicates still get returned sometimes. I want to change the query to SELECT DISTINCT but not sure how to do that in my particular case. Using First() seems to be complicated here. Can anyone help? My method is below:
public List<String> GetRandomDescriptions(string cat, string subcat, int n)
{
using (MyContext ctx = new MyContext())
{
var temp = ctx.Interactions.Where(d => (d.Category.Equals(cat) && d.Subcategory.Equals(subcat)))).OrderBy(d=>Guid.NewGuid()).Take(n).Select(d=>d.Description).ToList();
return temp;
}
}
Here is my class:
[Table("[Records]")]
public class Interaction
{
[Key, Column("RECORD_ID")]
public string DescId { get; set;}
public string Category { get; set; }
public string Subcategory { get; set; }
public string Description{get; set;}
}

You can use something like this
var result = ctx.Interactions
.Where(d => d.Category == cat && d.Subcategory == subcat)
.Select(d => d.Description)
.Distinct()
.Take(n)
.ToList();
The key points are - first apply the filter, then select the description, then make it distinct and finally take the required number of items.
If you really need to pick a random items, then just insert your OrderBy before Take.

You don't need to do the funny OrderBy construct. Try something like this:
public List<String> GetRandomDescriptions(string cat, string subcat, int n)
{
using (MyContext ctx = new MyContext())
{
var temp = ctx.Interactions
.Where(d => d.Category.Equals(cat) && d.Subcategory.Equals(subcat)))
.Select(d=>d.Description)
.Distinct()
.ToList();
return temp;
}
}

Related

linq any clause on multiple values

I am trying to create a linq statement to filter otu results using an any clause. My issue is that I dont have a single value to compare against.
In the example below I have a PropertyTaxBill entity that is the parent. Each one has a collection of TaxPropertyAssessmentDetails attached to it.
In this query people can specify that they only want to deal with bills pertaining to a specific class strata so I check to see if any values exist in the classStrata variable. If so then the user selected specific ones. I was trying to do an any clause on the classStrata but instead of giving it a single value to match on I was trying to select all the values in the TaxPropertyAssessmentDetails collection attached to the PropertyTaxBill. Is this possible?
using (var dataContext = contextProvider.GetContext())
{
var query = dataContext.PropertyTaxBills.Where(x => x.Id > 1);
var classStrata = new int[0];
if (classStrata != null && classStrata.Any())
{
query = query.Where(x => classStrata.Any(y => y == x.TaxPropertyAssessmentDetails.SelectMany(z => z.PropertyTaxClassStrataId)));
}
}
You need to use contains so that ef is able to translate to a ef query.
Not sure that i understood your model correctly. Here's a example with a model. Please tell me if i understood incorrect so i can fix it.
public void TestMethod1()
{
IEnumerable<TaxBill> TaxBills = new List<TaxBill>();
var query = TaxBills.Where(x => x.Id > 1);
var classStrata = new int[0];
if (classStrata != null && classStrata.Any())
{
query = query.Where(x => x.AssessmentDetails.Any(ad => ad.TaxClassStrataId.Any(cs => classStrata.Contains(cs))));
}
}
And the entities
public class TaxBill
{
public int Id { get; set; }
public ICollection<AsessmentDetails> AssessmentDetails { get; set; }
}
public class AsessmentDetails
{
public ICollection<int> TaxClassStrataId { get; set; }
}

LINQ search Product Index

I have a List<Product> contains properties of Bikes (Name, ProductName, Color, List Price). I'm struggling to figure out how to write a search function using LINQ. I'd like to find a name of Bike. Any suggest will be help me some ways.
Imagine that your name is taken from a variable called nameToSearch.
This is if you want to get the Product.
string nameToSearch = "BikeName";
List<Product> list = bikes.Where(x => x.Name == nameToSearch).ToList();
I assume you have the following Product class:
public class Product
{
public String Name { get; set; }
public String ProductName { get; set; }
public String Color { get; set; }
public String List { get; set; }
public String Price { get; set; }
}
You also mentioned you have your data in a List<Product>. I will give a demo name for it:
List<Product> myProductList = GetProductList();
// Where GetProductList() will create a new List<Product> and populate it.
String bikeNameFilter = GetNameFilter();
// You can chnage this by the string you want for filtering.
You can use the following to get your data:
List<Product> myFilteredProductList = (from p in myProductList
where p.Name = bikeNameFilter
select p;
).ToList()
Obviously you can change the filter you want to use to another property of your product. Finally to get the actual name, you can loop through the list you just got:
foreach (var p in myFilteredProductList)
{
Console.WriteLine(p.ProductName);
// Use this value wherever you want.
}
Take a look at a nuget package I have created
http://www.nuget.org/packages/NinjaNye.SearchExtensions
This will enable the following (and more) which will return results where the search term appears in any of the properties specified
var result = products.Search("searchTerm", p => p.Name, p => p.ProductName);
Performing a search against all string properties can be done as follows:
var result = products.Search("searchTerm");
Alternatively, you can perform an AND search where the search term exists in a set of properties as follows:
string searchTerm = "searchTerm";
var result = products.Search(searchTerm, p => p.Name)
.Search(searchTerm, p => p.ProductName);
For more information take a look at the projects GitHub page or my blog posts
UPDATE: don't forget the using directive...
using NinjaNye.SearchExtensions

Linq Grouping looses the child entities

I have the following query:
var _customers = (from c in _db.UserProfiles.Include(x=>x.ParentCompanies).Include(x=>x.cProfile).Include(x=>x.cProfile.PhoneNumbers).Include(x=>x.cProfile.Addresses)
where (c.ParentCompanies.Any(pc => pc.CompanyUsers.Any(cu => cu.UserName == userName)) && c.cProfile != null)
group c by c.FirstName.Substring(0, 1).ToUpper() into customerGroup
select new ContactsViewModel
{
FirstLetter = customerGroup.Key,
Customers = customerGroup
}).OrderBy(letter => letter.FirstLetter);
if I take out the group, it works well and includes all the children (parentCompanies, cProfile, ...) as soon as I put the group back in it looses all of the children. How do I solve this issue?
update
I guess I should also include the view model that I'm usign to put the result in.
public class ContactsViewModel
{
public string FirstLetter { get; set; }
public IEnumerable<UserProfile> Customers { get; set; }
}
Include only applies to items in the query results (i.e. the final projection) and cannot contain operations that change the type of the result between Include and the outermost operation (e.g. GroupBy())
http://wildermuth.com/2008/12/28/Caution_when_Eager_Loading_in_the_Entity_Framework
If you want to eager load, do the grouping client-side (i.e. enumerate the query then call the GroupBy method on the results)

How to dynamically add properties to a LINQ GroupBy clause

I have a class Product:
class Product
{
int Id { get; set; }
string Name { get; set; }
int CategoryId { get; set; }
int PlantId { get; set; }
DateTime ProductionDate { get; set; }
}
I would like to use the LINQ GroupBy on multiple properties but I do not know in advance how many and which properties. For instance I might want to group by just CategoryId, just PlantId or both. I found an article on the net that describes how to use LINQ GrouBy dinamically.
This might work good indeed but if I want to perform the Group By on ProductionDate.Year and ProductionDate.Month without knowing the granularity in advance? As granularity I mean whether I want to group all the Products produced in a specific year or narrow the group by to the month.
The only logical solution that I found is:
public ProductInfo GetGroupedProducts(int? year, int? month, int? selectedCategoryId, int? selectedPlantId)
{
List<Product> products = GetProducts();
var groupedProducts = products.GroupBy(p => new { (year == null ? p.ProductionDate.Year : string.Empty),
(month == null ? p.ProductionDate.Month : string.Empty),
(selectedCategoryId == null ? p.CategoryId : string.Empty),
(selectedPlantId == null ? p.PlantId : string.Empty)
});
//perform some additional filtering and assignments
}
But I guess there could be a cleaner and more proper solution. With the old style way of building queries, based on strings, this task was much easier to accomplish. If there is no other way, I really think this is a part of LINQ that needs to be improved.
The cleaner solution is to use this extension method:
public static TResult With<TInput, TResult>(this TInput? o, Func<TInput, TResult>
selector, TResult defaultResult = default(TResult)) where TInput : struct
{
return o.HasValue ? selector(o.Value) : defaultResult;
}
Like this:
string result = year.With(T => p.ProductionDate.Year, string.Empty);
of this, if nulls are okay:
string result = year.With(T => p.ProductionDate.Year);
or something with T which is int in case the int? has value.
But, you know, the better solution is out there, so feel free to expand your code so I could analyze it.
If I understand what you are asking, I had a similar issue Reversing typeof to use Linq Field<T>
I would do something like
public static IEnumerable<IGrouping<string, TElement>> GroupMany<TElement>(
this IEnumerable<TElement> elements,
params Func<TElement, object>[] groupSelectors)
{
return elements.GroupBy(e => string.Join(":", groupSelectors.Select(s => s(e))));
}
then you can call your function like
var groupedProducts = products.GroupMany(p => p.CategoryId , p => p.ProductionDate.Month);
The function groups via a string of the properties divided by a colon. The reason why I did this is because the hashcode for strings are guaranteed to be the same as opposed to a class.

RavenDB: Indexing blog-post tags from multiple blogs

How do I created the appropriate AbstractIndexCreationTask for the following scenario?
For a scenario of multiple blogs, how do I get the tags from specific blogs and the tag-count for these?
Members of interest for data-structure stored in RavenDB:
public class BlogPost {
public string BlogKey { get; set; }
public IEnumerable<string> Tags { get; set; }
/* ... */
}
The method I need to implement has the following signature:
public Dictionary<string, int> GetTagsByBlogs(string tag, params string[] blogKeys)
In normal LINQ I would write something like this:
var tags = from post in blogPosts
from tag in post.Tags
where blogKeys.Contains(post.BlogKey)
group tag by tag into g
select new {
Tag = g.Key,
Count = g.Count(),
};
But neither SelectMany or GroupBy are supported in RavenDB. I've tried different combinations for a map-reduce solution, but I can't figure out how to do this since the map and the reduce differ in data-structure.
How to create a tag cloud is described in the knowledge base of RavenDB.
In your case, you have to include the BlogKey in the index, especially in the group by clause:
public class Tags_Count : AbstractIndexCreationTask<BlogPost, Tags_Count.ReduceResult>
{
public class ReduceResult
{
public string BlogKey { get; set; }
public string Name { get; set; }
public int Count { get; set; }
}
public Tags_Count()
{
Map = posts => from post in posts
from tag in post.Tags
select new {
BlogKey = post.BlogKey,
Name = tag.ToString().ToLower(),
Count = 1
};
Reduce = results => from tagCount in results
group tagCount by new {
tagCount.BlogKey,
tagCount.Name } into g
select new {
BlogKey = g.Key.BlogKey,
Name = g.Key.Name,
Count = g.Sum(x => x.Count)
};
Sort(result => result.Count, SortOptions.Int);
}
}
Then query that index with the desired BlogKey:
var result = session.Query<Tags_Count.ReduceResult, Tags_Count>()
.Where(x => x.BlogKey = myBlogKey)
.OrderByDescending(x => x.Count)
.ToArray();
If you need to query for multiple blogs, you can try this query:
var tagsByBlogs = session.Query<Tags_Count.ReduceResult, Tags_Count>()
.Where(x => x.BlogKey.In<string>(blogKeys))
.OrderByDescending(x => x.Count)
.ToArray();
AFAIK that is as far as you can get with an index. You still have to aggregate the results on the client side as you did in your original question, except that you can skip the filtering on blogKeys:
var tags = from tag in tagsByBlogs
group tag by Name into g
select new {
Tag = g.Key,
Count = g.Count(),
};
Take a look at faceted search, you can specify the criteria at query time, like so:
var facetResults = s.Query<BlogPost>("BlogIndex")
.Where(x => x.BlogKey == "1" || x.BlogKey == "5" ...)
.ToFacets("facets/BlogFacets");
Then the grouping (and counts) is done on all the results that match the where clause.
Update You'll need an index that looks something like this:
from post in blogPosts
from tag in post.Tags
select new
{
post.BlogKey
Tag = tag
}

Resources