Receive Dictionary<int, string> from linq to entity? - entity-framework

continuation of the issue How get array in linq to entity?
but now is not array => Dictionary
City type is Dictionary
var sites = (from country in db.Countries
select new
{
Country = country.Title,
Cities = country.Cities.Select(m => m.Title)
})
.AsEnumerable()
.Select(country => new SitiesViewByUser()
{
Country = country.Country,
City = country.Cities.ToArray()
});
update:
public class SitiesViewByUser
{
public string Country { get; set; }
public Dictionary<int, string> City { get; set; }
}

You can use ToDictionary to create a dictionary from a LINQ sequence.
The first part is the key, and the second the value, E.g.
.Select(country => new SitiesViewByUser()
{
Country = country.Country,
City = country.Cities.ToDictionary(c => c, c => c);
});
This assumes that City on SitiesViewByUser is defined as Dictionary<string, string>
Your LINQ is quite confusing though. You are creating an anonymous type, asssumed to shape a Country, but which has a Country property on it, which is infact the Title of the country (is that the name of the country?).
You also have a collection of just the city Titles, so I'm not sure what value you are going to use in your City dictionary on your SitiesViewByUser type.
Also, what is a Sitie? :\
Update
You could do something like this:
var countries = (from country in db.Countries
select new
{
Title = country.Title,
CityIds = country.Cities.Select(c => c.Id),
CityTitles = country.Cities.Select(c => c.Title)
}).AsEnumerable();
// You have a collection of anonymous types at this point,
// each type representing a country
// You could use a foreach loop to generate a collection
// of SitiesViewByUser, or you could use LINQ:
var sitiesViewByUsers = countries.Select(c => new SitiesViewByUser
{
Country = c.Title,
City = c.CityIds.Zip(c.CityTitles, (k, v) => new { Key = k, Value = v })
.ToDictionary(x => x.Key, x => x.Value)
};
Alternatively, why don't you change the SitiesViewByUser type to be:
public class SitiesViewByUser
{
public string Country { get; set; }
public IEnumerable<City> Cities { get; set; }
}
Then you can do (using fluent syntax):
var sitiesViewByUsers = db.Countries.Select(c => new SitiesViewByUser
{
Country = c.Title,
Cities = c.Cities
});

Related

The right way to apply GroupBy extension method with aggregate function

I have this simple model.
public class Room
{
public Guid Id { get; set; }
public Guid? postSubjectId { get; set; }
[ForeignKey("postSubjectId")]
public PostSubject postSubject { get; set; }
public string MemberId { get; set; }
[ForeignKey("MemberId")]
public AppUser Member { get; set; }
}
Basically I need to get Grouped postSubjectId along with MemberId.Count() , I know it's easy .. but it never comes with the expected result.
I made this simple GroupBy query
var mmbrs = _context.Rooms
.Select(g => new { id = g.postSubjectId, mmbrscount = g.MemberId })
.AsEnumerable()
.GroupBy(g => new { id = g.id , mmbrscount = g.mmbrscount.Count() }).ToList();
but it gives me unexpected result
However I did the same using ordinary sql query
select [postSubjectId] as postId, count([MemberId]) as mmbrsCount from [dbo].[Rooms] group by [postSubjectId]
and It gives me result as expected
I need to apply that expected result using LINQ GruoupBy extention method
The grouping key new { id = g.postSubjectId, mmbrscount = g.MemberId }) is like typing group by [postSubjectId], count([MemberId]) in SQL.
The correct statement is:
_context.Rooms
.GroupBy(r => r.postSubjectId)
.Select(g => new
{
id = g.Key,
mmbrscount = g.Count()
})
So every Room has exactly one property PostSubjectId, and one string property MemberId.
I need to get Grouped postSubjectId along with MemberId.Count()
Apparently you want to make groups of Rooms that have the same value for property PostSubjectId AND have the same value for MemberId.Count().
var result = dbContext.Rooms.GroupBy(room => new
{
PostSubjectId = room.PostSucjectId,
MemberIdLength = room.MemberId.Count(),
});
The result is a sequence of groups of Rooms. Every group has a key, which is a combination of [PostSubjectId, MemberIdLength]. The group is a sequence of Rooms. All rooms in one group have the same combination of [PostSubjectId, MemberIdLength].
If you don't want a sequence of groups of Rooms, you can use the overload of GroupBy that has a parameter resultSelector
var result = dbContext.Rooms.GroupBy(
// parameter keySelector
room => new
{ PostSubjectId = room.PostSucjectId,
MemberIdLength = room.MemberId.Count(),
},
// parameter resultSelector:
// from every combination of [PostSubjectId, MemberIdLength] (= the key) and
// all rooms that have this combination, make one new object:
(key, roomsWithThisKey) => new
{
// select the properties that you actually plan to use, for example
PostSubjectId = key.PostSubjectId,
MemberIdLength = key.MemberIdLength,
RoomInformations = roomsWithThisKey.Select(roomWithThisKey => new
{
Id = roomWithThisKey.Id,
Member = roomWithThisKey.Member,
...
})
.ToList(),
});

