Casting issue in LINQ (C# .NET 4) - c#

I have come across a very confusing problem that I hope someone can help me with. In my application I have the following data structures:
public struct EntityDetails
{
public string EntityName { get; set; }
public List<AttributeDetails> Attributes { get; set; }
public bool EntityExists { get; set; }
}
public struct AttributeDetails
{
public string AttributeName { get; set; }
public string DisplayName { get; set; }
public string DataType { get; set; }
public string Description { get; set; }
public bool AttributeExists { get; set; }
}
I instantiate the object with the following:
public static List<EntityDetails> entityList { get; set; }
So, what I need to do is to be able to return a filtered list of attributes based on an entity name and an attribute name. To do this I wrote the following piece of LINQ:
public static List<AttributeDetails> GetFilteredAttributeList(string pEntityName, string pAttributeFilter)
{
return (List<AttributeDetails>)entityList.Where(e => e.EntityName == pEntityName)
.Select(e => e.Attributes
.Where (a => a.AttributeName
.Contains (pAttributeFilter)));
}
Initially, when I did this I didn't have the cast at the start, but it brought up a compile time error, so I added the cast to allow it to compile. However, when it gets to this method I get the following message:
{"Unable to cast object of type 'WhereSelectListIterator2[MPYA.BU.CRMClientUpdateTool.CRMAccess.CRMAccessLayer+EntityDetails,System.Collections.Generic.IEnumerable1[MPYA.BU.CRMClientUpdateTool.CRMAccess.CRMAccessLayer+AttributeDetails]]' to type 'System.Collections.Generic.List`1[MPYA.BU.CRMClientUpdateTool.CRMAccess.CRMAccessLayer+AttributeDetails]'."}
Now, from the research I've done it would be appear that one is of type IEnumerable and the other is list, which I understand, but I can't for the life of me work out how to cast it so it is acceptable! I've also tried ToList(), casting it through the extension methods and various other things. I've also confirmed the data structure contains the correct data.
Any help would be appreciated.
UPDATE
Apologies, but for some reason I can't reply to answers for 8 hrs sigh. I have followed the advice of everyone to use ToList and now I get the following error:
Thanks for the answers so far. In my mind ToList() was the only logical way to go, but when I do that I get the following compile-time error:
error CS0029: Cannot implicitly convert type 'System.Collections.Generic.List<System.Collections.Generic.IEnumerable<MPYA.BU.CRMClientUpdateTool.CRMAccess.CRMAccessLayer.AttributeDetails>>' to 'System.Collections.Generic.List<MPYA.BU.CRMClientUpdateTool.CRMAccess.CRMAccessLayer.AttributeDetails>'
The actual error message when I hover over it is "System.ArgumentNullException".

You can simply end your LINQ query with a call to .ToList() to convert the results to a List<T>.
One thing to keep in mind is that calling .ToList() on a LINQ query "realizes" it, meaning that execution is no longer deferred;the results are now stored in memory in your List<T>.
In addition, I believe you want to use the .SelectMany() clause, instead of .Select(). e.Attributes is a List<AttributeDetails>. If you use Select(), it will create an IEnumarable<list<AttributeDetails>>, with each element being the attributes from one of your entities. SelectMany will combine the returned lists and return an IEnumerable<AttributeDetails>, which appears to be what you want.
Ultimately, you want to use the following:
return entityList.Where(e => e.EntityName == pEntityName)
.SelectMany(e => e.Attributes
.Where (a => a.AttributeName
.Contains(pAttributeFilter)))
.ToList();

Use this code:
return entityList.Where(e => e.EntityName == pEntityName)
.Select(e => e.Attributes
.Where (a => a.AttributeName
.Contains (pAttributeFilter))).ToList()
LINQ returns IEnumerable which is not a list, so you cannot cast this object to list. When you call ToList() the linq query will be executed and converted to list

You need SelectMany to pull out the attributes into a single list. You also need ToList() to convert the result to a List(). The cast will be unnecessary.
Try
return entityList.Where(e => e.EntityName == pEntityName)
.SelectMany(e => e.Attributes
.Where (a => a.AttributeName
.Contains (pAttributeFilter)))
.ToList();

Given that your Attributes are in fact instances of AttributeDetails, then you can call .ToList() at the end of your query. However, another appropriate way could be for you to change the signature of the method to return an IEnumerable<AttributeDetails>, depending on what functionality the caller is expecting.
In fact, the caller can still transform the query results at their leisure if they need to do so, where you can reliably return a further readable and queryable collection. So...
public static IEnumerable<AttributeDetails> GetFilteredAttributeList(
string pEntityName, string pAttributeFilter) {
return entityList
.Where(e => e.EntityName == pEntityName)
.Select(e => e.Attributes
.Where (a => a.AttributeName.Contains (pAttributeFilter)
)
);
}

public static List<AttributeDetails> GetFilteredAttributeList(string pEntityName, string pAttributeFilter)
{
var entityList = new List<EntityDetails>(); //added just to makte it compile
var filtered =
from entity in entityList
where entity.EntityName == pEntityName
from attribute in entity.Attributes
where attribute.AttributeName.Contains(pAttributeFilter)
select attribute;
return filtered.ToList();
}

Related

Specification Pattern - Implemeting an expression using ICollection

I have implemented Specific Pattern following vkhorikov/SpecificationPattern
For example, i have a product table and it has many to many relation with WarehouseProducts table. I need to find all products for a given list of wearhouses. So, this is what i have kind of
public class Products
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public ICollection<WarehouseProduct> Warehouses { get; set; }
}
public class WarehouseProduct
{
[Key]
public int Id { get; set; }
public int WarehouseId { get; set; }
[ForeignKey("ProductId")]
public int ProductId { get; set; }
}
public class WarehouseProductSpecification : Specification<Products>
{
private readonly List<int> _ids;
public WarehouseProductSpecification(IEnumerable<int> warehouseIds)
{
_ids = warehouseIds.ToList();
}
public override Expression<Func<Products, bool>> ToExpression()
{
Expression<Func<WarehouseProduct, bool>> expr =
(w) => _ids.Contains(w.WarehouseId);
return
q => !_ids.Any()
|| (_ids.Any() && q.Warehouses != null && q.Warehouses.Any(expr.Compile()));
}
}
But, when i execute I got the following error
System.NotSupportedException Cannot compare elements of type
'System.Collections.Generic.ICollection`1[[Data.TableObjects.WarehouseProduct,
Data, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]'. Only
primitive types, enumeration types and entity types are supported.
I am really struggling to create the specification for a ICollection. Is there any way to achieve that?
FYI, I am using EF6 to connect to a SQLServer database.
Updated
// To resond to the first comment..
I have use the spec on the repostiory so the following code gets error
var products = _context.Products
.Include("WarehouseProducts")
.Where(warehouseProductSpec.ToExpression())
.ToList();
so the to list gets the error
Update 2
I tried to use the code added by #Evk
if (_ids.Count == 0)
return x => true;
return q => q.Warehouses.Any(w => _ids.Contains(w.WarehouseId));
I got the following error while trying your code
Test [0:10.297] Failed: System.ArgumentException: Property 'System.String WearhouseId' is not defined for type 'Data.TableObjects.Products'
System.ArgumentException
Property 'System.String WearhouseId' is not defined for type 'Data.TableObjects.Products'
at System.Linq.Expressions.Expression.Property(Expression expression, PropertyInfo property)
at System.Linq.Expressions.ExpressionVisitor.VisitArguments(IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)
at System.Linq.Expressions.ExpressionVisitor.VisitArguments(IArgumentProvider nodes)
at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
at Infrastructure.Expressions.AndSpecification`1.ToExpression() in C:\..\Expressions\AndSpecification
You have to always remember that your expression is converted to SQL query by entity framework. For example, think about how this
q.Warehouses.Any(expr.Compile())
can be translated to SQL? It cannot, because result of expr.Compile() is basically .NET code - it's not an expression tree any more. You can use third party libraries, like LinqKit, to be able to integrate one expression into another, but without that it will not just work. In your specific case that is not needed though.
First you need to clean up your expression. If list of _ids is empty - you don't need to filter anything. So return expression which just returns true (matches all):
if (_ids.Count == 0)
return x => true;
Now, after if, we know that there are ids in a list, so we can skip all checks related to that. Then, you don't need to check Warehouses for null. This check is what causes exception you observe. It doesn't make sense given that this expression will be converted to SQL query, so this check can be removed. Code inside expression will never be executed directly (at least in EF6), so no null reference exceptions are possible.
That leaves us only with one expression, which actually does useful work, so final ToExpression will be:
if (_ids.Count == 0)
return x => true;
return q => q.Warehouses.Any(w => _ids.Contains(w.WarehouseId));

Check if List is not null when using "contains" in LINQ query else select all records

I have a Search functionality in my application and it has 4 criterias basically Location, Status,PropertyType [List of String] and PriceRange [Min and Max Price] and below is the model
public class SearchFilters
{
public SearchFilters()
{
MinPrice = "10000";
MaxPrice = "8000000";
}
public IEnumerable<SelectListItem> Categories { get; set; }
public string[] CategoriesId { get; set; }
public IEnumerable<SelectListItem> Locations { get; set; }
public string[] LocationID { get; set; }
public IEnumerable<SelectListItem> Status { get; set; }
public string[] StatusID { get; set; }
public string MinPrice { get; set; }
public string MaxPrice { get; set; }
}
When data is received in controller List of Values Selected from SelectList will be stored in CategoriesId, LocationID and StatusID. Now Selecting values from each of List is optional, it can be single or multiple. So I need to filter Database and also if user does not select any item then this List will be null since it is an optional search criteria.
For Example
Values for status can be "Ongoing","Upcoming" and "Completed". So I used below LINQ to extract data.
[HttpGet]
public ActionResult Search(SearchFilters smodel)
{
var query=db.tblProperties.Where(p => smodel.StatusID.Contains(p.PropertyLocation)).Select(x=>x).ToList();
//.....
//.....
}
Just added one property comparison to demonstrate
This returns records without any problem, but if this smodel.StatusID comes null i.e. User does not select any value then this query fails with an exception. So how to overcome this? Also how to fetch all the records when no value is selected? Went through this post but the solution there wasn't useful to overcome this problem? Basically how can I incorporate a query for search in these situation?
Posted answers are correct and gave you the solution you need, I will go with checking for null, if you need this behavior in couple of places. If this request is repetitive in many places, i will go with the below solution.
There is another, more cleaner way if you're doing a lot of this checking, will be to add Extension Methods to do it for you.
Extension methods enable you to "add" methods to existing types
without creating a new derived type, recompiling, or otherwise
modifying the original type. Extension methods are a special kind of
static method, but they are called as if they were instance methods on
the extended type. For client code written in C# and Visual Basic,
there is no apparent difference between calling an extension method
and the methods that are actually defined in a type.
Code:
public static class CollectionExtension
{
public static bool CheckContainsIfHasValue<T>(this IEnumerable<T> source, T value)
{
return source == null || source.Contains(value);
}
}
Usage:
var query = db.tblProperties
.Where(p => smodel.StatusID.CheckContainsIfHasValue(p.PropertyLocation))
.ToList();
So if smodel.StatusID is null, you want to return all the records?
var query=db.tblProperties.Where(p => smodel.StatusID == null || smodel.StatusID.Contains(p.PropertyLocation))
.Select(x=>x).ToList();
So if you look at the Where clause now, if smodel.StatusID == null then every item will pass the Where clause. Note that the .Contains won't be called because of short cutting (if the first term of an OR is true, there's no point evaluating the second, so it won't).
You might also consider doing something like this:
.Where(p => smodel.StatusID == null ||
!smodel.StatusID.Any() ||
smodel.StatusID.Contains(p.PropertyLocation))
That way you are checking both that StatusID is not null and that the collection StatusID isn't empty.
If you can make StatusID default to an empty collection instead of null (for example, set it in the constructor of whatever class smodel is), then you can do this:
.Where(p => !smodel.StatusID.Any() ||
smodel.StatusID.Contains(p.PropertyLocation))
Since you won't need the null check anymore and Any should be translatable into LINQ to SQL.
Another option is to do the null check outside of the query
var initialQuery = db.tblProperties;
if(smodel.StatusID != null)
{
initialQuery = initialQuery.Where(p => smodel.StatusID.Contains(p.PropertyLocation));
}
var query = initialQuery.ToList();
Or as a helper method
public static IEnumerable<T> ConditionalWhere<T>(
this IEnumerable<T> collection,
Func<bool> condition,
Expression<Func<T, bool>> predicate)
{
if(condition())
return collection.Where(predicate);
return collection;
}
And then
var query = db.tblProperties.ConditionalWhere(
() => smodel.StatusID != null,
p => smodel.StatusID.Contains(p.PropertyLocation));
And you can chain them together
var query = db.tblProperties.ConditionalWhere(
() => smodel.StatusID != null,
p => smodel.StatusID.Contains(p.PropertyLocation))
.ConditionalWhere(
() => someOtherCollection != null,
p => someOtherCollection.Contains(p.PropertyLocation));
This will avoid running whatever the condition is multiple times for Linq-to-Objects and will allow you to use something that cannot be translated to SQL for EF or Linq-to-SQL.
You can simply add a null check in your where clause:
var query=db.tblProperties.Where(p => smodel.StatusID == null || smodel.StatusID.Contains(p.PropertyLocation))
.Select(x=>x).ToList();
C# uses Short-circuit evaluation of Boolean expressions. This means that C# stops evaluating an expression as soon as its result can be determined.
For instance in a && b, the result is known to be false if a is false, so b will not be evaluated. In a || b the result is known to be true if a is true, so b will not be evaluated.
You can use this to protect you from an exception by adding a null-test:
var query = db.tblProperties
.Where(p => smodel.StatusID == null ||
smodel.StatusID.Contains(p.PropertyLocation))
.ToList();
You can also drop the .Select(x=>x) part as it is doing nothing.
If you are using LINQ to EF then the text above does not apply. You cannot perform a null check on a collection, as this cannot translate to SQL. Instead make the check before:
bool ignore = smodel.StatusID == null || !smodel.StatusID.Any();
var query = db.tblProperties
.Where(p => ignore ||
smodel.StatusID.Contains(p.PropertyLocation))
.ToList();

How to convert the objects in a query result to custom type

I think the same question must have been asked, but I just don't know how to describe it properly.
I am using Entity Framework, and I have a table called vendor, consisting of many columns:
[vendor]
- [Id] [INT]
- [Name] [NVARCHAR]
- [ApiUrl] [NVARCHAR]
- ...
I am writing a Web Api to expose the function which users can get vendor records by. However, I want to provide only three columns.
So I created a model class, as below:
public class OM_Vendor
{
public int Id { set; get; }
public string Name { set; get; }
public string ApiUrl { set; get; }
}
After building the model classes with EF, the context object of EF has a property named vendors now. Then, in an API controller, I coded like this:
var vendors = this.objEntity.vendors
.Where<OM_Vendor>(v => v.Status == "1")
.OrderBy(v => v.Id)
.Skip(intSkip)
.Take(intLimit)
.ToList();
It doesn't work, neither does this:
var vendors = this.objEntity.vendors
.Where(v => v.Status == "1")
.OrderBy(v => v.Id)
.Skip(intSkip)
.Take(intLimit)
.ToList<OM_Vendor>();
Is there a way to convert the objects returned by the methods like above to my custom type?
Thanks a lot.
In addition, I know that I can get it to work in this way:
var vendors = new List<OM_Vendor>();
vendors = objCont.ExecuteStoreQuery<OM_Vendor>("SELECT * FROM vendor").ToList();
And this way works too:
var vendors = this.objCont.vendors
.Where(v => v.Status == "1")
.OrderBy(v => v.Id)
.Select(
row => new
{
Id= row.Id,
Name = row.Name,
ApiUrl = row.ApiUrl
}
)
.Skip(intSkip)
.Take(intLimit)
.ToList();
Update:
According to #Andrew's answer, I updated my code as below:
public class OM_Vendor
{
public int Id { set; get; }
public string Name { set; get; }
public string ApiUrl { set; get; }
public static implicit operator OM_Vendor(vendor vendor){
return new OM_Vendor
{
Id = vendor.Id,
Name = vendor.Name,
ApiUrl = vendor.ApiUrl
};
}
}
And in my controller class:
List<OM_Vendor> vendors = this.objCont.vendors
.Where(v => v.Status == "1")
.OrderBy(v => v.Id)
.Skip(intSkip)
.Take(intLimit)
.ToList();
I got error saying:
Cannot implicitly convert type
'System.Collections.Generic.List' to
'System.Collections.Generic.List'
What did I miss? As for the explicit cast, I will have to convert each instance explicitly in a loop?
You basically have 3 methods to achieve what you are trying to accomplish.
Use the .Select() linq extension method to create a projection (as is demonstrated by your last example.) Generally a good option, as this will create a SQL expression which only returns the fields which are needed by your Data Transfer Object.
Create a custom cast operator, e.g. public static implicit Operator OM_Vendor(Vendor vendor), which takes in a full Vendor object and assigns it to an instance of OM_Vendor. The main drawback here is that you are essentially retrieving all fields through Entity Framework, only to Flatten the entity and discard many of these fields during the implicit cast.
Use a custom library such as Automapper to automate the process of converting the objects. Suffers from most of the same issues as 2., but having it handled by a library can be helpful if you have many classes you are Flattening.
A more complete code example to demonstrate:
public class OM_Vendor {
...
public static implicit Operator OM_Vendor(Vendor vendor){
return new OM_Vendor {
Id = vendor.Id;
Name = vendor.Name;
ApiUrl = vendor.ApiUrl;
}
}
OM_Vendor om_Vendor = this.objEntity.vendors.Where(
...
List<OM_Vendor> vendors = this.objEntity.vendors.Where(
...
Either of these statements should execute correctly, using the implicit Operator as a Constructor when assigning a Vendor instance to an OM_Vendor. However, it cannot be overstated, this is Flattening the object at the code level, and will result in a larger than necessary SQL query.
This actually should be an explicit operator, since data loss occurs during conversion. Changing implicit Operator to explicit Operator will cause this conversion to require the cast, i.e. OM_Vendor omVendor = (OM_Vendor)vendor;. This makes it much more clear that a conversion is occurring, but makes the code slightly more verbose.
Have you tried projecting with:
.Select(row => new OM_Vendor{ Id=row.Id, Name=row.Name, ApiUrl=row.ApiUrl})

Using an extension method on a base class in a LINQ query

Apologies in advance for my naivety.
I am using Entity Framework to persist entities I have defined in my domain model. My domain model entities all inherit from my EntityBase class. This has properties I wish to be common to all my entities:
public class EntityBase
{
public string CreatedBy { get; set; }
public DateTime? Created { get; set; }
public int ModifiedBy { get; set; }
public DateTime? Modified { get; set; }
public bool Enabled { get; set; }
public bool Deleted { get; set; }
}
Now when I want to query EF using LINQ it would be nice if I didn't have to include elements to check if a particular entity is Enabled or Deleted. Every query would involve code, for example:
var messages = _db.Memberships.Where(m => m.UserId.Equals(userId))
.SelectMany(m => m.Group.Messages)
.Include(m => m.Group.Category)
.Select(m => m.Enabled && !m.Deleted)
.ToList();
Rather than doing this each time, I thought I would write an extension method which would act on IQueryable
public static IQueryable<EntityBase> Active(this IQueryable<EntityBase> entityCollection)
{
return entityCollection.Where(e => e.Enabled && !e.Deleted);
}
In my naivety I then thought I could just include this in any LINQ query which returns my entities which inherit from the EntityBase class - like so:
var messages = _db.Memberships.Where(m => m.UserId.Equals(userId))
.SelectMany(m => m.Group.Messages)
.Include(m => m.Group.Category)
.Active() <============================= Extension Methd
.ToList();
return Mapper.Map<List<Message>,List<MessageDto>>(messages);
However, the compiler now complains that:
Error 2 Argument 1: cannot convert from
'System.Collections.Generic.List<Diffusr.Business.Entities.EntityBase>' to
'System.Collections.Generic.List<Diffusr.Business.Entities.Message>'
Question : Can I achieve what I want to achieve, i.e. a common method for all my entities to return only Enabled and not Deleted? If so, how?
Instead of specifying a concrete class, use generics, as most extension methods do:
public static IQueryable<T> Active<T>(this IQueryable<T> entityCollection) where T:EntityBase
{
return entityCollection.Where(e => e.Enabled && !e.Deleted);
}
I assume you are using some version of .NET earlier than 4.0. Generic covariance wasn't allowed before 4.0 (ie passing an enumerable of a child type when an enumerable of the base type was expected).
Even after 4.0, it's not the absolute best idea to use covariance as the compiler ends up doing a lot of extra checks to do to ensure type safety whenever you try to store some new value to the List. Jon Skeet has a nice article about this
You can by changing the extension method:
public static IQueryable<T> Active(this IQueryable<T> entityCollection)
where T : EntityBase
{
return entityCollection.Where(e => e.Enabled && !e.Deleted);
}

Sort a List with OrderBy

I have the following model
public class UserViewModel
{
public String CVR_Nummer { get; set; }
public DateTime LastActivityDate { get; set; }
public DateTime CreationDate { get; set; }
public String FirmaNavn { get; set; }
public int ProcentAnswered { get; set; }
}
I create a List<UserViewModel> and try to sort it:
userviewmodel.OrderBy(x => x.ProcentAnswered);
It compiles, but the list doesnt get sorted. Howcome?
LINQ is side-effect free by design so it won't change the input. Also it uses lazy execution, it won't do anything till you try to reach the data. Re-assign the output to the list and it should work. Notice that I'm using ToList() method here because OrderBy() returns IEnumerable<UserViewModel> and it's not evaluated till we try to get the items in it. We create a new list from this sequence using ToList() method and forcing the query to execute.
userviewmodel = userviewmodel.OrderBy(x => x.ProcentAnswered).ToList();
Linq queries are executed lazily, which means that they do not execute until the source object is enumerated. Moreover, the query never operates on the actual object in place, but returns a new instance.
To force execution on the spot., you must explicitly call .ToList() after creating the query:
userviewmodel = userviewmodel.OrderBy(x => x.ProcentAnswered).ToList();
In this userviewmodel.OrderBy(x => x.ProcentAnswered); you have just prepared the query, you even didn't sorted your list.
In this userviewmodel.OrderBy(x => x.ProcentAnswered).ToList(); you have fired the query, and have sorted list, but above expression will give you a fresh list.
So you will need to do this
userviewmodel = userviewmodel.OrderBy(x => x.ProcentAnswered).ToList();
as suggested by everyone.
Hope this works for you.
userviewmodel.OrderBy(x => x.ProcentAnswered);
That's an expression, not an instruction to modify something. The value of the expression is your sorted list but you are not capturing it in a variable, and LINQ expressions are lazy-evaluated, so because you do not capture the result thereby forcing it to resolve, this statement has no effect whatsoever.
you can try
userviewmodel = userviewmodel.OrderBy(x => x.ProcentAnswered).ToList();

Categories