How to get dynamic type property? - c#

I have a project that I should place some code in other layer and because of this I should transfer a linq query to method.
This code:
var HRPersonnelContactInfoTelService = App.Api.HRPersonnelContactInfoTelService.Instance().Data();
var SMSGroupMemberService = App.Api.SMSGroupMemberService.Instance().Data();
return (from x in SMSGroupMemberService
where Recivers.Contains(x.GroupID)
join v in HRPersonnelContactInfoTelService on x.Pers_Code equals v.Pers_Code
select new { Pers_Code = (int)x.Pers_Code, Tel = v.Tel }).ToList();
I converted up code to:
public dynamic HRPersonnelContactInfoTelMethod(List<int> Recivers)
{
var HRPersonnelContactInfoTelService = App.Api.HRPersonnelContactInfoTelService.Instance().Data();
var SMSGroupMemberService = App.Api.SMSGroupMemberService.Instance().Data();
return (from x in SMSGroupMemberService
where Recivers.Contains(x.GroupID)
join v in HRPersonnelContactInfoTelService on x.Pers_Code equals v.Pers_Code
select new { Pers_Code = (int)x.Pers_Code, Tel = v.Tel }).ToList();
}
but when I use it in foreach
An exception of type 'Microsoft.CSharp.RuntimeBinder.RuntimeBinderException' occurred in System.Core.dll but was not handled in user code
Additional information: 'object' does not contain a definition for 'Pers_Code'
Use it like this:
var q = App.Api.HRPersonnelContactInfoTelService.Instance().HRPersonnelContactInfoTelMethod(Recivers);
foreach (var item in Recivers)
{
var res = App.Api.SMSMessageGroupService.Instance().AddOrUpdate(null, MessageId, item);
}
foreach (var z in q)
{
string SendNumber = Number[1].Trim().Substring(0, 3) == "+98" ? Number[1].Trim() : "+98" + Number[1].Trim();
var res = App.Api.SMSMessageLogService.Instance().AddOrUpdate(null, MessageId, (int)z.Pers_Code, z.Tel.ToString(),
0, int.Parse(ddlSMSWorkingGroups.SelectedValue.ToString()), (int)z.Pers_Code, SendNumber, 0);
send.SendSMS("nazmaran", "qwerty", SendNumber, "09122596898", txtPredefinedRemarks.Text);
}

I would never use dynamic to return the result of a linq query that uses anonymous types to project the results. Instead, I would create a class that holds the results:
public class SomeName
{
public int Pers_Code { set; get; }
public string /* Change to Correct Type */ Tel { set; get;}
}
Usage:
public List<SomeName> HRPersonnelContactInfoTelMethod(List<int> Recivers)
{
var HRPersonnelContactInfoTelService = App.Api.HRPersonnelContactInfoTelService.Instance().Data();
var SMSGroupMemberService = App.Api.SMSGroupMemberService.Instance().Data();
return (from x in SMSGroupMemberService
where Recivers.Contains(x.GroupID)
join v in HRPersonnelContactInfoTelService on x.Pers_Code equals v.Pers_Code
select new SomeName() { Pers_Code = (int)x.Pers_Code, Tel = v.Tel }).ToList();
}
Creating a type to hold the results is nothing compared to the complexity of used dynamic.

It seems, that .NET can not perform dynamic typization with var and iterator through dynamic, that presents a list of object.
So, when you are creating variable with var - .NET can not predict a return type and creates a new object variable, as every type is inherited from object.
However, implementing a return type as dynamic is a bad practice - possible errors can not be found until you does not call this method. So, implement a usage of another class, as #user3185569 suggested.

Related

Converting a GroupJoin statement to an Expression Tree

