How to pass a LINQ result to a method? - c#

From Efficient data structure to hold employee's activities? , i have a List of type ActividadEmpleado which is declared as:
public string Empleado { get; set; }
public DateTime Fecha { get; set; }
public string Actividad { get; set; }
The LINQ query variable reorders the result in the way i need, which is to store by date and then by ActividadEmpleado and a string. However, var types cannot be passed to methods, so searching this site i am finding out that i either need to create a class to store the results or to modify the LINQ variable to return a List, but i am having issues with the proper declaration.
The LINQ variable is:
var queryActividades = listaActividad
.GroupBy(a => a.Fecha, (fecha, fechaActividades) => new
{
Fecha = fecha,
FechaActividades = fechaActividades
.GroupBy(a => a.Empleado, (nombreEmpleado, actividadesEmpleado) => new
{
Actividades = actividadesEmpleado,
NombreEmpleado = nombreEmpleado
})
.OrderBy(a => a.NombreEmpleado)
})
.OrderBy(a => a.Fecha);
Visual Studio says that queryActividades is:
IOrderedEnumerable<'a>
Anonymous Types:
'a new datetime fecha, iorderedenumerable<'b>
'b new IEnumerable<ActividadEmpleado> Actividades, string NombreEmpleado
I need to pass queryActividades to another method. I tried passing it as an Object but then i lose the extension methods such as Where<> (can i cast it somehow?)
I also read that declaring the results as a tuple should work, but i think declaring a new class is cleaner.
I am just starting with LINQ, i have avoided it to use regular data structures but in this case it's really helpful and would like to know how to either handle anonymous types in them or convert the result to a regular List
Final solution:
class GrupoActividad
{
public DateTime Fecha { get; set; }
public IEnumerable<Actividad> Actividades { get; set; }
}
class Actividad
{
public IEnumerable<ActividadEmpleado> Actividades { get; set; }
public string NombreEmpleado { get; set; }
}
var queryActividades = listaActividad
.GroupBy(a => a.Fecha, (fecha, fechaActividades) => new GrupoActividad
{
Fecha = fecha,
Actividades = fechaActividades
.GroupBy(a => a.Empleado, (nombreEmpleado, actividadesEmpleado) => new Actividad
{
Actividades = actividadesEmpleado,
NombreEmpleado = nombreEmpleado
})
.OrderBy(a => a.NombreEmpleado)
})
.OrderBy(a => a.Fecha);
Receiving method:
var actividades = from a in queryActividades
where a.Fecha == fechaCiclo
select new { a.Fecha, a.Actividades };
foreach (var item in actividades)
{
//cycle that holds each day's activities
foreach (var empleado in item.Actividades)
{
//cycle that holds each employee with activities in that day
foreach (var actividad in empleado.Actividades)
{
//final cycle that actually reads the activity
ActividadEmpleado actividadEmpleado = (ActividadEmpleado)actividad;
}
}
}

Right now you are creating a collection that's based on an anonymous type (actually two anonymous types), which cannot practically be passed to another method (other than by using reflection or dynamic). The cleanest way is to create a concrete type that represents the collection - something like
public class ActivityGroup
{
public DateTime Fecha {get; set;}
public IEnumerable<Activity> Activities {get; set;}
}
public class Activity
{
public IEnumerable<Activity> Actividades {get; set;}
public string NombreEmpleado {get; set;}
}
then change your query to:
var queryActividades = listaActividad
.GroupBy(a => a.Fecha, (fecha, fechaActividades) => new ActivityGroup
{
Fecha = fecha,
FechaActividades = fechaActividades
.GroupBy(a => a.Empleado, (nombreEmpleado, actividadesEmpleado) => new Activity
{
Actividades = actividadesEmpleado,
NombreEmpleado = nombreEmpleado
})
.OrderBy(a => a.NombreEmpleado)
})
.OrderBy(a => a.Fecha);
and pass it as an IEnumerable<ActivityGroup>

