Implicit ViewModel generation from LINQ? - c#

In my project I have a GenericImageViewModel which is used by many entities.
Example of getting the ASP User Entity:
var query = UserRepository.Get(Id).Select(a => new TRDIdenityViewModel
{
FirstName = a.UserProfile.FirstName,
LastName = a.UserProfile.LastName,
NickName = a.UserProfile.NickName,
ProfileImage = a.UserProfile.ProfileImage
});
The ProfileImage is the GenericImageViewModel and has an implicit operator as follows:
public static implicit operator TRDGenericImageViewModel(TRDImage image)
{
return new TRDGenericImageViewModel
{
Id = image.Id,
AspectRatio = image.Ratio,
Url = image.Url,
};
}
If I run the query Entity Framework throws an exception:
"Unable to cast the type 'TRDImage' to type 'TRDGenericImageViewModel'. LINQ to Entities only supports casting EDM primitive or enumeration types."
If I create the GenericImageViewModel manually for each ViewModel everything is running fine:
var query = UserRepository.Get(Id).Select(a => new TRDIdenityViewModel
{
FirstName = a.UserProfile.FirstName,
LastName = a.UserProfile.LastName,
NickName = a.UserProfile.NickName,
ProfileImage = new TRDGenericImageViewModel {
Id = a.UserProfile.ProfileImage.Id,
AspectRatio = a.UserProfile.ProfileImage.Ratio,
Url = a.UserProfile.ProfileImage.Url,
},
});
But in this case I have to copy and paste the TRDGenericImageViewModel generation in every ViewModel and thats not the way it should goes. If something changed I have to modify all related classes.
So is there a way to avoid this exception?

Using .AsEnumerable() prior lambda is not possible due to selects later in that query.
var query = UserRepository
.Get(Id)
.AsEnumerable()
.Select(a => new TRDIdenityViewModel
{
FirstName = a.UserProfile.FirstName,
LastName = a.UserProfile.LastName,
NickName = a.UserProfile.NickName,
ProfileImage = new TRDGenericImageViewModel
{
Id = a.UserProfile.ProfileImage.Id,
AspectRatio = a.UserProfile.ProfileImage.Ratio,
Url = a.UserProfile.ProfileImage.Url,
},
Statistics = new TRDUserStatisticsViewModel
{
PostCount = a.Posts.Count(),
CommentCount = a.Comments.Count(),
ImageCount = a.Images.Count(),
VideoCount = a.Videos.Count(),
VoteCount = a.PostVotes.Count(),
}
});
When calling .AsEnumerable() only the entities included by the Include statement are counted. But if the user has more than 1000 Posts and 10.000 Votes the query is a data nightmare.

Related

Linq query rewriting

This has really stumped me...
I have two queries..
This one does NOT work, with the error,
"LINQ to Entities does not recognize the method GetAllCustomers()' method, and this method cannot be translated into a store expression."
results = theDocuments.Select(x => new DocumentModel()
{
document_id = x.document_id,
document_type = x.document_type,
franchisee_id = x.franchisee_id,
customer_id = x.customer_id,
address = x.address,
area_id = x.area_id,
***HERE***customer_name = outletService.GetAllCustomers().Where(xx => xx.id == x.customer_id).First().name
}).OrderBy(x => x.document_id);
Now rewriting it like this works:
results = from p in theDocuments
join cust in outletService.GetAllCustomers() on p.customer_id equals cust.id
select new DocumentModel()
{
customer_name = cust.name,
document_id = p.document_id,
document_type = p.document_type,
franchisee_id = p.franchisee_id,
customer_id = p.customer_id,
address = p.address
};
I understand that this cannot be converted to it's relevant SQL, however, I am not sure what the second query is doing differently in it's execution in order for it to work, does it execute the GetAllCustomers() before doing anything else? How is it working differently internally?
How could I rewrite the first query so that it executes correctly?
Thanks,
James.
**This is what I ended up with **
results = theDocuments.Join(
outletService.GetAllCustomers(), docs => docs.customer_id, customers => customers.id, (doc, cust) => new DocumentModel()
{
document_id = doc.document_id,
document_type = doc.document_type,
franchisee_id = doc.franchisee_id,
customer_id = doc.customer_id,
address = doc.address,
area_id = doc.area_id,
customer_name = cust.name
});
The first one try to translate your method into a SQL method, the second create a join in the query. In my opinion the correct way is using join :)
I was a problem. I found a solution. That might be help you.
dynamic user = new
{
Name = "",
Surname = ""
};
dynamic userModel = new
{
Name = "",
Surname = "",
FullName = ""
};
var query = db.User.AsNoTracking();
query = query.Select(x => new userModel
{
Name = x.Name,
Surname = x.SurName,
FullName = $"{x.Name}{x.SurName}",
}).ToList();
If you try to execute this query. You will take a crash. Linq doesnt support
FullName = $"{x.Name}{x.SurName}"
You have to change this line to
FullName = x.Name + " " + x.SurName