I'm attempting to do an outer join on two sets of data using this statement:
var destinationList = inners.GroupJoin(outers, inner => inner.JoinField, outer => outer.JoinField,
(inner, outerList) =>
outerList.Select(outer => new DestinationModel { Id = inner.JoinField, AggregationField = outer.DataField })
.DefaultIfEmpty(new DestinationModel { Id = inner.JoinField })).SelectMany(destination => destination).ToList();
This works correctly without problem, but I ultimately need to convert this to an expression tree to allow the datasets and the fields to change.
My data models look like this:
InnerModel:
public class InnerModel
{
public int JoinField;
public decimal DataField;
}
OuterModel:
public class OuterModel
{
public int JoinField;
public decimal DataField;
}
DestinationModel:
public class DestinationModel
{
public int Id;
public decimal AggregationField;
}
inners is a List<InnerModel>
outers is a List<OuterModel>
I've managed to get most of the way, but I'm falling short at the last step. This is what I have so far:
// Declare variables
var innerParameter = Expression.Parameter(typeof (InnerModel), "inner");
var innerSelect = Expression.Lambda<Func<InnerModel, int>>(Expression.Field(innerParameter, "JoinField"), innerParameter);
var outerParameter = Expression.Parameter(typeof (OuterModel), "outer");
var outerListParameter = Expression.Parameter(typeof (IEnumerable<OuterModel>), "outerList");
var outerSelect = Expression.Lambda<Func<OuterModel, int>>(Expression.Field(outerParameter, "JoinField"), outerParameter);
var existingBinding = Expression.MemberInit(Expression.New(typeof (DestinationModel)), Expression.Bind(typeof (DestinationModel).GetField("Id"), Expression.Field(innerParameter, "JoinField")));
// Create lambdas
var selector = Expression.Lambda<Func<OuterModel, DestinationModel>>(existingBinding, outerParameter);
var selectMethod = typeof (Enumerable).GetMethods().First(x => x.Name == "Select" && x.GetParameters().Length == 2).MakeGenericMethod(typeof(OuterModel), typeof(DestinationModel));
var selectCall = Expression.Call(selectMethod, outerListParameter, selector);
// Create the inner key selector for the GroupJoin method
var innerKeySelector = Expression.Lambda(selectCall, innerParameter, outerListParameter);
Everything works up until this point. When I try to plug the innerKeySelector into the original statement:
var result = inners.GroupJoin(outers, innerSelect.Compile(), outerSelect.Compile(), (inner, outerList) => outerList.Select(outer => new DestinationModel {Id = inner.JoinField, AggregationField = outer.DataField}).DefaultIfEmpty(new DestinationModel {Id = inner.JoinField})).SelectMany(destination => destination).ToList();
I get a compile error:
The type arguments for method 'Enumerable.GroupJoin(IEnumerable, IEnumerable, Func, Func, Func, TResult>)' cannot be inferred from the usage. Try specifying the type arguments explicitly.
I know I'm just missing something obvious, but after working on this for hours, I'm not seeing it. Can someone point me in the right direction?
I found my answer. I needed to place the DefaultIfEmpty call and give it the result of the Select call. I created a MethodInfo for the DefaultIfEmpty call:
var defaultIfEmptyMethod = typeof (Enumerable).GetMethods().First(x => x.Name == "DefaultIfEmpty" && x.GetParameters().Length == 2).MakeGenericMethod(typeof (DestinationTestModel));
Then I created a lambda expression that calls DefaultIfEmpty and Select together:
var innerKeySelectorWithDefault = Expression.Lambda<Func<InnerTestModel,IEnumerable<OuterTestModel>,IEnumerable<DestinationTestModel>>>(Expression.Call(defaultIfEmptyMethod, selectCall, nonExistingBinding), innerParameter, outerListParameter);
That enabled me to call the final methods:
var result = inners.GroupJoin(outers, innerSelect.Compile(), outerSelect.Compile(),innerKeySelectorWithDefault.Compile()).SelectMany(destination => destination).ToList();

Cannot convert type 'AnonymousType#1'

