I have a simple web service calling a sql view table through entity framework. I can bring all the columns in string fine but not the column in numeric like UID_NUM(numeric(38,8), null) in SQL. I have AddressALL class to set columns like below and error out at p.UID_NUM in LINQ.
public class GISAddressWebService : System.Web.Services.WebService
{
[WebMethod]
public AddressALL[] getAddress()
{
try
{
List<view_COBADDRESS> address = new List<view_COBADDRESS>();
using (GISAddressEntities database = new GISAddressEntities())
{
return database.view_COBADDRESS
.Where(p => p.UNIT_NUM == "103")
.Select(p => new AddressALL { UID_NUM = p.UID_NUM, ADD_FULL = p.ADD_FULL, POSTALCITY = p.POSTALCITY, ZIP5 = p.ZIP5}).ToArray();
}
}
catch (Exception)
{
return null;
}
}
}
public class AddressALL
{
public double UID_NUM { get; set; }
public string TLID { get; set; }
public string ADD_FULL { get; set; }
public string POSTALCITY { get; set; }
public string STATE { get; set; }
public string ZIP5 { get; set; }
public string IN_OUT { get; set; }
}
The decimal has more significant figures than the double, therefore it can be more precise and it also takes up slightly more memory. Because of this difference fou must explicitly program this change of type through (double)p.UID_NUM.
return database.view_COBADDRESS
.Where(p => p.UNIT_NUM == "103")
.Select(p => new AddressALL { UID_NUM = System.Convert.ToDouble(p.UID_NUM), ADD_FULL = p.ADD_FULL, POSTALCITY = p.POSTALCITY, ZIP5 = p.ZIP5}).ToArray();
MSDN
The obvious solution, instead of
.Select(p => new AddressALL
{
UID_NUM = p.UID_NUM,
ADD_FULL = p.ADD_FULL,
POSTALCITY = p.POSTALCITY,
ZIP5 = p.ZIP5
});
write
.Select(p => new AddressALL
{
UID_NUM = Convert.ToDouble(p.UID_NUM),
ADD_FULL = p.ADD_FULL,
POSTALCITY = p.POSTALCITY,
ZIP5 = p.ZIP5
});
In your select statement .Select(p => new AddressALL{ ... }) you are doing a projection that is trying to pick a new object of type AddressALL for each p, and you are using the object initializer syntax {...} to match the properties of your source objects p with the properties of your target type AddressALL.
Your error message however suggests your p.UID_NUM is of type decimal, while the UID_NUM property on your AddressALL is of type double. Therefore you have to convert the values to the necessary target type.
Related
I`m having simple method which builds IQueryable and returns it.
public IQueryable<ClassDTO> ReportByNestedProperty()
{
IQueryable<Class> query = this.dbSet;
IQueryable<ClassDTO> groupedQuery =
from opportunity in query
group new
{
ItemGroup = opportunity.OpportunityStage.Name,
EstimatedRevenue = opportunity.EstimatedRevenue,
CostOfLead = opportunity.CostOfLead
}
by new
{
opportunity.OpportunityStage.Name,
opportunity.OpportunityStage.Id
}
into item
select new ClassDTO()
{
ItemGroup = string.IsNullOrEmpty(item.Key.Name) ? "[Not Assigned]" : item.Key.Name,
Count = item.Select(z => z.ItemGroup.Name).Count(), // int
Commission = item.Sum(z => z.EstimatedRevenue), // decimal
Cost = item.Sum(z => z.CostOfLead), // decimal?
};
return groupedQuery;
}
This is fine. The thing i need is to create method with same return type, but groupby by different prperties dynamically. So from the above code I want to have 3 dynamic parts which will be passed as params:
ItemGroup = opportunity.OpportunityStage.Name
and
by new
{
opportunity.OpportunityStage.Name,
opportunity.OpportunityStage.Id
}
So the new method should be like this
public IQueryable<ClassDTO> ReportByNestedProperty(string firstNestedGroupByProperty, string secondNestedGroupByProperty)
{
// TODO: ExpressionTree
}
And call it like this:
ReportByNestedProperty("OpportunityStage.Name","OpportunityStage.Id")
ReportByNestedProperty("OtherNestedProperty.Name","OtherNestedProperty.Id")
ReportByNestedProperty("OpportunityStage.Name","OpportunityStage.Price")
So the main thing is to create expressions with these two selects:
opportunity.OpportunityStage.Name,
opportunity.OpportunityStage.Id
I have tried toe create the select expressions, groupby, the creation of Anonomoys classes and the DTO Class but I just cant get it right.
EDIT:
Here are the classes involved:
public class ClassDTO
{
public ClassDTO() { }
[Key]
public string ItemGroup { get; set; }
public decimal Commission { get; set; }
public decimal? Cost { get; set; }
public int Count { get; set; }
}
Class obj is a pretty big one so i`m posting just part of it
public partial class Class
{
public Class() { }
[Key]
public Guid Id { get; set; }
public Guid? OpportunityStageId { get; set; }
[ForeignKey(nameof(OpportunityStageId))]
[InverseProperty(nameof(Entities.OpportunityStage.Class))]
public virtual OpportunityStage OpportunityStage { get; set; }
}
public partial class OpportunityStage
{
public OpportunityStage()
{
this.Classes = new HashSet<Class>();
}
[Key]
public Guid Id { get; set; }
public string Name { get; set; }
[InverseProperty(nameof(Class.OpportunityStage))]
public virtual ICollection<TruckingCompanyOpportunity> Classes{ get; set; }
}
I have simplified your Grouping query and introduced private class IdName which should replace anonymous class usage:
class IdName
{
public int Id { get; set; }
public string Name { get; set; } = null!;
}
static Expression MakePropPath(Expression objExpression, string path)
{
return path.Split('.').Aggregate(objExpression, Expression.PropertyOrField);
}
IQueryable<ClassDTO> ReportByNestedProperty(IQueryable<Class> query, string nameProperty, string idProperty)
{
// Let compiler to do half of the work
Expression<Func<Class, string, int, IdName>> keySelectorTemplate = (opportunity, name, id) =>
new IdName { Name = name, Id = id };
var param = keySelectorTemplate.Parameters[0];
// generating expressions from prop path
var nameExpr = MakePropPath(param, nameProperty);
var idExpr = MakePropPath(param, idProperty);
var body = keySelectorTemplate.Body;
// substitute parameters
body = ReplacingExpressionVisitor.Replace(keySelectorTemplate.Parameters[1], nameExpr, body);
body = ReplacingExpressionVisitor.Replace(keySelectorTemplate.Parameters[2], idExpr, body);
var keySelectorLambda = Expression.Lambda<Func<Class, IdName>>(body, param);
// finalize query
IQueryable<ClassDTO> groupedQuery = query
.GroupBy(keySelectorLambda)
.Select(item => new ClassDTO()
{
ItemGroup = string.IsNullOrEmpty(item.Key.Name) ? "[Not Assigned]" : item.Key.Name,
Count = item.Count(x => x.Name), // int
Commission = item.Sum(x => x.EstimatedRevenue), // decimal
Cost = item.Sum(x => x.CostOfLead), // decimal?
});
return groupedQuery;
}
My Model, example:
public class Obj
{
public Obj()
{ }
public int? IdRestricao { get; set; }
public int? IdTipoRestringido { get; set; }
public string CodRestringido { get; set; }
public string NomeRestringido { get; set; }
public int? IdTipoRestricao { get; set; }
public string CodRestricao { get; set; }
public string NomeRestricao { get; set; }
public DateTime? PeriodoInicio { get; set; }
public DateTime? PeriodoFim { get; set; }
public int? IdStatus { get; set; }
}
Request
www.url.com.br?IdRestricao=1&IdTipoRestringido=2&CodRestringido=3&NomeRestringido=4&IdTipoRestricao=5&CodRestricao=6&NomeRestricao=7&PeriodoInicio=8&PeriodoFim=9
code:
var model = obj.*tostring()*//
something like
var request = new RestRequest($"/api/{**model**}", Method.GET);
edit1:
I'm wondering if there is a library or something that transforms my object in my request as in the examples above
You can override ToString to format the data from the Obj class into GET parameters.
You could write a utility function to convert all object properties into nameof and the associated value into key-value pairs.
Something such as this answer to convert your Obj into a dictionary, and then another method to convert the dictionary into Prop=value could be done.
For a one off something like this would be suitable:
public override string ToString()
{
return $"IdRestricao={IdRestricao}&IdTipoRestringido={IdTipoRestringido}&CodRestringido={CodRestringido}...
}
I found the answer to what I was looking for, it follows below in case anyone needs it in the future
private static string ToQueryString(this object request, string separator = ",")
{
if (request == null)
throw new ArgumentNullException("request");
// Get all properties on the object
var properties = request.GetType().GetProperties()
.Where(x => x.CanRead)
.Where(x => x.GetValue(request, null) != null)
.ToDictionary(x => x.Name, x => x.GetValue(request, null));
// Get names for all IEnumerable properties (excl. string)
var propertyNames = properties
.Where(x => !(x.Value is string) && x.Value is IEnumerable)
.Select(x => x.Key)
.ToList();
// Concat all IEnumerable properties into a comma separated string
foreach (var key in propertyNames)
{
var valueType = properties[key].GetType();
var valueElemType = valueType.IsGenericType
? valueType.GetGenericArguments()[0]
: valueType.GetElementType();
if (valueElemType.IsPrimitive || valueElemType == typeof(string))
{
var enumerable = properties[key] as IEnumerable;
properties[key] = string.Join(separator, enumerable.Cast<object>());
}
}
// Concat all key/value pairs into a string separated by ampersand
return string.Join("&", properties
.Select(x => string.Concat(
Uri.EscapeDataString(x.Key), "=",
Uri.EscapeDataString(x.Value.ToString()))));
}
I am getting a error in the line below.
temp.day1_veh_p = string.Join(Environment.NewLine, day1.Where(x => x.plannedTriips == 1).Select(x => new {value=x.vehicleNumber+":"+x.shiftCompletedOn }).Cast<string>().ToArray());
Th error Message being
Unable to cast object of type '<>f__AnonymousType0`1[System.String]' to type 'System.String'.
The list day1 is of type
public class tripDetails
{
public string accountID { get; set; }
public string supplierName { get; set; }
public string supplierCode { get; set; }
public DateTime shiftFrom { get; set; }
public DateTime shiftTo { get; set; }
public int plannedTriips { get; set; }
public int actualTrips { get; set; }
public DateTime forDate { get; set; }
public string vehicleNumber { get; set; }
public string shiftCompletedOn { get; set; }
public class Comparer : IEqualityComparer<tripDetails>
{
public bool Equals(tripDetails x, tripDetails y)
{
return x.supplierCode == y.supplierCode;
}
public int GetHashCode(tripDetails obj)
{
return (obj.supplierCode).GetHashCode();
}
}
}
What exactly Am i doing wrong??
The problem is the new { value = ... }
Replace:
Select(x => new {value=x.vehicleNumber+":"+x.shiftCompletedOn }).Cast<string>()
with
Select(x => x.vehicleNumber+":"+x.shiftCompletedOn)
and you're sorted. You won't need the Cast<string>() at all.
Your original code creates, for each record, a new instance of an anonymous type that has a member called value with the string desired; the second version just creates the string.
In a way, it is no different to trying this:
class Foo
{
public string Bar {get;set;}
}
...
var foo = new Foo { Bar = "abc" };
string s = (string)foo; // doesn't compile
Yes, an anonymous type is not a string, so replace this
.Select(x => new { value = x.vehicleNumber + ":" + x.shiftCompletedOn })
with
.Select(x => x.vehicleNumber + ":" + x.shiftCompletedOn)
Then you can use the query(you don't need to create a new array) for string.Join.
It's also helpful to use multiple lines, it makes your code much more readable:
var vehicles = day1.Where(x => x.plannedTriips == 1)
.Select(x => x.vehicleNumber + ":" + x.shiftCompletedOn);
string str = string.Join(Environment.NewLine, vehicles);
Replace this
(x => new {value=x.vehicleNumber+":"+x.shiftCompletedOn }).Cast<string>()
by this
(x => String.Format("{0}\:{1}", x.vehicleNumber, x.shiftCompletedOn))
When you are doing new { ... } you are creating items of anonymous type and then (Cast<string()) trying explicitly cast to string and such conversion is not defined - youn ends up with appropriate exception.
I hope it's more clear what I want to do from the code than the title. Basically I am grouping by 2 fields and want to reduce the results into a collection all the ProductKey's constructed in the Map phase.
public class BlockResult
{
public Client.Names ClientName;
public string Block;
public IEnumerable<ProductKey> ProductKeys;
}
public Block()
{
Map = products =>
from product in products
where product.Details.Block != null
select new
{
product.ClientName,
product.Details.Block,
ProductKeys = new List<ProductKey>(new ProductKey[]{
new ProductKey{
Id = product.Id,
Url = product.Url
}
})
};
Reduce = results =>
from result in results
group result by new {result.ClientName, result.Block} into g
select new BlockResult
{
ClientName = g.Key.ClientName,
Block = g.Key.Block,
ProductKeys = g.SelectMany(x=> x.ProductKeys)
};
}
I get some weird System.InvalidOperationException and a source code dump where basically it is trying to initialize the list with an int (?).
If I try replacing the ProductKey with just IEnumerable ProductIds (and make appropriate changes in the code). Then the code runs but I don't get any results in the reduce.
You probably don't want to do this. Are you really going to need to query in this manner? If you know the context, then you should probably just do this:
var q = session.Query<Product>()
.Where(x => x.ClientName == "Joe" && x.Details.Block == "A");
But, to answer your original question, the following index will work:
public class Products_GroupedByClientNameAndBlock : AbstractIndexCreationTask<Product, Products_GroupedByClientNameAndBlock.Result>
{
public class Result
{
public string ClientName { get; set; }
public string Block { get; set; }
public IList<ProductKey> ProductKeys { get; set; }
}
public class ProductKey
{
public string Id { get; set; }
public string Url { get; set; }
}
public Products_GroupedByClientNameAndBlock()
{
Map = products =>
from product in products
where product.Details.Block != null
select new {
product.ClientName,
product.Details.Block,
ProductKeys = new[] { new { product.Id, product.Url } }
};
Reduce = results =>
from result in results
group result by new { result.ClientName, result.Block }
into g
select new {
g.Key.ClientName,
g.Key.Block,
ProductKeys = g.SelectMany(x => x.ProductKeys)
};
}
}
When replicating I get the same InvalidOperationException, stating that it doesn't understand the index definition (stack trace omitted for brevity).
Url: "/indexes/Keys/ByNameAndBlock"
System.InvalidOperationException: Could not understand query:
I'm still not entirely sure what you're attempting here, so this may not be quite what you're after, but I managed to get the following working. In short, Map/Reduce deals in anonymous objects, so strongly typing to your custom types makes no sense to Raven.
public class Keys_ByNameAndBlock : AbstractIndexCreationTask<Product, BlockResult>
{
public Keys_ByNameAndBlock()
{
Map = products =>
from product in products
where product.Block != null
select new
{
product.Name,
product.Block,
ProductIds = product.ProductKeys.Select(x => x.Id)
};
Reduce = results =>
from result in results
group result by new {result.Name, result.Block}
into g
select new
{
g.Key.Name,
g.Key.Block,
ProductIds = g.SelectMany(x => x.ProductIds)
};
}
}
public class Product
{
public Product()
{
ProductKeys = new List<ProductKey>();
}
public int ProductId { get; set; }
public string Url { get; set; }
public string Name { get; set; }
public string Block { get; set; }
public IEnumerable<ProductKey> ProductKeys { get; set; }
}
public class ProductKey
{
public int Id { get; set; }
public string Url { get; set; }
}
public class BlockResult
{
public string Name { get; set; }
public string Block { get; set; }
public int[] ProductIds { get; set; }
}
My result Expression is
var result = dtFields.AsEnumerable().Join(dtCDGroup.AsEnumerable(),
fieldList=>fieldList.Field<string>("CDGroupID"),
cd=>cd.Field<string>("CDGroupID"),
(fieldList,cd) => new
{
FieldID = fieldList.Field<string>("FieldID"),
Name = cd.Field<string>("Name"),
CDCaption = fieldList.Field<string>("CDCaption"),
Priority = ((cd.Field<string>("Priority") == null) ? 99 : cd.Field<int>("Priority")),
fldIndex = fieldList.Field<string>("fldIndex")
}).OrderBy(result => result.Priority).ThenBy(result => result.fldIndex);
Casting above result to array or list throws an invalid cast exception.
How can extract result of above expression?
Add .ToArray() or .ToList() call respectively
Try to add a strongly typed type:
public class NewModule
{
public int FieldID { get; set; }
public string Name { get; set; }
public string CDCaption { get; set; }
public int Priority { get; set; }
public int fldIndex { get; set; }
}
instead of the anonymous type then you could use ToList<NewModule>() like this:
var result = dtFields.AsEnumerable().Join(dtCDGroup.AsEnumerable(),
fieldList=>fieldList.Field<string>("CDGroupID"),
cd=>cd.Field<string>("CDGroupID"),
(fieldList,cd) => new NewModule
{
FieldID = fieldList.Field<string>("FieldID"),
Name = cd.Field<string>("Name"),
CDCaption = fieldList.Field<string>("CDCaption"),
Priority = ((cd.Field<string>("Priority") == null) ? 99 : cd.Field<int>("Priority")),
fldIndex = fieldList.Field<string>("fldIndex")
}).OrderBy(result => result.Priority)
.ThenBy(result => result.fldIndex)
.ToList<NewModule>();