What the best way of translating created user name with ef core 3.1

public class Entity
{
public string Id { get; set; }
public string CreatedBy { get; set; }
public string LastUpdatedBy { get; set; }
[NotMapped]
public string Creator { get; set; }
[NotMapped]
public string Updater { get; set; }
}
public class User
{
public string Name { get; set; }
public string Id { get; set; }
}
I am going to search Entities, sort on Creator/Updater properties(and return UserInfo.Name) with ef core query, any idea?
After hours of researches, refers
How do you perform a left outer join using linq extension methods
Entity Framework Join 3 Tables
There 3 ways in oder(presonal perfer the first than second):
class Program
{
static void Main()
{
using TenantDBContext dbContext = new TenantDBContext("PORT=5432;DATABASE=linqtosql;HOST=xxx.com;PASSWORD=xxx;USER ID=postgres;Pooling=true;Minimum Pool Size=10;Application Name=xxx");
var result = (
from entity in dbContext.Entities
join user in dbContext.Users on entity.CreatedBy equals user.Id into temp1
from ce in temp1.DefaultIfEmpty()
join user1 in dbContext.Users on entity.UpdatedBy equals user1.Id into temp2
from cu in temp2.DefaultIfEmpty()
select new Entity() { Id = entity.Id, CreatedBy = entity.CreatedBy, UpdatedBy = entity.UpdatedBy, Creator = ce.Name, Updater = ce.Name }
).ToList();
Console.WriteLine(JsonConvert.SerializeObject(result, Formatting.Indented));
var result2 = dbContext.Entities
.GroupJoin(dbContext.Users, e => e.CreatedBy, u => u.Id, (e, u) => new { Entity = e, User = u })
.SelectMany(eUser => eUser.User.DefaultIfEmpty(), (e, u) => new Entity() { Id = e.Entity.Id, CreatedBy = e.Entity.CreatedBy, UpdatedBy = e.Entity.UpdatedBy, Creator = u.Name })
.GroupJoin(dbContext.Users, e => e.UpdatedBy, u => u.Id, (e, u) => new { Entity = e, User = u })
.SelectMany(eUser => eUser.User.DefaultIfEmpty(), (e, u) => new Entity() { Id = e.Entity.Id, CreatedBy = e.Entity.CreatedBy, UpdatedBy = e.Entity.UpdatedBy, Creator = e.Entity.Creator, Updater = u.Name }
).ToList();
Console.WriteLine(JsonConvert.SerializeObject(result2, Formatting.Indented));
var result3 = dbContext.Entities
.SelectMany(entity => dbContext.Users.Where(user => entity.CreatedBy == user.Id).DefaultIfEmpty(), (entity, user) => new { Entity = entity, User = user })
.SelectMany(entity => dbContext.Users.Where(user => entity.Entity.UpdatedBy == user.Id).DefaultIfEmpty(), (entity, user) => new Entity { Id = entity.Entity.Id, CreatedBy = entity.Entity.CreatedBy, UpdatedBy = entity.Entity.UpdatedBy, Creator = entity.User.Name, Updater = user.Name })
.ToList();
Console.WriteLine(JsonConvert.SerializeObject(result2, Formatting.Indented));
}
}

EF core - parent.InverseParent returns null for some rows