How to get value from Dictionary to new Object in LINQ query

I have a Collection of Data and the Dictionary:
Collection handle Student Data
Dictionary Keeps Student Courses.
I want to get the Course Name from the Dictionary and put it into as CourseName.
private viod GenerateStudentDetails(Student studentData)
{
var courses = m_courses.GetCoursesDictionary(); // returns Dictionary<Guid,string>()
var studentDetails= from data in studentData
select new
{
FirstName = data.FirstName,
LastName = data.LastName,
Email = data.Email,
Mobile = data.Profile.Mobile,
City = data.Profile.City,
PostCode = data.Profile.PostCode,
CourseName = courses[data.Profile.CourseID ?? Guid.Empty]
};
}
"LINQ to Entities does not recognize the method 'System.String get_Item(System.Guid)' method, and this method cannot be translated into a store expression."
You could try something as the below:
private viod GenerateStudentDetails(Student studentData)
{
var courses = m_courses.GetCoursesDictionary(); // returns Dictionary<Guid,string>()
var studentDetails= (from data in studentData
select new
{
FirstName = data.FirstName,
LastName = data.LastName,
Email = data.Email,
Mobile = data.Profile.Mobile,
City = data.Profile.City,
PostCode = data.Profile.PostCode,
CourseID = data.Profile.CourseID
}).AsEnumerable()
.Select(item=>new
{
FirstName = item.FirstName,
LastName = item.LastName,
Email = item.Email,
Mobile = item.Profile.Mobile,
City = item.Profile.City,
PostCode = item.Profile.PostCode,
CourseName = courses[item.Profile.CourseID ?? Guid.Empty]
});
}
What's the problem?
The problem is that the last expression in the anonymous type you create,
CourseName = courses[data.Profile.CourseID ?? Guid.Empty]
cannot be in this place, because it can't be translated appropriately. So you have this option. You can declare a sequence of the data you want from the studentData and then make any conversion of call anything you want to the new anonymous type we create.
Just for thought
var eCourses = ((IEnumerable<KeyValuePair<Guid, string>>) courses);
var studentDetails = (from data in studentData
select new
{
data.FirstName,
data.LastName,
data.Email,
data.Profile.Mobile,
data.Profile.City,
data.Profile.PostCode,
CourseName = eCourses.FirstOrDefault(s => s.Key == data.Profile.CourseID).Value
});

C# how to modify this method to return only with matching criteria?

