I've got a method I've been using against IEnumerable no problem. However I want to start using it against IQueryable as well. However, the way I currently have it wont work as its trying to execute my method against the database.
The situation is as follows. I want a property on the object I'm selecting into to be be null if the value selecting from is null or the Id and Name of the property if it exists. For example:
var foos = FooRepository.All().Select(s => new FooBrief()
{
Id = s.Id,
Customer = SimpleData.To(s.Customer, m => m.Id, m => m.Name)
});
where SimpleData.To looks like:
public class SimpleData
{
public int Id { get; set; }
public string Name { get; set; }
public static SimpleData To<T>(T t, Func<T, int> id, Func<T, string> name) where T : class
{
if (t != null)
{
return new SimpleData { Id = id(t), Name = name(t) };
}
return null;
}
}
Is there someway I can get this behaviour whilst allowing it to execute against the database?
NOTE: Because of reasons elsewhere in my code I cannot use .ToList(). I may be adding additional filtering at a later point
The simplest approach is just to perform the selection outside the database, using AsEnumerable:
var foos = FooRepository.All()
.Select(x => new { Id = x.Id,
CustomerId = x.Customer.Id,
CustomerName = x.Name })
.AsEnumerable()
.Select(s => new FooBrief {
Id = s.Id,
Customer = new SimpleData {
Id = s.CustomerId,
Name = s.CustomerName
}
});
The first Select is just to make sure that the database query only pulls out the required fields. If you really still want to use your SimpleData.To method:
// Outdented to avoid scrolling
var foos = FooRepository.All()
.Select(x => new { Id = x.Id,
CustomerId = x.Customer.Id,
CustomerName = x.Name })
.AsEnumerable()
.Select(s => new FooBrief {
Id = s.Id,
Customer = SimpleData.To(s,
s => s.CustomerId,
s => s.CustomerName)
});
Related
Using EF Core, I want to asynchronously obtain a list of FooModel which have a collection property of ChildModel.
public class FooModel
{
public Guid Id { get; set; }
public string Name { get; set; }
public List<ChildModel> Childs { get; set; }
}
While the synchronous version returns without issues, the async one will cause the application to freeze.
//Async version.
public static async Task<List<FooModel>> ListFooModelAsync()
{
using (var db = new AppDbContext())
{
var foo_items = await db.Foos
.Include(e => e.Childs)
.Select(e => new FooModel
{
Id = e.Id,
Name = e.Name,
Childs = e.Childs.Select(
child => new ChildModel { Id = child.Id, Name = child.Name })
.ToList()
})
.ToListAsync()
.ConfigureAwait(false);
return foo_items;
}
}
I think the ToList() call on Childs is causing a deadlock somewhere in the pipeline.
If I remove the .ToList() in the Childs construction line it wont freeze and return the list of FooModel, but its Childs collection will be of type Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.EnumerableAdapter. As soon as I try to use the result in the client the application stop responding, supposedly because EF tries to resolve Childs collection but there is not a DbContext available at that point.
Any thought on how to resolve this?
//Sync version works fine.
public static List<FooModel> ListFooModel()
{
using (var db = new AppDbContext())
{
var foo_items = db.Foos
.Include(e => e.Childs)
.Select(e => new FooModel
{
Id = e.Id,
Name = e.Name,
Childs = e.Childs.Select(
child => new ChildModel { Id = child.Id, Name = child.Name })
.ToList()
})
.ToList();
return foo_items;
}
}
You can break the fetching from the database and the re-shaping into separate queries like this:
public static async Task<List<FooModel>> ListFooModelAsync()
{
using (var db = new AppDbContext())
{
var foo_items = await db.Foos
.Include(e => e.Childs)
.ToListAsync();
var results = foo_items
.Select(e => new FooModel
{
Id = e.Id,
Name = e.Name,
Childs = e.Childs.Select(
child => new ChildModel { Id = child.Id, Name = child.Name })
.ToList()
}).ToList();
return results;
}
}
public static async Task<List<FooModel>> ListFooModelAsync()
{
using (var db = new AppDbContext())
{
var foo_items = await db.Foos
.Select(e => new FooModel
{
Id = e.Id,
Name = e.Name,
Childs = e.Childs.Select(
child => new ChildModel { Id = child.Id, Name = child.Name })
}).ToList()
.ToListAsync();
return foo_items;
}
}
I'm doing this very type of nested projection in my app (slightly modified in my answer). If this is still causing deadlocks for you, maybe you need to examine how you are calling this procedure. I'm am full async from Controller down to Database so maybe that your issue? Note- I removed the Include, you shouldn't need it since you are projecting all the columns you are returning.
EDIT:
Initially I only had EF6 available and what I had posted was working. You're right, EF Core seems to be working differently. I added the ToList() to my code, so it's basically the same as your first post, but this works just fine for me, however, the execution basically makes n+1 DB calls. Maybe it will be addressed at some point and you can drop the inner ToList. Another thought extending what David Browne had to say would be to make two separate projection queries and just quickly join them, so basically only 2 DB reads, like this:
var foo_outer = await db.Foos
.Select(e => new FooModel
{
Id = e.Id,
Name = e.Name,
Childs = new List<ChildModel>()
}).ToListAsync();
var foo_inner = await db.Childs
.Where(x => foo_outer.Select(y => y.id).Contains(x.FoosForeignKey))
.Select(x => new
{
Id = x.Id,
Name = x.Name,
FooKey= x.FoosForeignKey
}).ToListAsync();
var foo_items= foo_outer.Select(x => new
{
Id = x.Id,
Name = x.Name,
Childs = foo_inner.Where(y => y.FooKey == x.Id).ToList()
});
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();
I have this query
[HttpGet]
public List<AttachedPhotosModel> GetReportAttachedPhotos(int reportId)
{
var photos = new ReportsRepository().GetInjuryPhotos(reportId);
return photos.Select(x => new AttachedPhotosModel()
{
Id = x.Id,
Type = x.InjuryType,
Photos = photos.Where(y => y.InjuryType == x.InjuryType).Select(z => z.ServicePhotoUrl).ToList()
}).ToList();
}
I need to GroupBy InjuryType, how to do this?
I added return photos.GroupBy(k => k.InjuryType).Select(x => new AttachedPhotosModel() but how to select model, x have new value key and I don't know how to select my data
This code should work. Assuming photos is collection of objects with InjuryType property and PhotoUrl property and AttachedPhotosModel has an InjuryType and Photos properties like this.
public class AttachedPhotosModel
{
public string InjuryType { set; get; }
public List<string> Photos { set; get; }
}
Code for grouping by InjurType.
var grouped = photos
.GroupBy(s => s.InjuryType,d => d.PhotoUrl, (k, g) => new
AttachedPhotosModel
{
InjuryType = k,
Photos = g.ToList()
}).ToList();
I'm new to LINQ. I'm trying to join two tables, but I have difficulties returning the results.
Here is my code:
using (DatabaseDataContext db = new DatabaseDataContext())
{
var list = db.Products.Join(db.ProductDetails,
p => p.ID,
d => d.ProductID,
(p, d) => new {
p.ID,p.Photo,d.Name,d.LanguageID
}).Where(d=>d.LanguageID== lang).ToList();
}
Well I can not use the variable list outside using and when I declare the variable outside 'using' (before it) like: var list;.
I get the error:
Implicitly-typed local variables must be initialized
Update:
I changed the code to:
DatabaseDataContext db = new DatabaseDataContext();
var products = db.Products.Join(db.ProductDetails,
p => p.ID,
d => d.ProductID,
(p, d) => new {
p.ID,p.Photo,d.Name,d.LanguageID
}).Where(d=>d.LanguageID== langs[language].ParseInt()).ToList();
and it worked. As I have omitted using, do I have to do anything like closing the connection?
Is there a problem not using the using?
If you don't use the results of the query in the same scope, you must make it typed so you can declare variables of appropriate type. First define a class for the result objects and use it. It would be cleaner to put this all as a method.
public class Result
{
public int ID { get; set; }
public string Photo { get; set; }
public string Name { get; set; }
public int LanguageID { get; set; }
}
public List<Result> GetJoinResult(int languageId)
{
using (DatabaseDataContext db = new DatabaseDataContext())
{
return db.Products.Join(db.ProductDetails, p => p.ID, d => d.ProductID,
(p, d) => new Result // not anonymous
{
ID = p.ID,
Photo = p.Photo,
Name = d.Name,
LanguageID = d.LanguageID,
})
.Where(x => x.LanguageID == languageId)
.ToList();
}
}
If defining the types is too much, then you must use it immediately in the same scope.
using (DatabaseDataContext db = new DatabaseDataContext())
{
var results = db.Products.Join(db.ProductDetails, p => p.ID, d => d.ProductID,
(p, d) => new
{
p.ID,
p.Photo,
d.Name,
d.LanguageID,
})
.Where(x => x.LanguageID == languageId)
.ToList();
// do stuff with results
}
I have a datatable as below,
I have a service which returns list ,
i want to group data with distinct rowid and field name using linq and put them in
a separate lists. how can i achieve it?
here is my code for grouping by id
var query = ae.Result.CommonDataValues.GroupBy(item => item.RowID)
.Select(g => g.Max(item => item.ID));
Assuming that you want/have a class which maps these properties like:
public class Data
{
public int ID { get; set; }
public int RecordID { get; set; }
public int RecordFieldData { get; set; }
public int RowID { get; set; }
public int FieldID { get; set; }
public string FieldName { get; set; }
}
and you want a List<List<Data>> which contains a List<Data> for every unique RowID+FieldName combination, you can GroupBy an anonymous type:
List<List<Data>> allData = data.AsEnumerable()
.GroupBy(r => new { RowID = r.Field<int>("RowID"), FieldName = r.Field<string>("FieldName") })
.Select(g => g
.Select(r => new Data
{
RowID = g.Key.RowID,
FieldName = g.Key.FieldName,
FieldID = r.Field<int>("RowID"),
ID = r.Field<int>("ID"),
RecordFieldData = r.Field<int>("RecordFieldData"),
RecordID = r.Field<int>("RecordID")
})
.ToList())
.ToList();
If you want to use a Dictionary instead with the RowId + FieldName as key:
Dictionary<Tuple<int, string>, List<Data>> allData = data.AsEnumerable()
.GroupBy(r => new { RowID = r.Field<int>("RowID"), FieldName = r.Field<string>("FieldName") })
.ToDictionary(
g => Tuple.Create(g.Key.RowID, g.Key.FieldName),
g => g.Select(r => new Data
{
RowID = g.Key.RowID,
FieldName = g.Key.FieldName,
FieldID = r.Field<int>("RowID"),
ID = r.Field<int>("ID"),
RecordFieldData = r.Field<int>("RecordFieldData"),
RecordID = r.Field<int>("RecordID")
})
.ToList());
// lookup with RowId + FieldName, f.e.:
List<Data> datas;
if(allData.TryGetValue(Tuple.Create(1, "name"), out datas))
{
// dictionary contains this data
}
Why don't you use the linq GroupBy, and then use the ForEach?
Something like the following;
// Group the result by rowId
finalData.GroupBy(x => x.rowid).ForEach(group =>
{
// Group would be the items which you are after...
// Do your logic here?
});
You can create the ForEach IEnumerable function, by adding this extension in your project;
public static void ForEach(this IEnumerable enumeration, Action action)
{
foreach(T item in enumeration)
{
action(item);
}
}
You would be able to get your MAX, etc, and all that you are after by accessing the group variable in the ForEach then... It should work for you.
Yet another variant
var query = from cdv in ae.Result.CommonDataValues
group cdv by new {cdv.RowID, cdv.FieldName} into g
select g.ToList()
if you want groupping only for one field yo can try this
var query = from cdv in ae.Result.CommonDataValues
group cdv by new cdv.RowID into g
select g.ToList()