Return NotMapped property in LINQ - c#

I have one entity which is used in Entity Framework Code First.
public class MyEntity
{
public int Id { get; set; }
[Required]
public string Name { get; set; }
public List<string> ListOfValues
{
get
{
return StringsAsStrings?.Split(';').ToList();
}
set
{
StringsAsStrings = value.Count > 0 ? String.Join(";", value) : null;
}
}
[Column("ListOfValues")]
private string StringsAsStrings { get; set; }
public int RelationId { get; set; }
}
Here I am giving values as List to public and that should be stored as semicolon(;) separated string as in the table.
As this is specific to this data model, I specified StringsAsStrings as private and created a Convention to include in the migraion. All these are fine.
Now my scenario is to select the collection using LINQ. Please check the following linq query i used;
var items = from A in MyContext.MyEntities.Where(x => x.RelationId == paramId)
select new
{
Id = A.Id,
Name = A.Name,
ListOfValues = A.ListOfValues,
};
var result = items.ToList();
Here I got following error
The specified type member 'ListOfValues' is not supported in LINQ to
Entities. Only initializers, entity members, and entity navigation
properties are supported.
Ok fine. EF didn't support NotMapped properties. So for the work around i just changed private StringsAsStrings to public StringsAsStrings. Then changed the linq like below;
var items = from A in MyContext.MyEntities.Where(x => x.RelationId== paramId)
select new
{
Id = A.Id,
Name = A.Name,
ListOfValues = A.StringsAsStrings.Split(';').ToList(),
};
var s = items.ToList();
Now i got the following error;
LINQ to Entities does not recognize the method 'System.String[]
Split(Char[])' method, and this method cannot be translated into a
store expression.
Please help to solve this case.
Also help me if any other work around for this to avoid changing private to public property.

Related

Argument type IOrderedQueryable does not match the corresponding member type IOrderedEnumerable (LINQ to SQL)

I'm trying to run this LINQ-to-SQL query
var sourceFilters = await _context.Sources.Where(s => s.CreatedBy == userId).Select(s => new {
SourceId = s.Id,
SourceName = s.Name,
Models = s.Vehicles.Select(v => new VehicleKeys()
{
Model = v.Model,
Code = v.Code,
RegistrationNumber = v.RegistrationNumber
}).Distinct().OrderBy(k => k.Model).ThenBy(k => k.Code).ThenBy(k => k.RegistrationNumber)
}).ToListAsync()
s.Vehicles is a navigation property
But I get the following Exception:
Argument type 'System.Linq.IOrderedQueryable`1[MyProject.Infrastructure.Services.SourceService+VehicleKeys]' does not match the corresponding member type 'System.Linq.IOrderedEnumerable`1[MyProject.Infrastructure.Services.SourceService+VehicleKeys]'
I tried adding .ToList() after all of the OrderBy calls, but then the resulting sourceFilters.Models collection only has a single element (even if I add Include(s=>s.Vehicles) to _context.Sources)
I also tried turning anonymous type into a normal type SourceKeys, but if I define Models property as a IOrderedQueryable or IQueryable - I get a compile time error. If I define it as a IEnumerable or IOrderedEnumerable - I get an Argument types do not match exception
Another thing I attempted was adding AsQueryable(), right after s.Vehicles, after Distinct() or after all of the OrderBy clauses. In the first and second cases I get the following exception:
Argument type 'System.Linq.IQueryable`1[MyProject.Infrastructure.Services.SourceService+VehicleKeys]' does not match the corresponding member type 'System.Linq.IOrderedQueryable`1[MyProject.Infrastructure.Services.SourceService+VehicleKeys]'
In the third case I get:
Argument type 'System.Collections.Generic.List`1[MyProject.Infrastructure.Services.SourceService+VehicleKeys]' does not match the corresponding member type 'System.Linq.IQueryable`1[MyProject.Infrastructure.Services.SourceService+VehicleKeys]'
I've also tried both having a named type for the elements of the sourceFilters collection, and adding AsQueryable()
If I have Models be IOrderedQueryable<VehicleKeys> and put AsQueryable() after s.Vehicles or after Distinct() I get the Argument types do not match exception. If I put AsQueryable() after all of the OrderBy() clauses - I get a compile error.
If I have Models be IQueryable<VehicleKeys> I get Argument types do not match exception, no matter where I put AsQueryable().
At this point I have absolutely no ideas on what else to try. I can work around the problem by doing the ordering on the client-side, but I would prefer to things like ordering on the SQL Server, to leverage the benefits of indexation, and I also really want to understand what's the cause of the problem, so if similar problems were to pop up in the future I would be able to deal with them.
Edit:
Models:
public class Source {
public int Id { get; set; }
public string Name { get; set; }
public string CreatedBy { get; set; }
public virtual IEnumerable<Vehicle> Vehicles { get; set; }
}
public class Vehicle {
public int Id { get; set; }
public string Model { get; set; }
public string Code { get; set; }
public string RegistrationNumber { get; set; }
public int? SourceId { get; set; }
public Source Source { get; set; }
//some other properties
}
{Model, Code, RegistrationNumber, SourceId, /* some other properties*/} have an index and an unique constraint
private class VehicleKeys {
public string Model { get; set; }
public string Code { get; set; }
public string RegistrationNumber { get; set; }
}
I would suggest to split this query to two parts. One should execute Eager Loading query, second post process records on the client side:
var rawData = await _context.Sources
.Where(s => s.CreatedBy == userId)
.Select(s => new
{
SourceId = s.Id,
SourceName = s.Name,
Models = s.Vehicles
.Select(v => new
{
Model = v.Model,
Code = v.Code,
RegistrationNumber = v.RegistrationNumber
}).ToList()
})
.ToListAsync();
var sourceFilters = rawData
.Select(s => new
{
s.SourceId,
s.SourceName,
Models = s.Models
.Distinct()
.OrderBy(k => k.Model)
.ThenBy(k => k.Code)
.ThenBy(k => k.RegistrationNumber)
.Select(v => new VehicleKeys()
{
Model = v.Model,
Code = v.Code,
RegistrationNumber = v.RegistrationNumber
}).ToList()
})
.ToList();