I have an existing method of generating a list of every operators. I would like to modify it, to only display operators who are not in a so called 'Inactive' role- this information comes from OperatorType table, column: Role
The existing code:
public static List<TPPROperatorDetails> GetOperators()
{
return DataHelper.DbTPPRTracer.TPPROperators.Select(
op => new TPPROperatorDetails{
Id = op.Id,
FullName = op.Name,
UserName = op.UserName,
Designation = op.Position,
OperatorTypes = ParseOperatorType(op.UserType),
SignatureImage = op.SignatureImage
}).ToList();
}
You can use the Where method. Something like this
public static List<TPPROperatorDetails> GetOperators()
{
return DataHelper.DbTPPRTracer.TPPROperators
.Where(op => ParseOperatorType(op.UserType) == "Inactive")
.Select(
op => new TPPROperatorDetails{
Id = op.Id,
FullName = op.Name,
UserName = op.UserName,
Designation = op.Position,
OperatorTypes = ParseOperatorType(op.UserType),
SignatureImage = op.SignatureImage
})
.ToList();
}

Data binding directly to a store query (DbSet, DbQuery, DbSqlQuery, DbRawSqlQuery) is not supported [duplicate]

This question already has answers here:
Data binding directly to a store query (DbSet, DbQuery, DbSqlQuery) is not supported
(3 answers)
Closed 7 years ago.
There error is as the title states: Data binding directly to a store query (DbSet, DbQuery, DbSqlQuery, DbRawSqlQuery) is not supported. I've tried searching for possible fixes, but couldn't find the one that was working for me.
Here is my DataLayer:
public virtual IQueryable<T> All()
{
return this.DbSet.AsQueryable();
}
My Controller:
public IQueryable<Movie> GetAllMovies()
{
var data = this.Data.Movies.All().Select(x => new Movie
{
Id = x.Id,
Name = x.Name,
ReleaseDate = x.ReleaseDate,
Rating = x.Rating,
Duration = x.Duration,
Director = x.Director,
Writer = x.Writer,
Cost = x.Cost,
Type = x.Type
}).OrderBy(x => x.Id);
return data;
}
And my GUI where I am calling it:
public MovieManagementGUI()
{
InitializeComponent();
***this.radListView1.DataSource = movieCtr.GetAllMovies();*** //<-- Here I am getting the error
this.radListView1.ViewType = ListViewType.IconsView;
this.radListView1.AllowEdit = false;
this.radListView1.AllowRemove = false;
ImagePrimitive searchIcon = new ImagePrimitive();
searchIcon.Alignment = ContentAlignment.MiddleRight;
SetupIconsView();
}
I am trying to fetch all the data from the the DB and post it in a ListView. Can somebody look at the code and try fixing me up and if you need additional code, let me know.
Thank you,
Marius J.
I managed to fix my problem, I created a new DTO class and edited my controller:
public IEnumerable<MovieDTO> GetAllMovies()
{
var data = this.Data.Movies.All().Select(x => new MovieDTO
{
Id = x.Id,
Name = x.Name,
ReleaseDate = x.ReleaseDate,
Rating = x.Rating,
Duration = x.Duration,
Director = x.Director,
Writer = x.Writer,
Cost = x.Cost,
Type = x.Type
}).OrderBy(x => x.Id).ToList();
return data;
}

Anonymous Type in LINQ