You can use the approach presented by D Stanley. But it would be kind a boring to have to create such classes for any similar query that you write in the future. Instead, you can introduce as generic class for that, like this
public class Grouping<TKey, TElement>
{
public TKey Key { get; set; }
public IEnumerable<TElement> Elements { get; set; }
}
and use it instead of the anonymous types like this
var queryActividades = listaActividad
.GroupBy(a => a.Fecha, (fecha, fechaActividades) => new Grouping<DateTime, Grouping<string, ActividadEmpleado>>
{
Key = fecha,
Elements = fechaActividades
.GroupBy(a => a.Empleado, (nombreEmpleado, actividadesEmpleado) => new Grouping<string, ActividadEmpleado>
{
Key = nombreEmpleado,
Elements = actividadesEmpleado
})
.OrderBy(a => a.Key)
})
.OrderBy(a => a.Key);
which can be be passed as IEnumerable<Grouping<DateTime, Grouping<string, ActividadEmpleado>>>.
As you can see, there is a trade off between reusability and readability. Basically this is a Tuple with a little more meaningful names. Note that although we cannot improve the verbosity in the result, we can use a similar technique to Tuple.Create to remove the verbosity inside the query, by adding a class like this
public static class Grouping
{
public static Grouping<TKey, TElement> Create<TKey, TElement>(TKey key, IEnumerable<TElement> elements)
{
return new Grouping<TKey, TElement> { Key = key, Elements = elements };
}
}
and use it like this
var queryActividades = listaActividad
.GroupBy(a => a.Fecha, (fecha, fechaActividades) => Grouping.Create(
fecha, fechaActividades
.GroupBy(a => a.Empleado, (nombreEmpleado, actividadesEmpleado) => Grouping.Create(
nombreEmpleado, actividadesEmpleado))
.OrderBy(a => a.Key)))
.OrderBy(a => a.Key);

You can't pass anonymous types between methods; if you need to pass the data to another method, you need to create an explicit named type that contains the Actividades and NombreEmpleado properties, and another one with Fecha and FechaActividades.

Related

Map property to property of a collection automapper

I have two entities
public class A{
public string Example { get; set; }
public ICollection<B> BCollection { get;set; } = new HashSet<B>();
}
public class B {
public string MyProperty { get; set; }
}
And a simple ViewModel
public class AFirstLoadViewModel {
public string Example { get; set; }
public string MyProperty { get; set; }
}
The thing, is, this viewmodel will be use only in the first data entry, when A will only have one B object inside.
So, i'm trying to map a object like this:
var source = new AFirstLoadViewModel
{
Example = "example",
MyProperty = "myproperty"
}
to this
var destination = new A {
Example = "example"
BCollection = new List<B> {
new B { MyProperty = "myproperty" }
}
}
I try to do the trick using ForPath and BeforeMap without luck
CreateMap<AFirstLoadViewModel, A>()
.ForMember(x => x.Example, c => c.MapFrom(x => x.Example))
.ForPath(x => x.BCollection.First().MyProperty, c => c.MapFrom(x => x.MyProperty))
.BeforeMap((viewModel, entity) => {
if(!entity.BCollection.Any())
BCollection.Add(new B());
});
But i get
System.ArgumentOutOfRangeException: Only member accesses are allowed.
How can i deal with it?
I clarify: both, view model and model have many more properties, the question classes are by way of example
Edit:
I try the solution proposed by Johnatan, and it works, the problem here, is that i cant Unit Testing anymore.
I'm testing with
var config = new MapperConfiguration(cfg => cfg.CreateMap<AFirstLoadViewModel, A>(MemberList.Source));
And when i call config.AssertConfigurationIsValid() fails because the MyProperty property is not mapped
The problem is you are trying to map to .First(). First does not yet exist because the query is on a null / empty collection. You can't assign to the .First() element in a collection if one does not exist already. Instead just map as a collection directly.
CreateMap<AFirstLoadViewModel, A>()
.ForMember(x => x.Example, c => c.MapFrom(x => x.Example))
.ForMember(x => x.BCollection, c => c.MapFrom(x => new [] { new B { MyProperty = x.MyProperty } }));
CreateMap<AFirstLoadViewModel, A>()
.ForMember(x => x.Example, c => c.MapFrom(x => x.Example))
.ForMember(x => x.BCollection, c => c.MapFrom(x => new [] { new B { MyProperty = x.MyProperty } }));

C# Dynamic Test for fields before aggregation functions like GroupBy

Assume the following code:
var citizens = await _stateProvider.SelectWhere(whereParams);
var retDto = new PercentGroupBy()
{
Total = citizens.Count,
Elements = citizens.GroupBy(p => p.Content.Current.AggState.ToString()).ToDictionary(g => g.Key, g => g.Count())
};
return retDto;
citizens is a list of the following class:
public class Citizen {
public string ETag { get; set; }
public string Name{ get; set; }
public dynamic Content { get; set; }
}
What is the best option to test that "p.Content.Current.AggState" property exists?
SelectWhere might return a few citizens where Content.Current is null and therefore asking for AggState throws error.
Oops I found the answer while posting the question, so here it is to share the knowledge:
Add a fluid "Where" before "GroupBy"
var citizens = await _stateProvider.SelectWhere(whereParams);
var retDto = new PercentGroupBy()
{
Total = citizens.Count,
Elements = citizens
.Where(p => p.Content.Current != null)
.GroupBy(p => p.Content.Current.AggState.ToString())
.ToDictionary(g => g.Key, g => g.Count())
};
return retDto;

