You may think this is same question as other around but I've checked and can't find an answer, so please if anyone could help...
I have this class which has many other properties, but let's focus in this 2...
public class MyClass
{
public DateTime? EndDate { get; set; }
public string State { get; set; }
}
Both EndDate and State has some values in Database.
Ex. EndDate = 2016-11-20 00:00:00 and State = "Closed".
What I need is to Change those values, so I have an Action button which does this...
using (MyContext ctx = new MyContext())
{
MyClass rec = new MyClass { Id = Id };
db.MyClasses.Attach(rec);
rec.State = "Opened";
rec.EndDate = null;
db.Configuration.ValidateOnSaveEnabled = false;
ctx.SaveChanges();
db.Configuration.ValidateOnSaveEnabled = true;
}
But, when I run this command, the State is changed in Database to "Opened" but the EndDate stills same value as before, I mean it does not turn NULL.
What am I doing wrong here?
Thanks
This is because you don't actually change the end date:
MyClass rec = new MyClass { Id = Id };
db.MyClasses.Attach(rec);
...
rec.EndDate = null;
But it was null when you attached the entity.
So you have to tell the change tracker that the property is modified:
db.Entry(rec).Property(r => r.EndDate).IsModified = true;
Related
I want to build an expression for IQueryable GroupBy. While at the moment I'm just simplifying the problem to try and get it working, the eventual final implementation will involve the creation of quite complex expression trees so I want to build a complete expression that can then be integrated into other expressions.
I specifically want to build an expression of this overload:
public static System.Linq.IQueryable<TResult> GroupBy<TSource,TKey,TResult> (
this System.Linq.IQueryable<TSource> source,
System.Linq.Expressions.Expression<Func<TSource,TKey>> keySelector,
System.Linq.Expressions.Expression<Func<TKey,System.Collections.Generic.IEnumerable<TSource>,TResult>> resultSelector);
... my problem is in the implementation of the resultSelector and and the IEnumerable<TSource>.
I have a table of Customers (just dummy data for the purposes of working out this problem). This is stored in an SQL DB and I specifically want to use IQueryable to access the data.
public class Customer
{
public int Id { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public int Age { get; set; }
}
I also have a GroupResult class used to hold the results of the GroupBy (I have different constructors which I've been using in my testing to work out where my problem is occurring)
internal class GroupResult
{
public string? Name { get; set; }
public int NumRecords { get; set; }
public decimal AverageAge { get; set; }
public int TotalAge { get; set; }
public GroupResult() { }
public GroupResult(string name)
{
Name = name;
}
public GroupResult(IEnumerable<Customer> customers)
{
Name = Guid.NewGuid().ToString();
NumRecords = customers.Count();
}
public GroupResult(string name, IEnumerable<Customer> customers)
{
Name = name;
NumRecords = customers.Count();
}
}
The main static class that displays prompts to select column to group on, creates the relevant expression tree and executes it
internal static class SimpleGroupByCustomer
{
internal static DataContext db;
internal static void Execute()
{
using (db = new DataContext())
{
//get input
Console.WriteLine();
Console.WriteLine("Simple Customer GroupBy");
Console.WriteLine("=======================");
Console.WriteLine("Simple GroupBy on the Customer Table");
Console.WriteLine();
Console.WriteLine("Select the property that you want to group by.");
Console.WriteLine();
var dbSet = db.Set<Customer>();
var query = dbSet.AsQueryable();
//for this example we're just prompting for a column in the customer table
//GetColumnName is a helper function that lists the available columns and allows
//one to be selected
string colName = Wrapper.GetColumnName("Customer");
MethodInfo? method = typeof(SimpleGroupByCustomer).GetMethod("GetGroupBy",
BindingFlags.Static | BindingFlags.NonPublic);
if (method != null)
{
method = method.MakeGenericMethod(new Type[] { typeof(String), query.ElementType });
method.Invoke(null, new object[] { query, colName });
}
}
}
internal static void GetGroupBy<T, TTable>(IQueryable query, string colName)
{
Type TTmp = typeof(TTable);
var param = Expression.Parameter(TTmp, "c");
var prop = Expression.PropertyOrField(param, colName);
LambdaExpression keySelector = Expression.Lambda<Func<TTable, T>>(prop, param);
var param1 = Expression.Parameter(typeof(T), "Key");
var param2 = Expression.Parameter(typeof(IEnumerable<TTable>), "Customers");
var ci = typeof(GroupResult).GetConstructor(new[] { typeof(T), typeof(IEnumerable<TTable>) });
//var ci = typeof(GroupResult).GetConstructor(new[] { typeof(T) });
//var ci = typeof(GroupResult).GetConstructor(new[] { typeof(IEnumerable<TTable>) });
if (ci == null)
return;
var pExp = new ParameterExpression[] { param1, param2 };
var methodExpression = Expression.Lambda<Func<T, IEnumerable<TTable>, GroupResult>>(
Expression.New(ci, new Expression[] { param1, param2 }), //<--- ERROR HERE
pExp
);
Type[] typeArgs = new Type[] { typeof(TTable), typeof(T), typeof(GroupResult) };
Expression[] methodParams = new Expression[] { query.Expression, keySelector, methodExpression };
var resultExpression = Expression.Call(typeof(Queryable), "GroupBy", typeArgs, methodParams);
IQueryable dbQuery = query.Provider.CreateQuery(resultExpression);
if (dbQuery is IQueryable<GroupResult> results)
{
foreach (var result in results)
{
Console.WriteLine("{0,-15}\t{1}", result.Name, result.NumRecords.ToString());
}
}
}
}
When I run this and try and iterate through the results I get the following exception:
System.InvalidOperationException: 'variable 'Customers' of type 'System.Collections.Generic.IEnumerable`1[ExpressionTrees3.Data.Customer]' referenced from scope '', but it is not defined'
which is being caused by the param2 ParameterExpression marked above.
If I use the GroupResult constructor that just takes the key value
var ci = typeof(GroupResult).GetConstructor(new[] { typeof(T) });
and omit the param2 from the Lambda body definition the code works as expected and I get a collection of GroupResult records containing the distinct key values in the Name field (but obviously no summary value).
I've tried everything I can think of and just can't get past this error - it's as though the GroupBy is not actually producing the IEnumerable grouping of Customers for each key.
I suspect I'm missing something really obvious here, but just can't see it. Any help would really very much appreciated.
Please note that I am after answers to this specific issue, I'm not looking for alternative ways of doing a GroupBy (unless there's a fundamental reason why this shouldn't work) - this will be rolled into a much larger solution for building queries and I want to use the same process throughout.
Thanks Svyatoslav - as I thought, it was me being especially dumb!
Your comments, as well as a discussion with a friend who has a lot SQL knowledge pointed me in the right direction.
I had been thinking that the GroupBy expression was going to return an Enumerable for each key value and was trying to pass that into a function ... it always felt wrong, but I just ignored that and kept going.
It's obvious now that I need to tell the GroupBy what to calculate and return (i.e. your comment about aggregation).
So for this easy example, the solution is very simple:
var pExp = new ParameterExpression[] { param1, param2 };
var countTypes = new Type[] { typeof(TTable) };
var countParams = new Expression[] { param2 };
var countExp = Expression.Call(typeof(Enumerable), "Count", countTypes, countParams);
var methodExpression = Expression.Lambda<Func<T, IEnumerable<TTable>, GroupResult>>(
Expression.New(ci, new Expression[] { param1, countExp }),
pExp
);
Just by adding the 'Count' expression into the GroupBy method call it works!
.. and adding a new ctor for GroupResult:
public GroupResult(string name, int count)
{
Name = name;
NumRecords = count;
}
(yep, I feel a bit stupid!)
I need some help here. I am trying to make a portion of an EF Query reusable.
var query = from sr in SomeEFRepository.SelectAll()
select new {
KeyValuePivotField1 = (from kvd in sr.KeyValueData
where kvd.KeyName == "FieldName1"
select kvd.Value).FirstOrDefault(),
KeyValuePivotField2 = (from kvd in sr.KeyValueData
where kvd.KeyName == "FieldName2"
select kvd.Value).FirstOrDefault(),
KeyValuePivotField3 = (from kvd in sr.KeyValueData
where kvd.KeyName == "FieldName3"
select kvd.Value).FirstOrDefault()
}
If you look you can see that I am constantly repeating myself with the following code.
(from kvd in sr.KeyValueData
where kvd.KeyName == "SomeFieldName"
select kvd.Value).FirstOrDefault(),
How can I make a method that EF will recognize in order to do something like this?
var query = from sr in SomeEFRepository.SelectAll()
select new {
KeyValuePivotField1 = GetFieldFromKeyValue("FieldName1"),
KeyValuePivotField2 = GetFieldFromKeyValue("FieldName2"),
KeyValuePivotField3 = GetFieldFromKeyValue("FieldName3"),
}
Any suggestions will help. I have been reading about Expression Trees but not sure if
that would be a good approach or even possible.
Note: I am using EF 5.0 with DBContext.
Extension methods seem like a good fit for this. I don't know exactly what you are doing so this is a example
Customer class
public class Customer{
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public override string ToString()
{
return string.Format("Customer is {0} {1}", this.FirstName, this.LastName);
}
}
CustomerExtensionMethod
//Class MUST be static
public static class CustomerExtensionMethods
{
//Method MUST be static and use this keyword to be an extension method
public static Customer GetByFirstName(this IEnumerable<Customer> source, string value)
{
return source.Where(c => c.FirstName == value).FirstOrDefault();
}
}
Main program to show how it works
var customers = new List<Customer>
{
new Customer { Id = 1, FirstName = "Foo", LastName = "Bar" },
new Customer { Id = 2, FirstName = "Mark", LastName = "Whoknows" },
new Customer { Id = 3, FirstName = "Ronald", LastName = "McDonald" },
};
var userWithFirstNameOfRonald = customers.GetByFirstName("Ronald");
Console.WriteLine(userWithFirstNameOfRonald.ToString());
This should print "Customer Is Ronald McDonald". The important parts are commented. The Class that contains the method should be static and the method needs to be static. Along with that the first parameter of the method needs to be this to signal it is an extension method. This will allow you to call it on the specific type you put for the first parameter.
Found a great answer and article to my troubles.
http://www.codeproject.com/Articles/402594/Black-Art-LINQ-expressions-reuse
A nice NuGet Package LinqExpressionProjection 1.0.0 that is perfect for my problem.
http://nuget.org/packages/LinqExpressionProjection
Basically what I want to do is return a read only string (user name) which is dependent on the navigation property User and just concatenates the first and last name together.
Ideally I'd just assign a value to OwnerUserId and then use the navigation property to retrieve the data if this is possible? I want to keep the model class as clean as possible.
Any ideas?
POCO Model
namespace Model
{
public class TicketReply
{
public int TicketReplyId { get; set; }
public string Title { get; set; }
/* ....more.... */
/* User ID FK */
public int OwnerUserId { get; set; }
/* User Navigation property */
[ForeignKey("OwnerUserId")]
[IgnoreDataMember]
public User User { get; set; }
/* Here is where I am stuck.... */
public string UserName
{
get { return User.FirstName + " " + User.LastName; }
set { }
}
}
}
*DB initialiser* - just to give some context
private List<TicketReply> AddReplies(DbContext context)
{
var replies = new List<TicketReply>
{
new TicketReply { TicketReplyId = 1, TicketId = 1, CreatedAt = DateTime.Now, OwnerUserId = 1, Text = "My initial query"},
new TicketReply { TicketReplyId = 2, TicketId = 1, CreatedAt = DateTime.Now, OwnerUserId = 2, Text = "Test reply."},
new TicketReply { TicketReplyId = 3, TicketId = 2, CreatedAt = DateTime.Now, OwnerUserId = 1, Text = "Test query"}
};
replies.ForEach(status => context.TicketReplies.Add(status));
context.SaveChanges();
return replies;
}
Thanks
Arthur
Roughly (and typing from memory)
Make it part of the User, e.g...
public class User
{
[NotMapped] // or from code "modelBuilder.Entity<User>().Ignore(x => x.UserName);"
public string UserName
// your user name implementation
}
You cannot just assign OwnerUserId and expect it to work - User will
be loaded when you load Ticket. Also you don't use 'OwnerUserId' in
your fk-entity - to search for for User.
You can use that to 'set' the User 'it points to' - it's saved when you first do the 'SaveChanges'.
e.g...
var user = new User {First = ... Second=...};
db.Tickets.Add(new Ticket{ User = user, ...});
// or...
db.Tickets.Add(new Ticket{ OwnerUserId = ownerid, ...}); // to point to exisitng one you know exists
db.SaveChanges();
...sometime later on...
var ticket = new Ticket { OwnerUserId = ownerid };
// ticket.User is null - won't work
var ticket = // tickets query // db.Tickets.Where(x => x.TicketId == ticketid).FirstOrDefault();
var username = ticket.User.UserName; // now it works, you have your User loaded
Or explicitely...
var user = db.Users.Where(x => x.UserId == ownerid).FirstOrDefault();
var username = user.UserName;
Trying to make use of System.Web.Http.OData.Delta to implement PATCH methods in ASP.NET Web API services, but it seems unable to apply changes to properties of type IEnumerable<T>. I'm using the latest Git revision of Delta (2012.2-rc-76-g8a73abe). Has anyone been able to make this work?
Consider this data type, which it should be possible to update in a PATCH request to the Web API service:
public class Person
{
HashSet<int> _friends = new HashSet<int>();
public int Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public IEnumerable<int> Friends
{
get { return _friends; }
set
{
_friends = value != null ? new HashSet<int>(value) : new HashSet<int>();
}
}
public Person(int id, string firstName, string lastName)
{
Id = id;
FirstName = firstName;
LastName = lastName;
}
public Person()
{
}
}
This Web API method implements patching of a Person through Delta<Person>:
public void Patch(int id, Delta<Person> delta)
{
var person = _persons.Single(p => p.Id == id);
delta.Patch(person);
}
If I send a PATCH request with the following JSON to the service, the person's Friends property should be updated, but alas it doesn't happen:
{"Friends": [1]}
The crux of the matter is really how to make Delta update Friends with this data. See also the discussion at CodePlex.
The problem likely is that Deta will try to assign JSON's JArray to your Hashset<int>
If you are using it against JsonMEdiaTypeFormatter and you internalized the Delta code (meaning you can modify it), you'd have to do something like this (this is rough, but works):
Inside, bool TrySetPropertyValue(string name, object value) of Delta<T>, where it returns false:
if (value != null && !cacheHit.Property.PropertyType.IsPrimitive && !isGuid && !cacheHit.Property.PropertyType.IsAssignableFrom(value.GetType()))
{
return false;
}
Change to:
var valueType = value.GetType();
var propertyType = cacheHit.Property.PropertyType;
if (value != null && !propertyType.IsPrimitive && !propertyType.IsAssignableFrom(valueType))
{
var array = value as JArray;
if (array == null)
return false;
var underlyingType = propertyType.GetGenericArguments().FirstOrDefault() ??
propertyType.GetElementType();
if (underlyingType == typeof(string))
{
var a = array.ToObject<IEnumerable<string>>();
value = Activator.CreateInstance(propertyType, a);
}
else if (underlyingType == typeof(int))
{
var a = array.ToObject<IEnumerable<int>>();
value = Activator.CreateInstance(propertyType, a);
}
else
return false;
}
This will only work with collections of int or string but hopefully nudges you into a good direction.
For example, now your model can have:
public class Team {
public HashSet<string> PlayerIds { get; set; }
public List<int> CoachIds { get; set; }
}
And you'd be able to successfully update them.
You could override the TrySetPropertyValue method of the Delta class and make use of JArray class:
public sealed class DeltaWithCollectionsSupport<T> : Delta<T> where T : class
{
public override bool TrySetPropertyValue(string name, object value)
{
var propertyInfo = typeof(T).GetProperty(name);
return propertyInfo != null && value is JArray array
? base.TrySetPropertyValue(name, array.ToObject(propertyInfo.PropertyType))
: base.TrySetPropertyValue(name, value);
}
}
If you are using the ODataMediaTypeFormatter, this should be working. There are a couple of caveats to mention though.
1) your collections have to be settable.
2) the entire collection is replaced. you cannot remove/add individual elements.
Also, there is an issue tracking item 1 - '670 -Delta should support non-settable collections.'
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);
}