NotMapped property is not supported in Linq?

I have added RunCount property in class and called in function as below.
public class ItemsDataObject
{
[Key]
[Column("ID")]
public string Id{ get; set; }
.
.
.
[NotMapped]
public int RunCount { get; set; }
}
public static List<ItemsDataObject> GetAllItemsWithPaging(int startingPageIndex, int pageSize, string orderColumn, string orderDir)
{
using (var ctx = new OracleDbContext())
{
List<ItemsDataObject> list = new List<ItemsDataObject>();
var v = (from a in ctx.Items select a);
v = v.OrderBy(orderColumn + " " + orderDir);
list = v.Skip(startingPageIndex).Take(pageSize).ToList();
return list;
}
}
There are large data in list so i need to firstly sort items and get 10 rows(pagesize) and then .ToList().
But i am getting System.NotSupportedException error. How can i fix the issue?
The specified type member 'RunCount' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported.
you can use
var v = from a in ctx.Items
orderby a.orderColumn ,a.orderDir;
list = v.Skip(startingPageIndex).Take(pageSize).ToList();

Prevent SELECT N+1 issue in Entity Framework query using Joins

I'm trying to query something from an indirectly related entity into a single-purpose view model. Here's a repro of my entities:
public class Team {
[Key]
public int Id { get; set; }
public string Name { get; set; }
public List<Member> Members { get; set; }
}
public class Member {
[Key]
public int Id { get; set; }
public string Name { get; set; }
}
public class Pet {
[Key]
public int Id { get; set; }
public string Name { get; set; }
public Member Member { get; set; }
}
Each class is in a DbSet<T> in my database context.
This is the view model I want to construct from a query:
public class PetViewModel {
public string Name { get; set; }
public string TeamItIndirectlyBelongsTo { get; set; }
}
I do so with this query:
public PetViewModel[] QueryPetViewModel_1(string pattern) {
using (var context = new MyDbContext(connectionString)) {
return context.Pets
.Where(p => p.Name.Contains(pattern))
.ToArray()
.Select(p => new PetViewModel {
Name = p.Name,
TeamItIndirectlyBelongsTo = "TODO",
})
.ToArray();
}
}
But obviously there's still a "TODO" in there.
Gotcha: I can not change the entities at this moment, so I can't just include a List<Pet> property or a Team property on Member to help out. I want to fix things inside the query at the moment.
Here's my current solution:
public PetViewModel[] QueryPetViewModel_2(string pattern) {
using (var context = new MyDbContext(connectionString)) {
var petInfos = context.Pets
.Where(p => p.Name.Contains(pattern))
.Join(context.Members,
p => p.Member.Id,
m => m.Id,
(p, m) => new { Pet = p, Member = m }
)
.ToArray();
var result = new List<PetViewModel>();
foreach (var info in petInfos) {
var team = context.Teams
.SingleOrDefault(t => t.Members.Any(m => m.Id == info.Member.Id));
result.Add(new PetViewModel {
Name = info.Pet.Name,
TeamItIndirectlyBelongsTo = team?.Name,
});
}
return result.ToArray();
}
}
However, this has a "SELECT N+1" issue in there.
Is there a way to create just one EF query to get the desired result, without changing the entities?
PS. If you prefer a "plug and play" repro containing the above, see this gist.
You've made the things quite harder by not providing the necessary navigation properties, which as #Evk mentioned in the comments do not affect your database structure, but allow EF to supply the necessary joins when you write something like pet.Member.Team.Name (what you need here).
The additional problem with your model is that you don't have a navigation path neither from Team to Pet nor from Pet to Team since the "joining" entity Member has no navigation properties.
Still it's possible to get the information needed with a single query in some not so intuitive way by using the existing navigation properties and unusual join operator like this:
var result = (
from team in context.Teams
from member in team.Members
join pet in context.Pets on member.Id equals pet.Member.Id
where pet.Name.Contains(pattern)
select new PetViewModel
{
Name = pet.Name,
TeamItIndirectlyBelongsTo = team.Name
}).ToArray();