Do you know how fix this error? It` s show error in this line "foreach (int s in itemColl)"
What I have to do?
Error 1 Cannot convert type 'AnonymousType#1' to
'int' C:\Users\Rafal\Desktop\MVC ksiązka\moj
projekt\sklep\SportsStore.WebUI\Controllers\ProductController.cs 37 21 SportsStore.WebUI
var itemColl = from p in re.Kategorie
where p.Nazwa == category
select new
{
p.Id_kat
};
foreach (int s in itemColl)
{
Console.WriteLine(s);
}
You are selecting itemColl with new keyword, defining an anonymous type, you can't apply foreach loop with int type. Your current query is returning something like IEnumerable<AnonymousType>
Instead you may do:
var itemColl = from p in re.Kategorie
where p.Nazwa == category
select p.Id_Kat;
This will return IEnumerable<int> and that you can use in your current foreach loop.
But if you want to use your current query with selection on anonymous type, you have to modify your foreach loop with implicit type var, and since your current query is returning an anonymous type object you may select the Id_kat from the object. Something like.
foreach (var s in itemColl)
{
Console.WriteLine(s.Id_kat);
}
IMO, the second approach is not recommended because you are just returning an int type wrapped inside an anonymous type. Its better if you can change your query to return IEnumerable<int>
You just need to change the select to:
var itemColl = from p in re.Kategorie
where p.Nazwa == category
select p.Id_kat;
This will create an IEnumerable<int> which is what you are trying to use in the foreach.
The select new { p.Id_Kat } is creating a new Anonymous Type which is in the simplest way of saying it is a new class like this:
class AnonymousType#1
{
public int Id_Kat {get; set;}
}
var itemColl = from p in re.Kategorie
where p.Nazwa == category
//This is Anonymous Type
select new
{
//Anonymous type's attribute(s) go(es) here
IntItem = p.Id_kat
};
foreach (var s in itemColl)
{
Console.WriteLine(s.IntItem);
}
Well, you could return a real (int)-value instead of an anonymous linq-result
var itemColl = from p in re.Kategorie
where p.Nazwa == category
select p.Id_Kat;

IEnumerable.except error

I have two IEnumerable<dynamic> of data retrieved from database tables, called FullSet and InScopeSubSet .
The second IEnumerable<dynamic> is a subset of the first set (both sets have ChildID as their unique ID)
I would like to create a new IEnumerable<dynamic> that contains only items from x that do not occur in y
I have tried the following but it will not compile. It says:
"extension methods cannot be dynamically dispatched. Consider casting the dynamic arguments or calling the extension method without the extension method syntax"
var sql = "SELECT ChildID, FirstName, Surname FROM Child ORDER BY ChildID ASC";
var FullSet = DB.Query(sql);
sql = "
SELECT UserScope.ChildID, Child.FirstName, Child.Surname
FROM UserScope
INNER JOIN Child ON UserScope.ChildID=Child.ChildID
WHERE UserAccountID = #0 ORDER BY ChildID ASC
";
var InScopeSubSet = DB.Query(sql, UserAccount.UserAccountID);
var OutScopeSubSet = FullSet .Except(InScopeSubSet );
To resolve the compiler error, use its second suggestion:
var OutScopeSubSet = Enumerable.Except(FullSet, InScopeSubSet);
The Enumerable one runs ok but returns the whole first set without taking out any items.
If that's the case, you are probably getting reference comparisons on objects that are not identical. You might be able to do it by implementing a custom IEqualityComparer. The call becomes
var OutScopeSubSet = Enumerable.Except(FullSet, InScopeSubSet, new DynamicChildIdComparer());
And DynamicChildIdComparer is:
class DynamicChildIdComparer : IEqualityComparer<object>
{
public bool Equals(object x, object y)
{
return ((dynamic)x).ChildID.Equals(((dynamic)y).ChildID);
}
public int GetHashCode(object obj)
{
return ((dynamic)obj).ChildID.GetHashCode();
}
}
Are you looking for a "not in" SQL type operation
"Not in" in LINQ can be implemented like this :
var filterData = new List { "ListA", "ListB" };
var data= context.table.Select( c => c.id ).Except( filterData );

Should my DAL return DataTables or I...whatever<>?

EDIT 1
I apologize but after reading the 2 suggested articles I still don't understand what I should use. I understand that using IQueryable is not preferred for various reasons but does that eliminate IEnumerable as well? Is a DataTable really my best option?
In short, I guess, what is the preferred Return type?
I have the following simple LINQ query that I want to abstract out into a DAL. What is the type of var and therefore what type should my method be?
ConnectDBDataContext context = new ConnectDBDataContext();
var lName = textEdit1.Text;
var searchByPersonLName = from c in context.tblPersons
where c.LastName == lName
orderby c.LastName
select new { c.FirstName,c.LastName,c.PersonID};
dataGridView1.DataSource = searchByPersonLName;
When I hover over it in VS it says IQueryable<T> but when I put in a breakpoint and run it it seems to call itself IEnumerable. Which is correct and how should I declare my method?
Like this -->
public static DataTable SearchPerson(string SearhParam)
{
ConnectDBDataContext context = new ConnectDBDataContext();
var persons = (from person in context.tblPersons
orderby person.LastName
select new { person.PersonID, person.LastName, person.FirstName, person.SSN });
var filteredPersonsList = persons.Where(p => p.LastName == SearhParam).ToList();
if (filteredPersonsList.Count == 0)
filteredPersonsList = persons.Where(p => p.LastName.StartsWith(SearhParam)).ToList();
var dataTable = filteredPersonsList.CopyLinqToDataTable();
return dataTable;
}
If I use IQueryable<T> what is <T> or how do I know that and what would I return?
Thanks!
For Reference the CopyToDataTable() is below.
public static DataTable CopyLinqToDataTable<T>(this IEnumerable<T> source)
{
return new ObjectShredder<T>().Shred(source, null, null);
}
public static DataTable CopyLinqToDataTable<T>(this IEnumerable<T> source,
DataTable table, LoadOption? options)
{
return new ObjectShredder<T>().Shred(source, table, options);
}
First off, IQueryable implements IEnumerable, so that is why you are potentially seeing both. See here for more details
Generally, I would recommend your DAL return your actually objects whenever possible.
I would read this blog for guidelines on how to, and how not to do what you are suggesting. Short answer, don't return IQueryable.
EDIT:
Example:
internal static File[] GetAllFilesByUserID(int userID)
{
var db = GetDataContext();
return (from files in db.Files where files.OwnerUserID == userID select files).ToArray();
}
What he means is to map your data to the object you are wanting the DAL to return.
In answer to your first question "var" is really just short for variable, and the type is what ever type is defined in the assignment.
var myvariable = string.empty;
In this example the type is that of a string.
var myreader = new StringReader();
While in this example the type is that of a StringReader.
As for your second question of "what is ". T is a generic type.
For an example of where your dal would be returning an actual object:
public Product GetProduct(int ProductID)
{
var product = from p in db.MyTable
where p.productID == ProductID
select new product { name = p.name, pricepoint = p.pricepoint, qty = p.quantity };
return product;
}

Linq To Sql return from function as IQueryable<T>

Ok, I have managed to get the following working
public IQueryable getTicketInformation(int ticketID)
{
var ticketDetails = from tickets in _context.tickets
join file in _context.file_objects on tickets.ticket_id equals file.source_id
where tickets.ticket_id == ticketID
select new { tickets.ticket_id, tickets.title, tickets.care_of_email, file.filename };
return ticketDetails.AsQueryable();
}
I went ahead and created my own class (myObject) containing the primitives
ticket_id, title, care_of_email and filename. Which are the items I am returning in my linq statement.
I modified my statement to be
public IQueryable<myObject> getTicketInformation(int ticketID)
{
var ticketDetails = from tickets in _context.tickets
join file in _context.file_objects on tickets.ticket_id equals file.source_id
where tickets.ticket_id == ticketID
select new { tickets.ticket_id, tickets.title, tickets.care_of_email, file.filename };
return ticketDetails.AsQueryable()<myObject>;
}
thinking that this would make it type safe with generics, but I get the error
"Cannot convert method group 'AsQueryable' to non-delegate type 'System.Linq.IQueryable'. Did you intend to invoke the method?"
Is what I am trying to do even possible?
Does the myObject class need to implement IEnumerable or IQueryable?
Or is it best to construct the object MyObject from the linq resultset and then just return from the function the object MyObject
public myObject getTicketInformation(int ticketID)
{
....linq statement....
myObject o = null;
foreach (obj in linqstatemt)
{
o = new myObject();
o.ticket_id = obj.ticket_id
.......
}
return o;
}
You mean:
select new MyObject { TicketId = tickets.ticket_id,
Title = tickets.title, ...};
(note I tweaked the names slightly to be more C#-idiomatic)
This is an "object initializer" that creates a new MyObject (per record) and assigns the properties from the source data. What you had was an "anonymous type" initializer, which isn't the same. Note that if you have a non-default constructor, you could also use something like:
select new MyObject(tickets.ticket_id, tickets.title);
which uses the specified constructor, passing in the supplied values from the source data.
This will then be IQueryable<MyObject>; you don't need to call .AsQueryable(). Note it would be better for your function to return the typed form (IQueryable<MyObject>) than the untyped IQueryable.
This line is syntactically incorrect:
return ticketDetails.AsQueryable()<myObject>;
and should read
return ticketDetails.AsQueryable<myObject>();
Also, you're creating anonymous objects with the select new {, but you want to create myObject instances. A correct implementation would look like this:
public IQueryable<myObject> getTicketInformation(int ticketID)
{
return from tickets in _context.tickets
join file in _context.file_objects on tickets.ticket_id equals file.source_id
where tickets.ticket_id == ticketID
select new myObject() {
ticket_id = tickets.ticket_id,
title = tickets.title,
care_of_email = tickets.care_of_email,
filename = file.filename
};
}
The new SomeClass() { Property = value, ... syntax creates a SomeClass instance and sets the properties to the given values. Alternatively you could implement a constructor on the myObject class and call it in the linq statement with select new myObject(...).
As Marc stated you're not constructing instances of myObject when your query is run. But additionally you don't need to cast it to an IQueryable<T>, a LINQ select statment will return an IQueryable<T> unless explicity cast to an IEnumerable<T>.
Also, be careful that your DataContext hasn't been disposed of before you try and access the data being returned. But I noticed your context is not constructed in the method, be careful that you're not maintaining a DataContext for too long, it's a unit-of-work object and not meant to be kept open for long periods of time.
Gents,
It all makes sense as long as you're only returning single table, but what if there's two or more to be returned???
RPDTDataContext smdt = new RPDTDataContext();
var projectedUsers = smdt.SM_Users.Join(
smdt.SM_CTSGroups, u => u.CtsGroupID, c => c.id,
(u, c) => new { CTSGroup = c.Name, UserName = u.Name, u.EmpID, u.Email });
return projectedUsers;

Categories

Resources