I have a Category table and it has a Parent Category, I try to iterate over all the categories and get the parents categories with it's Inverse Parent but some of them returns without the inverse parents from unknown reason.
Categories.cs
public partial class Categories
{
public Categories()
{
InverseParent = new HashSet<Categories>();
}
public int Id { get; set; }
public int? ParentId { get; set; }
public DateTime CreateDate { get; set; }
public bool? Status { get; set; }
public virtual Categories Parent { get; set; }
public virtual ICollection<Categories> InverseParent { get; set; }
}
This is how I try to iterate them to create a select list items:
var parentCategories = await _context.Categories.
Include(x => x.Parent).
Where(x => x.Status == true).
Where(x => x.Parent != null).
Select(x => x.Parent).
Distinct().
ToListAsync();
foreach (var parent in parentCategories)
{
SelectListGroup group = new SelectListGroup() { Name = parent.Id.ToString() };
foreach (var category in parent.InverseParent)
{
categories.Add(new SelectListItem { Text = category.Id.ToString(), Value = category.Id.ToString(), Group = group });
}
}
So the problem is that some of my parent categories returns all their children categories and some don't and I don't why.
There are several issues with that code, all having some explaination in the Loading Related Data section of the documentation.
First, you didn't ask EF Core to include InverseParent, so it's more logically to expect it to be always null.
What you get is a result of the following Eager Loading behavior:
Tip
Entity Framework Core will automatically fix-up navigation properties to any other entities that were previously loaded into the context instance. So even if you don't explicitly include the data for a navigation property, the property may still be populated if some or all of the related entities were previously loaded.
Second, since the query is changing it's initial shape (Select, Disctinct), it's falling into Ignored Includes category.
With that being said, you should build the query other way around - starting directly with parent categories and including InverseParent:
var parentCategories = await _context.Categories
.Include(x => x.InverseParent)
.Where(x => x.InverseParent.Any(c => c.Status == true)) // to match your query filter
.ToListAsync();
While you are including Include(x => x.Parent), you don't seem to do the same for InverseParent. This might affect your results exactly the way you describe. Would including it fix it?
parentCategories = await _context.Categories.
Include(x => x.Parent).
Include(x => x.InverseParent).
Where(x => x.Status == true).
Where(x => x.Parent != null).
Select(x => x.Parent).
Distinct().
ToListAsync();
foreach (var parent in parentCategories)
{
SelectListGroup group = new SelectListGroup() { Name = parent.Id.ToString() };
foreach (var category in parent.InverseParent)
{
categories.Add(new SelectListItem { Text = category.Id.ToString(), Value = category.Id.ToString(), Group = group });
}
}
UPD: Since you are selecting x => x.Parent anyway it might be necessary to use ThenInclude() method instead.

Get nested data and sharp into DTO with nested DTO