I'm trying to get anonymous object from query:
var myList = from td in MyObjectList
select new
{
a = td.a,
b = td.b,
c = td.c,
name = (from r in contex.NewList
where r.aa == td.a && r.bb == td.b
select r.Name).ToList()
};
I would like name to have r.Name value cause I expect that name list contains only one element. If it contains 0 elements I would like name to have value NONE if more then 1 element then exception should be thrown or something.
Is it even possible to achieve something like that? Thanks for help.
Instead of .ToList() use
.SingleOrDefault() ?? (td.a == 0 ? "XNone" : "None")
Edit: Changed anwer based on comment.
Also I would recomend not to put such logic into Linq-to-SQL. Sometimes this can result in big chunk of highly-unoptimized SQL code and, unless you dont mind some performance isues, can result in much slower SQL execution.
You can achieve that using SingleOrDefault and a temporary variable within the expression. Something like this:
var myList =
from td in MyObjectList
let nameValue = contex.NewList
.Where(r => r.aa== td.a && r.bb == td.b)
.Select(r => r.Name)
.SingleOrDefault()
select new
{
a = td.a,
b = td.b,
c = td.c,
name = nameValue ?? "NONE"
};
Update: instead of presenting almost the same solution as #Euphorics answer, I've restructured the code a bit. I often find nested LINQ expressions making things less readable. Converting comprehension syntax into call chains could improve that.
Update 2: with some added requirements, the following select should do the trick:
select new
{
a = td.a,
b = td.b,
c = td.c,
name = nameValue ?? (td.a == 0 ? "XNone" : "None")
};
Theoretically you CAN'T.
Unless you have a type from where you could inspect lambda properties.
The trick is to convert your anonymous object to json and deserialize it into a known type that you must have in advance.
Caution working with EF core because your linq query will be executed CLIENT side!!.
That means that all the records will be retrieved from your dbset and evaluate on client.
Do NOT use code like this on EF dbset iquerables.
Any away I will copy some extensions methods code to do it so.
Having a defined type like...
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
}
Extension convert any object to json string...
public static class ObjectExtensions
{
public static string ToJson(this object source)
{
return JsonConvert.SerializeObject(source, Formatting.None);
}
}
Extension convert any json string to typed object...
public static class StringExtensions
{
public static T FromJson<T>(this string source) where T : class
{
return JsonConvert.DeserializeObject<T>(source);
}
}
Some xUnit Test
[Fact]
public void AddSelectTest()
{
var data = new[]
{
new {Id = 01, Name = "Engineering", GroupName = "Engineering and Development"},
new {Id = 02, Name = "Tool Design", GroupName = "Tool Design and Research"},
new {Id = 03, Name = "Sales", GroupName = "Sales and Marketing"},
new {Id = 04, Name = "Marketing", GroupName = "Marketing and Sales"},
new {Id = 05, Name = "Purchasing", GroupName = "Inventory Management"},
new {Id = 06, Name = "Research and Development", GroupName = "Development and Research"},
new {Id = 07, Name = "Production", GroupName = "Manufacturing and Production"},
new {Id = 08, Name = "Production Control", GroupName = "Control and Production"},
new {Id = 09, Name = "Human Resources", GroupName = "Human Resources and Administration"},
new {Id = 10, Name = "Finance", GroupName = "Finance and Executive General"},
new {Id = 11, Name = "Information Services", GroupName = "Information Services and Administration"},
new {Id = 12, Name = "Document Control", GroupName = "Document Control and Quality Assurance"},
new {Id = 13, Name = "Quality Assurance", GroupName = "Administration and Quality Assurance"},
new {Id = 14, Name = "Facilities and Maintenance", GroupName = "Maintenance and Facilities"},
new {Id = 15, Name = "Shipping and Receiving", GroupName = "Receiving and Shipping"},
new {Id = 16, Name = "Executive", GroupName = "Executive General and Administration"}
};
var queryable = data.AsQueryable();
var first = queryable.Select(d => new { Id = d.Id, Name = d.Name }).FirstOrDefault(d => d.ToJson().FromJson<Department>().Id == 1);
Assert.True(first != null, "Expected a department value but 'null' was found.");
}
Once again... let me say that if you are querying anonymous in memory object it could be ok, but be very cautious if your iquerable cames from EF core, since client side evaluation will happen.
Please enable throw exception warning when client side evaluation happens on your EF core code thought your DbContextOptionsBuilder, to prevent EF Core of executing client side evaluation code. You could do it follows..
builder.UseSqlServer(connection, sql =>
{
sql.EnableRetryOnFailure();
sql.MigrationsAssembly(assembly);
})
.UseQueryTrackingBehavior(track ? QueryTrackingBehavior.TrackAll : QueryTrackingBehavior.NoTracking)
.ConfigureWarnings(w => w.Throw(RelationalEventId.QueryClientEvaluationWarning));

Categories