Order by array values in Linq to Entity Framework Query

I am trying to write an OrderBy clause in a Linq to EntityFramework query. My problem is that the entity table I am looking at stores an ID, that relates to a table in a different database and I cannot adjust the database.
MainDatabase.EntityToOrder
ID
Name
OtherID
SecondDatabase.OtherEntity
ID
Name
My C# EntityToOrder Model looks like this, and I need to be able to order by "OtherName"
EntityToOrder.cs
public class EntityToOrder
{
[DataMember]
public long ID { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public long OtherId { get; set; }
public string OtherName { get; set; }
}
So, I would like to Order EntityToOrder by "OtherName" in the most efficient way possible. My existing query looks like this.
var entities = mainContext.EntityToOrder.OrderBy(e => e.Name).Skip(startIndex).Take(pageSize).ToList();
var otherIds = entities.Select(e => e.OtherID).ToList();
Dictionary<long, string> otherNames = secondContext.OtherEntity
.Where(oe => otherIds.Contains(oe.ID))
.Select(oe => new { ID = oe.ID, Name = oe.Name })
.ToDictionary(oe => oe.ID, oe => oe.Name);
entities.ForEach(e => OtherName = otherNames[e.OtherID]);
How can I write the most efficient query to order by "OtherName", preferably avoiding selecting the whole EntityToOrder table into memory.
Update
For clarity, here is some code that achieves the OrderBy, but needs to retrieve the entire EntityToOrder table into memory. I was hoping this could be achieved in a more efficient way. Also, the OtherEntity can belong to many EntityToOrder rows.
var entities = mainContext.EntityToOrder.ToList();
var otherIds = entities.Select(e => e.OtherID).ToList();
Dictionary<long, string> otherNames = secondContext.OtherEntity
.Where(oe => otherIds.Contains(oe.ID))
.Select(oe => new { ID = oe.ID, Name = oe.Name })
.ToDictionary(oe => oe.ID, oe => oe.Name);
entities.ForEach(e => OtherName = otherNames[e.OtherID]);
return entities.OrderBy(e => e.OtherName).Skip(startIndex).Take(pageSize).ToList();
Quite challenging task. I was thinking initially just to switch the roles and perform pagination (OrderBy/Skip/Take) on OtherEntity table, but unfortunately that doesn't work due to one to many relationship. So I ended up with doing some pre pagination in memory on OtherEntity. However, in order to do that I needed counts of the matching items in EnityToOrder, so this is retrieved with additional db query, which makes the solution involving 3 db queries and some memory processing. Here it is
var countByOtherId = db.EntityToOrder
.GroupBy(e => e.OtherId)
.Select(g => new { ID = g.Key, Count = g.Count() })
.ToDictionary(e => e.ID, e => e.Count);
var other = new Dictionary<long, string>();
int skipCount = startIndex, useCount = 0;
foreach (var e in db.OtherEntity.OrderBy(e => e.Name))
{
int count;
if (!countByOtherId.TryGetValue(e.ID, out count)) continue;
if (skipCount > 0 && other.Count == 0)
{
if (skipCount >= count) { skipCount -= count; continue; }
count -= skipCount;
}
other.Add(e.ID, e.Name);
if ((useCount += count) >= pageSize) break;
}
var entities = db.EntityToOrder
.Where(e => other.Keys.Contains(e.OtherId))
.AsEnumerable()
.Select(e => new EntityToOrder { ID = e.ID, Name = e.Name,
OtherId = e.OtherId, OtherName = other[e.OtherId] })
.OrderBy(e => e.OtherName).ThenBy(e => e.Name)
.Skip(skipCount).Take(pageSize)
.ToList();
Now, I'm not quite sure if that's better to what are you doing currently, but it's worth trying.
If you can change the model, then you might try the following:
public class EntityToOrder
{
[DataMember]
public long ID { get; set; }
[DataMember]
public string Name { get; set; }
[DataMember]
public long OtherId { get; set; }
[ForeignKey("OtherId")]
public OtherEntity OtherEntity{ get; set; }
}
Then, you should be able to perform this query:
using System.Data.Entity;
var entities = mainContext
.EntityToOrder
.Include(x => x.OtherEntity)
.OrderBy(e => e.OtherEntity.Name)
.Skip(startIndex)
.Take(pageSize)
.ToList();
Edit :
Sorry, I missed the point that you had 2 databases....
I found an alternative which I thought I would post in case it is useful to anyone. I used a .Join() to merge the dictionary of OtherEntity into my query. This still selects into an IEnumerable so I don't think it is more efficient.
var entities = mainContext.EntityToOrder;
var otherIds = entities.Select(e => e.OtherID).ToList();
Dictionary<long, string> otherNames = secondContext.OtherEntity
.Where(oe => otherIds.Contains(oe.ID))
.Select(oe => new { ID = oe.ID, Name = oe.Name })
.ToDictionary(oe => oe.ID, oe => oe.Name);
Func<EntityToOrder, KeyValuePair<long, string>, EntityToOrder> joinFunc = ((a, b) => {
a.OtherName= b.Value;
return a;
});
return entities.Join(otherNames, e => e.OtherID, oe => oe.Key, joinFunc)
.OrderBy(e => e.OtherName)
.Skip(startIndex)
.Take(pageSize)
.ToList();
Note on Includes
When applying Join you select into an IEnumerable and therefore lose the ability to access properties from a linked table. To counter this you would need to add a .Include() for any linked table you need to access before applying the .Join(). E.g.
var entities = mainContext.EntityToOrder
.Include("LinkedEntity");
return entities.Join(otherNames, e => e.OtherID, oe => oe.Key, joinFunc)
.OrderBy(e => e.OtherName)
.ThenBy(e => e.LinkedEntity.Name) //reference to linked table
.ToList();

Cannot access members of a class in the select linq method

static void Main(){
List<Foo> t = new List<Foo>{
new Foo(){Id=1,Name="A",Value=1},
new Foo(){Id=2,Name="B",Value=1},
new Foo(){Id=3,Name="C",Value=1},
new Foo(){Id=3,Name="D",Value=1}};
var x = t.GroupBy(gp => gp.Id).Select(sel => new Foo { Id = ,Name=,Value= });
}
public class Foo{
public string Name { get; set; }
public int Id { get; set; }
public int Value { get; set; }
}
In the var x I want to group all the Foo objects by their ID and get the SUM in the Value field.
The problem is that it seems I cannot access the members/fields of the class in the select method.
Thanks
After GroupBy you don't select an IEnumerable<Foo> but groups of them. You probably want:
var x = t.GroupBy(f => f.Id)
.Select(grp => new Foo {
Id = grp.Key,
Name = String.Join(",", grp.Select(f => f.Name)),
Value = grp.Sum(f => f.Value)
});
I'm using String.Join to concenate all names of each ID-group, the values are summed.
Try this way
var x = t.GroupBy(gp => gp.Name).OrderBy(group => group.Key).Select(group => Tuple.Create(group.Key, group.Count()));

Linq How to perform Subquery with max function that returns a primitive value such as an integer?

I have the below query. :
List<MyItem> myList
= randomEntity
.GroupBy(x => new {
x.Id,
x.randomInnerEntity.Name,
x.randomInnerEntity.Number
})
.Select(z => new MyItem
{
Id = z.Key.Id,
Name = z.Key.Name,
Totals = z.Count(),
LatestObj = randomEntity.Where(x => x.Id == z.Key.Id)
.GroupBy(x => x.Id)
.Select(gr => new {
item1 = gr.Key,
item2 = gr.Max(x => x.SomeInteger.Value)
})
})
.OrderBy(z => z.Name)
.ToList();
As you can see the inner subquery, that provides a dynamic object of LatestObj works but it provides an anonymous object. I was wondering how to perform the same query but return a primitive type like integer.
***As requested
public class MyItem
{
property Guid? Id { get; set; }
property String Name { get; set; }
property Int MaxValueTryingToGet { get; set; } //This is the field I would like to set with the integer max
property Int Totals { get; set; }
property dynamic LatestObj { get; set; }
property randInner randomInnerEntity { get; set; }
}
LatestObj = randomEntity.Where(x => x.Id == z.Key.Id)
.GroupBy(x => x.Id)
.Select(gr => new {
item1 = gr.Key,
item2 = gr.Max(x => x.SomeInteger.Value)
})
Since you are just selecting items where the item id matches z.Key.Id, there does not seem to be much point grouping by id.
You can should be able to get the maximum value directly by using
LatestObj = randomEntity.Where(x => x.Id == z.Key.Id).Max(x -> x.SomeInteger.Value)

Categories

Resources