I'm newbie to EF, Linq and C# in general, I'm stuck with developing following.
I cannot map data into structure like this:
Id,
Actions [
Action1,
Action2,
Action3
]
I have 2 DTO classes like this:
public class TestDTO
{
public int TestId { get; set; }
public TestDTO2[] Actions { get; set; }
}
and
public class TestDTO2
{
public int TestActionId { get; set; }
public DateTime? StartDate { get; set; }
...
}
I've separated calls to DB into file called BusinessLogic, I'm doing it like this:
public IQueryable<TestDTO> GetNested(Filter filter)
{
var query =
from a in db.Table1.AsQueryable()
select new TestDTO
{
TestId = a.Id,
Actions = (
from b in db.Table2.AsQueryable()
where a.Id == b.TestId
select new TestDTO2
{
TestActionId = b.TestActionId,
StartDate = b.StartDate
}
).ToArray()
};
return query;
}
I'm getting following error:
LINQ to Entities does not recognize the method 'Project.Core.Models.TestDTO2[] ToArrayTestDTO2' method, and this method cannot be translated into a store expression.
You can't perform exactly this query, it is better to make two simple queries and then process their results on client side:
var main = db.Table1.Select(x => new { x.Id, x.Title }).ToList();
var mainIds = main.Select(x => x.Id).ToList();
var actions = db.Table2.Where(x => mainIds.Contains(x.TestId)).Select(x => new
{
x.TestId,
x.TestActionId,
x.StartDate
}).ToList();
var result = main.Select(x => {
var actions = actions.Where(y => y.TestId == x.Id).Select(y => new TestDTO2
{
TestActionId = y.TestActionId,
StartDate = y.StartDate
}).ToArray();
return new TestDTO
{
TestId = x.Id,
Title = x.Title,
Actions = actions.Length == 0 ? null : actions
};
}).ToList();
yes, you can't use any c# method that can't translate a sql in EF.
actually, you need get a list,then covert it to your DTO
db.Table1
.Join(db.Table2,
a => a.Id,
b => b.TestId,
(a, b) => new
{
a.Id,
b
})
.GroupBy(k => k.Id, v => v).ToList()
.Select(a=>new TestDTO
{
TestId = a.Id,
Actions = a.Select(b=>
new TestDTO2
{
TestActionId = b.TestActionId,
StartDate = b.StartDate
}.ToArray()
}).ToList()

Converting ESQL to LINQ to Entities. Sort by related entities

I am using EF + RIA and unfortunately meet some problems with sorting by related entities.
For such purpose there is ESQL query that I implemented (found only this solution):
var queryESQL = string.Format(
#" select VALUE ent from SomeEntities as ent
join Attributes as ea ON ea.EntityId = ent.Id
where ea.AttributeTypeId = #typeId
order by ea.{0} {1}", columnName, descending ? "desc" : "asc");
var query = ObjectContext.CreateQuery<SomeEntity>(queryESQL, new ObjectParameter("typeId", attributeTypeId));
Tables have following structure:
<Attribute>:
int Id;
decimal DecimalColumn;
string StringColumn;
int EntityId;
int AttributeTypeId;
<SomeEntity>:
int Id;
string Name;
Is there any way to rewrite this stuff(sorting), using LINQ to Entities approach?
Here's my attempt, I can't guarantee it will work. I need to think more on how to get a dynamic column name, I'm not sure on that one. EDIT: you can use a string for the order column.
int typeId = 1115;
bool orderAscending = false;
string columnName = "StringColumn";
var query = from ent in SomeEntities
join ea in Attributes on ea.EntityId = ent.Id
where ea.AttributeTypeId = typeId;
if(orderAscending)
{
query = query.OrderBy(ea => columnName).Select(ea => ea.Value);
}
else
{
query = query.OrderByDescending(ea => columnName).Select(ea => ea.Value);
}
var results = query.ToList(); // call toList or enumerate to execute the query, since LINQ has deferred execution.
EDIT: I think that ordering after the select stops is from ordering by. I moved the select statement to after the order by. I also added the "query =", but I'm not sure if that is needed. I don't have a way to test this at the moment.
EDIT 3: I fired up LINQPad today and made a few tweaks to what I had before. I modeled your data in a Code-first approach to using EF and it should be close to what you have.
This approach works better if you're just trying to get a list of Attributes (which you aren't). To get around that I added an Entity property to the MyAttribute class.
This code works in LINQPAD.
void Main()
{
// add test entities as needed. I'm assuming you have an Attibutes collection on your Entity based on your tables.
List<MyEntity> SomeEntities = new List<MyEntity>();
MyEntity e1 = new MyEntity();
MyAttribute a1 = new MyAttribute(){ StringColumn="One", DecimalColumn=25.6M, Id=1, EntityId=1, AttributeTypeId = 1, Entity=e1 };
e1.Attributes.Add(a1);
e1.Id = 1;
e1.Name= "E1";
SomeEntities.Add(e1);
MyEntity e2 = new MyEntity();
MyAttribute a2 = new MyAttribute(){ StringColumn="Two", DecimalColumn=198.7M, Id=2, EntityId=2, AttributeTypeId = 1, Entity=e2 };
e2.Attributes.Add(a2);
e2.Id = 2;
e2.Name = "E2";
SomeEntities.Add(e2);
MyEntity e3 = new MyEntity();
MyAttribute a3 = new MyAttribute(){ StringColumn="Three", DecimalColumn=65.9M, Id=3, EntityId=3, AttributeTypeId = 1, Entity=e3 };
e3.Attributes.Add(a3);
e3.Id = 3;
e3.Name = "E3";
SomeEntities.Add(e3);
List<MyAttribute> attributes = new List<MyAttribute>();
attributes.Add(a1);
attributes.Add(a2);
attributes.Add(a3);
int typeId = 1;
bool orderAscending = true;
string columnName = "StringColumn";
var query = (from ent in SomeEntities
where ent.Attributes.Any(a => a.AttributeTypeId == typeId)
select ent.Attributes).SelectMany(a => a).AsQueryable();
query.Dump("Pre Ordering");
if(orderAscending)
{
// query = is needed
query = query.OrderBy(att => MyEntity.GetPropertyValue(att, columnName));
}
else
{
query = query.OrderByDescending(att => MyEntity.GetPropertyValue(att, columnName));
}
// returns a list of MyAttributes. If you need to get a list of attributes, add a MyEntity property to the MyAttribute class and populate it
var results = query.Select(att => att.Entity).ToList().Dump();
}
// Define other methods and classes here
}
class MyAttribute
{
public int Id { get; set; }
public decimal DecimalColumn { get; set; }
public string StringColumn { get; set; }
public int EntityId { get; set; }
public int AttributeTypeId { get; set; }
// having this property will require an Include in EF to return it then query, which is less effecient than the original ObjectQuery< for the question
public MyEntity Entity { get; set; }
}
class MyEntity
{
public int Id { get; set; }
public string Name { get; set; }
public ICollection<MyAttribute> Attributes { get; set; }
public MyEntity()
{
this.Attributes = new List<MyAttribute>();
}
// this could have been on any class, I stuck it here for ease of use in LINQPad
// caution reflection may be slow
public static object GetPropertyValue(object obj, string property)
{
// from Kjetil Watnedal on http://stackoverflow.com/questions/41244/dynamic-linq-orderby
System.Reflection.PropertyInfo propertyInfo=obj.GetType().GetProperty(property);
return propertyInfo.GetValue(obj, null);
}