Is it possible to flatten a one-to-many relationship using dynamic LINQ?
For example, I might have a list of Users and the User class contains a list of many UserPreferences. The UserPreference class is essentially a name/value pair.
A user will define what types of user preferences are available for a group of users.
public class User
{
public string FirstName
{
get;
set;
}
public string LastName
{
get;
set;
}
public IList<UserPreference> UserPreferences
{
get;
set;
}
}
public class UserPreference
{
public UserPreference(string name, object userValue)
{
this.Name = name;
this.UserValue = userValue;
}
public string Name
{
get;
set;
}
public object UserValue
{
get;
set;
}
}
Therefore one user group might be defined in the following way:
List<User> users = new List<User>();
User user1 = new User();
user1.FirstName = "John";
user1.LastName = "Doe";
user1.UserPreferences.Add(new UserPreference("Favorite color", "Red"));
User user2 = new User();
user2.FirstName = "Jane";
user2.LastName = "Doe";
user2.UserPreferences.Add(new UserPreference("Favorite mammal", "Dolphin"));
user2.UserPreferences.Add(new UserPreference("Favorite color", "Blue"));
users.Add(user1);
users.Add(user2);
return users;
The desired output would be:
First Name Last Name Favorite Color Favorite Mammal
John Doe Red NULL
Jane Doe Blue Dolphin
Is there a way to create an anonymous type so that UserPreferences would get rolled up into the User?
For example,
var u = UserScopedSettingAttribute.Select("new (FirstName as FirstName, UserValue as FavoriteColor)", null);
string name = u.FirstName;
string color = u.FavoriteColor;
Ultimately this list of Users will get bound to an ASP.NET GridView web control. There will be a large volume of data involved in this operation and performance will be critical.
Any suggestions are appreciated!
I know it doesn't exactly answer your question, but compiling strings into new classes at runtime like dlinq does has always had kind of a bad smell to it. Consider just simply using a DataTable like this,
DataTable prefs = new DataTable();
IEnumerable<DataColumn> cols = (from u in users
from p in u.UserPreferences
select p.Name)
.Distinct()
.Select(n => new DataColumn(n));
prefs.Columns.Add("FirstName");
prefs.Columns.Add("LastName");
prefs.Columns.AddRange(cols.ToArray());
foreach (User user in users)
{
DataRow row = prefs.NewRow();
row["FirstName"] = user.FirstName;
row["LastName"] = user.LastName;
foreach (UserPreference pref in user.UserPreferences)
{
row[pref.Name] = pref.UserValue;
}
prefs.Rows.Add(row);
}
This should do it. Flattening is generally done with SelectMany extension method, but in this case I am using a let expression. The code to remove the null preferences is a bit ugly and could prob be improved but it works:
var flattenedUsers = from user in GetUsers()
let favColor = user.UserPreferences.FirstOrDefault(pref => pref.Name == "Favorite color")
let favMammal = user.UserPreferences.FirstOrDefault(pref => pref.Name == "Favorite mammal")
select new
{
user.FirstName,
user.LastName,
FavoriteColor = favColor == null ? "" : favColor.UserValue,
FavoriteMammal = favMammal == null ? "" : favMammal.UserValue,
};
My best suggestion would be to not use dynamic LINQ, but add a flatuser class and then loop through the users. The code for this is simple, and if you were able to get a linq query with similar results it would generate the same code, although you can't really tell how optimized it would be as it might involve some joins that would incur a performance penalty instead of just looping. If you were pulling this from a database using LINQ to SQL then you could use an entity relation to to get the data using linq instead of this loop.
Loop:
List<FlatUser> flatusers = new List<FlatUser>();
foreach (User u in users)
{
foreach (UserPreference up in u.UserPreferences)
{
flatusers.Add(new FlatUser
{
FirstName = u.FirstName,
LastName = u.LastName,
Name = up.Name,
UserValue = up.UserValue
});
}
}
Flat User Class:
public class FlatUser
{
public string FirstName
{
get;
set;
}
public string LastName
{
get;
set;
}
public string Name
{
get;
set;
}
public object UserValue
{
get;
set;
}
}
Unfortunately
var u = UserScopedSettingAttribute.Select("new {FirstName as FirstName, UserValue as FavoriteColor}", null);
string name = u.FirstName;
string color = u.FavoriteColor;
won't work. When you use DLINQ Select(string) the strongest compile time class information you have is Object, so u.FirstName will throw a compile error. The only way to pull the properties of the runtime generated anonymous class is to use reflection. Although, if you can wait, this will be possible with C# 4.0 like this,
dynamic u = UserScopedSettingAttribute.Select("new {FirstName as FirstName, UserValue as FavoriteColor}", null);
string name = u.FirstName;
string color = u.FavoriteColor;
I think the pragmatic answer here is to say your attempting to force C# to become a dynamic language and any solution is going to be really pushing C# to its limits. Sounds like your trying to transform a database query of columns that are only determined at query time into a collection that is based on those columns and determined at run time.
Linq and Gridview binding is really pretty and succinct and all but you have to start thinking about weighing the benefit of getting this compiler bending solution to work just so you don't have to dynamically generate gridview rows and columns by yourself.
Also if your concerned about performance I'd consider generating the raw HTML. Relying on the collection based WebForms controls to efficiently display large sets of data can get dicey.
You add in a couple of OnItemDataBound events and boxing and unboxing is going to really gum up the works. I'm assuming too your going to want to add interactive buttons and textboxes to the rows as well and doing 1000 FindControls has never been fast.
There are probably more efficient ways to do this, but to actually answer your question, I came up with the following code. (Note that I've never worked with DynamicLinq before, so there may be a better way to use it to accomplish your goal.)
I created a console application, pasted in the classes from your post, then used the following code.
static void Main(string[] args)
{
var users = GetUserGroup();
var rows = users.SelectMany(x => x.UserPreferences.Select(y => new { x.FirstName, x.LastName, y.Name, y.UserValue }));
var userProperties = rows.Select(x => x.Name).Distinct();
foreach (var property in userProperties)
{
Console.WriteLine(property);
}
Console.WriteLine();
// The hard-coded variety.
var results = users.Select(x => new
{
x.FirstName,
x.LastName,
FavoriteColor = x.UserPreferences.Where(y => y.Name == "Favorite color").Select(y => y.UserValue).FirstOrDefault(),
FavoriteAnimal = x.UserPreferences.Where(y => y.Name == "Favorite mammal").Select(y => y.UserValue).FirstOrDefault(),
});
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// The dynamic variety.
DynamicProperty[] dynamicProperties = new DynamicProperty[2 + userProperties.Count()];
dynamicProperties[0] = new DynamicProperty("FirstName", typeof(string));
dynamicProperties[1] = new DynamicProperty("LastName", typeof(string));
int propIndex = 2;
foreach (var property in userProperties)
{
dynamicProperties[propIndex++] = new DynamicProperty(property, typeof(string));
}
Type resultType = ClassFactory.Instance.GetDynamicClass(dynamicProperties);
ConstructorInfo constructor = resultType.GetConstructor(new Type[] {});
object[] constructorParams = new object[] { };
PropertyInfo[] propInfoList = resultType.GetProperties();
PropertyInfo[] constantProps = propInfoList.Where(x => x.Name == "FirstName" || x.Name == "LastName").OrderBy(x => x.Name).ToArray();
IEnumerable<PropertyInfo> dynamicProps = propInfoList.Where(x => !constantProps.Contains(x));
// The actual dynamic results creation.
var dynamicResults = users.Select(user =>
{
object resultObject = constructor.Invoke(constructorParams);
constantProps[0].SetValue(resultObject, user.FirstName, null);
constantProps[1].SetValue(resultObject, user.LastName, null);
foreach (PropertyInfo propInfo in dynamicProps)
{
var val = user.UserPreferences.FirstOrDefault(x => x.Name == propInfo.Name);
if (val != null)
{
propInfo.SetValue(resultObject, val.UserValue, null);
}
}
return resultObject;
});
//////////////////////////////////////////////////////////////////////////////////////////////////////////
// Display the results.
var displayResults = dynamicResults;
//var displayResults = results;
if (displayResults.FirstOrDefault() != null)
{
PropertyInfo[] properties = displayResults.First().GetType().GetProperties();
int columnWidth = Console.WindowWidth / properties.Length;
int index = 0;
foreach (PropertyInfo property in properties)
{
Console.SetCursorPosition(index++ * columnWidth, Console.CursorTop);
Console.Write(property.Name);
}
Console.WriteLine();
foreach (var result in displayResults)
{
index = 0;
foreach (PropertyInfo property in properties)
{
Console.SetCursorPosition(index++ * columnWidth, Console.CursorTop);
Console.Write(property.GetValue(result, null) ?? "(null)");
}
Console.WriteLine();
}
}
Console.WriteLine("\r\nPress any key to continue...");
Console.ReadKey();
}
static List<User> GetUserGroup()
{
List<User> users = new List<User>();
User user1 = new User();
user1.FirstName = "John";
user1.LastName = "Doe";
user1.UserPreferences = new List<UserPreference>();
user1.UserPreferences.Add(new UserPreference("Favorite color", "Red"));
user1.UserPreferences.Add(new UserPreference("Birthday", "Friday"));
User user2 = new User();
user2.FirstName = "Jane";
user2.LastName = "Doe";
user2.UserPreferences = new List<UserPreference>();
user2.UserPreferences.Add(new UserPreference("Favorite mammal", "Dolphin"));
user2.UserPreferences.Add(new UserPreference("Favorite color", "Blue"));
users.Add(user1);
users.Add(user2);
return users;
}
Related
I have following methods:
Controller:
...
var appmap = Services.GetReqAppMapList(value);
var applist = Services.GetApplicationList(docid, appid, reqid, appmap);
...
Model:
public static IEnumerable<AppMap> GetReqAppMapList(int aiRequestTypeId)
{
try
{
var appmap = new List<AppMap>();
using (var eties = new eRequestsEntities())
{
appmap = (from ram in eties.ReqAppMaps
where ram.IsActive == 1
select new AppMap
{
RequestTypeId = ram.RequestTypeId
}).ToList();
return appmap;
}
}
catch(Exception e)
{
throw e;
}
}
public static IEnumerable<TicketApplication> GetApplicationList(int aiDocumentTypeId, int aiApplicationTypeId, int aiRequestTypeId, IEnumerable<AppMap> appmap)
{
try
{
var applicationlist = new List<TicketApplication>();
using (var applicationentity = new eRequestsEntities())
{
applicationlist = (from app in applicationentity.Applications
where 1==1
<<<Some Conditions Here???>>>
== && appmap.Contains(app.ApplicationTypeId) ==
&& app.IsActive == 1
select new TicketApplication
{
ApplicationId = app.ApplicationId,
Description = app.Description,
DeliveryGroupId = app.DeliveryGroupId,
ApplicationTypeId = app.ApplicationTypeId,
DeliveryTypeId = app.DeliveryTypeId,
DocumentTypeId = app.DocumentTypeId,
SupportGroupId = app.SupportGroupId
}).OrderBy(a => a.Description).ToList();
return applicationlist;
}
And I was thinking how can filter query result of GetApplicationList using the result from GetReqAppMapList
I'm kinda stuck with the fact that I must convert/cast something to the correct type because every time I do a result.Contains (appmap.Contains to be exact), I always get the following error
Error 4 Instance argument: cannot convert from
'System.Collections.Generic.IEnumerable<Test.Models.AppMap>' to
'System.Linq.ParallelQuery<int?>'
You should directly join the two tables in one query.
using (var applicationentity = new eRequestsEntities())
{
applicationlist = (from app in applicationentity.Applications
join ram in applicationentity.ReqAppMaps on app.ApplicationTypeId equals ram.RequestTypeId
where ram.IsActive == 1 && app.IsActive == 1
select new TicketApplication
{
ApplicationId = app.ApplicationId,
Description = app.Description,
DeliveryGroupId = app.DeliveryGroupId,
ApplicationTypeId = app.ApplicationTypeId,
DeliveryTypeId = app.DeliveryTypeId,
DocumentTypeId = app.DocumentTypeId,
SupportGroupId = app.SupportGroupId
}).OrderBy(a => a.Description).ToList();
You can delete the other method if it is not needed anymore. No point hanging onto code which is dead.
Looks like there is no other way to do this (as far as I know), so I have to refactor the code, I hope still that there would be a straight forward conversion and matching method in the future (too lazy). Anyway, please see below for my solution. Hope this helps someone with the same problem in the future. I'm not sure about the performance, but this should work for now.
Controller:
...
var appmap = Services.GetReqAppMapList(value);
var applist = Services.GetApplicationList(docid, appid, reqid, appmap);
...
Model:
<Removed GetReqAppMapList>--bad idea
public static IEnumerable<TicketApplication> GetApplicationList(int aiDocumentTypeId, int aiApplicationTypeId, int aiRequestTypeId)
{
try
{
//This is the magic potion...
List<int?> appmap = new List<int?>();
var applist = (from ram in applicationentity.ReqAppMaps
where ram.RequestTypeId == aiRequestTypeId
&& ram.IsActive == 1
select new AppMap
{
ApplicationTypeId = ram.ApplicationTypeId
}).ToList();
foreach (var item in applist)
{
appmap.Add(item.ApplicationTypeId);
}
//magic potion end
var applicationlist = new List<TicketApplication>();
using (var applicationentity = new eRequestsEntities())
{
applicationlist = (from app in applicationentity.Applications
where 1==1
===>>>&& appmap.Contains(app.ApplicationTypeId)<<<===
&& app.IsActive == 1
select new TicketApplication
{
ApplicationId = app.ApplicationId,
Description = app.Description,
DeliveryGroupId = app.DeliveryGroupId,
ApplicationTypeId =app.ApplicationTypeId,
DeliveryTypeId = app.DeliveryTypeId,
DocumentTypeId = app.DocumentTypeId,
SupportGroupId = app.SupportGroupId
}).OrderBy(a => a.Description).ToList();
return applicationlist;
}
A side-note, C# is a strongly-typed language, just make sure your data types matches during evaluation, as int? vs int etc.., will never compile. A small dose of LINQ is enough to send some newbies circling around for hours. One of my ID-10T programming experience but just enough to remind me that my feet's still flat on the ground.
I created this form to generate a list of students with the ability to filter by some criteria (on the left) and to display any information needed (from the right)
When the form is initializing at the start I am grabbing the whole student list with Entity Framework
List<Student> students = await context.Students.ToListAsync().ConfigureAwait(false);
And I am saving it into two lists:
private List<Student> _listOfAllStudents = new List<Student>();
private List<Student> _filteredStudents = new List<Student>();
Then I am performing my logic against the lists like this:
private void PrepareFilteredStudentListAccordingToFilterCheckedBoxes()
{
_filteredStudents = _listOfAllStudents;
if (ColonieFilterCheckBox.Checked)
{
_filteredStudents = _filteredStudents.Intersect(_listOfAllStudents.Where(x => x.Colonie).Select(x => x).ToList()).ToList();
}
if (NatationFilterCheckBox.Checked)
{
_filteredStudents = _filteredStudents.Intersect(_listOfAllStudents.Where(x => x.Nataion).Select(x => x).ToList()).ToList();
}
if (ExcursionFilterCheckBox.Checked)
{
_filteredStudents = _filteredStudents.Intersect(_listOfAllStudents.Where(x => x.Excursion).Select(x => x).ToList()).ToList();
}
//Rest of the code is omitted but you get the idea...
}
Same logic is being done according to display checkboxes:
private void FillDataGridViewWithFilteredStudentAccordingToDisplayCheckBoxes()
{
FilteredStudentDataGridView.Columns.Add("Id", "Numero");
FilteredStudentDataGridView.Columns.Add("LastName", "Nom");
FilteredStudentDataGridView.Columns.Add("FirstName", "Prenom");
if (MiddleNameDisplayCheckBox.Checked)
{
FilteredStudentDataGridView.Columns.Add("MiddleName", "Nom Du Pere");
}
if (BirthdayDateDisplayCheckBox.Checked)
{
FilteredStudentDataGridView.Columns.Add("DateOfBirth", "Date De Naissance");
}
//Rest of the code omitted, but same concept.
foreach (Student student in _filteredStudents)
{
List<object> rowsValues = new List<object>();
foreach (object column in FilteredStudentDataGridView.Columns)
{
string columnName = ((DataGridViewTextBoxColumn)column).Name;
if (columnName == "Id")
{
rowsValues.Add(student.StudentId);
}
if (columnName == "FirstName")
{
rowsValues.Add(student.FirstName);
}
//Code omitted.
}
object[] arrayRowsValues = rowsValues.ToArray();
FilteredStudentDataGridView.Rows.Add(arrayRowsValues);
}
}
I was wondering if there is a way to use LINQ instead of all those if blocks to filter the data according to my conditions?
I would definitely move the filtering to the database:
IQueryable<Student> studentQuery = context.Students;
if (ColonieFilterCheckBox.Checked) {
studentQuery = studentQuery.Where(x => x.Colonie);
}
if (NatationFilterCheckBox.Checked) {
studentQuery = studentQuery.Where(x => x.Nataion);
}
if (ExcursionFilterCheckBox.Checked) {
studentQuery = studentQuery.Where(x => x.Excursion);
}
var _filteredStudents = await studentQuery.ToListAsync().ConfigureAwait(false);
If you prefer local filtering logic, you can combine the conditions a little or use Reflection to simplify the code but slow it down some.
For combined conditions, you can do
_filteredStudents = _listOfAllStudents
.Where(x => (!ColonieFilterCheckBox.Checked || x.Colonie) &&
(!NatationFilterCheckBox.Checked || x.Natation) &&
(!ExcursionFilterCheckBox.Checked || x.Excursion)).ToList();
but that checks the CheckBoxs per Student.
Instead, using Reflection, you can build the code dynamically for the filter (again, assuming you name the CheckBox controls after the fields):
IEnumerable<Student> queryStudents = _listOfAllStudents;
var xParm = Expression.Parameter(typeof(Student));
foreach (var filterField in new[] { "Colonie", "Natation", "Excursion" }) {
if (((CheckBox)Controls.Find($"{filterField}CheckBox")).Checked) {
var whereLambda = (Expression<Func<Student, bool>>)Expression.Lambda(Expression.PropertyOrField(xParm, filterField), xParm);
queryStudents = queryStudents.Where(whereLambda.Compile());
}
}
_filteredStudents = queryStudents.ToList();
For your display logic, I would rename all the check boxes to match the data field names, and then use a lot of Reflection:
private void FillDataGridViewWithFilteredStudentAccordingToDisplayCheckBoxes() {
var headerText = new Dictionary<string, string> { { "Id", "Numero" }, { "LastName", "Nom" }, { "FirstName", "Prenom" }, { "MiddleName", "Nom Du Pere" },
{ "DateOfBirth", "Date De Naissance" } };
var viewColumns = new List<string> { "Id", "LastName", "FirstName" };
foreach (var possibleColumn in headerText.Keys) {
var displayColumns = Controls.Find(possibleColumn + "DisplayCheckBox", true);
if (displayColumns.Length == 1) {
if (((CheckBox)displayColumns[0]).Checked)
viewColumns.Add(possibleColumn);
}
}
//Rest of the code omitted, but same concept.
foreach (var dataFieldName in viewColumns)
FilteredStudentDataGridView.Columns.Add(dataFieldName, headerText[dataFieldName]);
foreach (var student in _filteredStudents) {
var studentType = student.GetType();
var rowValues = new List<object>();
foreach (var dataFieldName in viewColumns)
rowValues.Add(studentType.GetProperty(dataFieldName).GetValue(student, null));
FilteredStudentDataGridView.Rows.Add(rowValues.ToArray());
}
}
Note that if you care about the order of the columns displayed, you will need some logic to order or sort the headerText.Keys. In my asp implementation of something similar, I just have a manual list of procedure calls with data names in the order that I want, and the procedure checks if the data item is to be displayed (in viewColumns), then adds the data and column header.
You could reuse your linq expressions in a single method. Try to use AsQueryable extension method from System.Linq namespace instead:
private ICollection<Student> FilterStudents(ICollection<Student> students)
{
var query = students.AsQueryable();
if (ColonieFilterCheckBox.Checked)
{
query = query.Where(x=>x.Colonie);
}
if (NatationFilterCheckBox.Checked)
{
query = query.Where(x=>x.Nation);
}
if (ExcursionFilterCheckBox.Checked)
{
query = query.Where(x=>x.Excursion);
}
return query.ToList();
}
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();
}
I have an object in my database, i.e. with 10 attributes.
Now I want to let the user select some of them (1 or 2 up to 10 of them) and then according by user's selection I make a list of object with the attributes selected by user
the scenario that I think about is this:
A page with check boxes that shows the attributes(columns) of that abject then user selects each of them he needs.
But here is my problem, how to make the selected check boxes run as query?
For example user selected col 1 , col 2, col 6 , col 10, how can I write a query responsible for user selection?
Example I wanna the meaningful phrase of this:
var file2 = file.Select(f => new { "attributes selected by user" }).OrderBy(what user wants)
they System.Linq.Dynamic library on Nuget is a way to go
[TestMethod]
public void StringyAndDangerous()
{
var fakePersonDbSet = new List<Person> { new Person() { FirstName = "Some", LastName = "Guy" } }.AsQueryable();
var attributes = new string[] { "FirstName", "LastName" };
var selectedFields = String.Join(",", attributes);
var exprssion = string.Format("new ({0})", selectedFields);
var result = fakePersonDbSet.Select(exprssion, attributes).Cast<dynamic>().First();
}
but you loose type safety and compile time checking. You might be better taking another approach
[TestMethod]
public void SlowerButSafer()
{
var fakePersonDbSet = new List<Person> { new Person() { FirstName = "Some", LastName = "Guy" } }.AsQueryable();
var attributes = new string[] { "FirstName", "LastName" };
var personPropertylist = CovertToKeyValuePair(fakePersonDbSet.First())
.Where(c=> attributes.Contains(c.Key))
.ToArray();
}
private IEnumerable<KeyValuePair<string, object>> CovertToKeyValuePair<T>(T #object)
{
var result = new List<KeyValuePair<string, object>>();
var properties = typeof (T).GetProperties();
foreach (var property in properties)
{
result.Add(new KeyValuePair<string, object>(property.Name, property.GetValue(#object, null)));
}
return result;
}
you'll take a performance hit both for pulling fields from the database that you don't need and for using reflection but the code will be less error prone and you won't end up with errors for trying to select columns that don't exist.
Use DynamicLinq. (link)
Extension methods:
public static T GetValue<T>(this DynamicClass dynamicObject, string propName)
{
if (dynamicObject == null)
{
throw new ArgumentNullException("dynamicObject");
}
var type = dynamicObject.GetType();
var props = type.GetProperties(BindingFlags.Public
| BindingFlags.Instance
| BindingFlags.FlattenHierarchy);
var prop = props.FirstOrDefault(property => property.Name == propName);
if (prop == null)
{
throw new InvalidOperationException("Specified property doesn't exist.");
}
return (T)prop.GetValue(dynamicObject, null);
}
public static string ToDynamicSelector(this IList<string> propNames)
{
if (!propNames.Any())
throw new ArgumentException("You need supply at least one property");
return string.Format("new({0})", string.Join(",", propNames));
}
Usage:
using System.Linq.Dynamic;
// ..
var columns = new[] { "col1", "col2", etc };
var result = context.Files.OrderBy(file => file.Id)
.Select(columns.ToDynamicSelector())
.Cast<DynamicClass>.ToList();
Result will be the collecion of DynamiClass instances wchich columns will contain selected properties.
To get single property from DynamicClass:
var columnValue = result.First().GetValue<string>("col1");
If you want to get values from IEnumerable:
var list = new List<File> { File1, File2, etc.. };
var result = list.AsQueryable().Select( /* the same as above */);
I have a bunch of Linq to Entity methods that had the same select statement, so I thought I would be clever and separate that out into it's own method to reduce redundancy... but when i attempted to run the code, i got the following error...
this method cannot be translated into
a store expression
Here is the method i created...
public User GetUser(DbUser user, long uid)
{
return new User
{
Uid = user.uid,
FirstName = user.first_name,
LastName = user.last_name
};
}
And am calling in a method like this...
public User GetUser(long uid)
{
using (var entities = new myEntities()) {
return
entities.DbUsers.Where( x => x.uid == uid && x.account_status == ( short )AccountStatus.Active ).
Select( x => GetUser( x, uid ) ).FirstOrDefault( );
}
}
UPDATE: here is the code that works inline
public User GetUser(long uid, long uid_user)
{
using (var entities = new myEntities())
{
var q = from u in entities.DbUsers
where u.uid == uid_user
select new User
{
Uid = u.uid,
FirstName = u.first_name,
LastName = u.last_name,
BigPicUrl = u.pic_big,
Birthday = u.birthday,
SmallPicUrl = u.pic_small,
SquarePicUrl = u.pic_square,
Locale = u.locale.Trim(),
IsFavorite = u.FavoriteFriends1.Any(x => x.uid == uid),
FavoriteFriendCount = u.FavoriteFriends.Count,
LastWishlistUpdate = u.WishListItems.OrderByDescending(x => x.added).FirstOrDefault().added,
Sex = (UserSex)u.sex
};
var user = q.FirstOrDefault();
user.DaysUntilBirthday = user.Birthday.DaysUntilBirthday();
return user;
}
}
The error is spot on, you can't translate that into a T-SQL (or P-SQL) query.
You need to make sure you've executed the query before you attempt to hydrate it into some other type.
Keep it simple, use an extension method. That's what they are there for.
public static User ToUserEntity(this DbUser user)
{
return new User
{
Uid = user.uid,
FirstName = user.first_name,
LastName = user.last_name
};
}
Then in your DAL:
public User GetUser(long uid)
{
User dbUser;
using (var entities = new myEntities())
{
dbUser = entities.DbUsers
.Where( x => x.uid == uid && x.account_status == (short)AccountStatus.Active )
.FirstOrDefault(); // query executed against DB
}
return dbUser.ToUserEntity();
}
See how i hydrate the POCO into an object after the context has been disposed? This way, you ensure EF has finished it's expression work before you attempt to hydrate into a custom object.
Also i dont know why you're passing uid to that method, it's not even being used.
On a further note, you shouldn't need to do this kind of thing (project EF POCO's into your own objects).
If you do, it's a good case for custom POCO's (map the tables straight into your custom POCO's, don't use the Code Generation).
This expression will work to give the desired result (somewhat) I still havent figured out how to pass in additional variables in teh select statements...
..... .Select(GetUser).FirstOrDefault()
static readonly Expression<Func<DbUser, User>> GetUser = (g) => new User {
Uid = g.uid,
FirstName = g.first_name,
LastName = g.last_name,
BigPicUrl = g.pic_big,
Birthday = g.birthday,
SmallPicUrl = g.pic_small,
SquarePicUrl = g.pic_square,
Locale = g.locale.Trim(),
//IsFavorite = g.FavoriteFriends1.Any(x=>x.uid==uid),
FavoriteFriendCount = g.FavoriteFriends.Count,
LastWishlistUpdate = g.WishListItems.OrderByDescending( x=>x.added ).FirstOrDefault().added
};
You can't do this because the getUser method cannot be converted to any TSQL statement.
if you return your DBUser first and then use it as the first parameter of the GetUser method then you are forcing it to execute and once you have you DBUser you can pass it to GetUser
Maybe you can try this:
public User GetUser(long uid)
{
using (var entities = new myEntities())
{
return GetUser(
entities.DbUsers
.Where( x => x.uid == uid && x.account_status == (short)AccountStatus.Active )
.FirstOrDefault(),
uid);
}
}
EDIT
Since you are saying it still fails could it be beacuse of the enum??
public User GetUser(long uid)
{
using (var entities = new myEntities())
{
short status = (short)AccountStatus.Active;
return GetUser(
entities.DbUsers
.Where( x => x.uid == uid && x.account_status == status )
.FirstOrDefault(),
uid);
}
}