How to write LINQ query that selects a property which is a string concat of a collection of a nested entity properties?

Trying to be more efficient with my queries, but not sure how to write this all as one query. I've got a domain model:
public class UserReport : Entity
{
public string Name { get; set; }
public List<string> Statuses { get; set; }
public List<GroupModel> Groups { get; set; }
public string Email { get; set; }
public string OfficeStates {get; set;} //Comma delimited list
}
public class GroupModel : Entity
{
public string Name {get; set;}
public string Type {get; set;
}
Which is a "compound entity", if you will. The standard entities representing those collections are M2M relational entities with the User object:
public class User : Entity
{
public string FirstName { get; set; }
public string LastName { get; set; }
public ICollection<Status> Statuses { get; set; }
public ICollection<Group> Groups { get; set; }
public ICollection<Office> Offices { get; set; }
}
public class Office : Entity
{
//other properties
public State State { get; set; }
}
public class State : Entity
{
public string Name { get; set; }
public string Abbreviation { get; set; }
}
So far I've got:
Context.DbSet<User>.Select(user => new UserReport()
{
Name = user.FirstName + ", " + user.LastName,
Email = user.Email,
Statuses = user.Statuses.Select(status => status.Name).ToList(),
Groups = user.Groups.Select(group => new GroupModel(){ Name = group.Name, Type = group.Type.Name}).ToList(),
OfficeStates = string.Join(",", user.Offices.Select(office => office.State.Abbreviation).ToList())
}).ToList();
Which throws an error:
LINQ to Entities does not recognize the method 'System.String Join(System.String, System.Collections.Generic.IEnumerable`1[System.String])' method, and this method cannot be translated into a store expression.
I totally get what this is saying and I know if I called .ToList() before my select it would work, but I need to have this be one query, so I can still have an IQueryable to apply filtering later. Surely there must be some way in LINQ to do this. It can be done in SQL: Concatenate many rows into a single text string?, how can it be done via LINQ?
You can see that even in pure SQL that is not trivial (for xml, declaring variables etc). As far as I know there is no way to do exactly what you want with pure LINQ. However, at least in some cases you can do that using one query, though this query will not be the same as you would do this yourself in pure SQL. In your case that would be something like this:
Context.DbSet<User>.Select(user => new // note, anonymous type here
{
Name = user.FirstName + ", " + user.LastName,
Email = user.Email,
Statuses = user.Statuses.Select(status => status.Name), // no ToList - this won't work
Groups = user.Groups.Select(group => new GroupModel(){ Name = group.Name, Type = group.Type.Name}), // no ToList()
OfficeStates = user.Offices.Select(office => office.State.Abbreviation) // no ToList(), no String.Join()
}).ToList() // here we materizized, and now we can concatenate strings by hand
.Select(c => new UserReport {
Name = c.Name,
Email = c.Email,
Statuses = c.Statuses.ToList(),
Groups = c.Groups.ToList(),
OfficeStates = String.Join(",", c.Offices)
});
In simple cases I tested on, this generates one query to database, and this query receives only columns you need (though as I said - generated query is not the same you would use in SQL, conceptually). So I suggest to try this approach and see what query is generated (note also my comments in code above).

The specified type member is not supported in LINQ to Entities

I have a set of Stories and Comments and I'm trying to return a smaller DTO entity when being called by an API.
I'm trying to retrieve just the last comment but getting the error that "The specified type member 'LastComment' is not supported in LINQ to Entities. Only initializers, entity members, and entity navigation properties are supported."
My Story.cs:
public Story()
{
Comments = new List<Comment>();
}
public int StoryId { get; set; }
public List<Comment> Comments { get; set; }
public Comment LastComment
{
get
{
return Comments.LastOrDefault();
}
}
And my API GET method:
public IEnumerable<StoryDTO> Get()
{
return from p in db.Stories
.Include(x => x.Comments)
select new StoryDTO()
{
StoryId = p.StoryId,
LastComment = p.LastComment,
NumberOfComments = p.Comments.Count
};
}
I expect that Linq can't convert my query to SQL, but I'm unsure of the correct approach to resolve this.
You can try the following code:
return (db.Stories.Include(x => x.Comments)).AsEnumerable().Select(p =>
new StoryDTO()
{
StoryId = p.StoryId,
LastComment = p.LastComment,
NumberOfComments = p.Comments.Count
};
This way you will be dealing with LINQ to Objects and EF will not try to convert all the stuff into SQL

Categories

Resources