ROW_NUMBER() and nhibernate - finding an item's page - nhibernate

given a query in the form of an ICriteria object, I would like to use NHibernate (by means of a projection?) to find an element's order,
in a manner equivalent to using
SELECT ROW_NUMBER() OVER (...)
to find a specific item's index in the query.
(I need this for a "jump to page" functionality in paging)
any suggestions?
NOTE: I don't want to go to a page given it's number yet - I know how to do that - I want to get the item's INDEX so I can divide it by page size and get the page index.

After looking at the sources for NHibernate, I'm fairly sure that there exists no such functionality.
I wouldn't mind, however, for someone to prove me wrong.
In my specific setting, I did solve this problem by writing a method that takes a couple of lambdas (representing the key column, and an optional column to filter by - all properties of a specific domain entity). This method then builds the sql and calls session.CreateSQLQuery(...).UniqueResult(); I'm not claiming that this is a general purpose solution.
To avoid the use of magic strings, I borrowed a copy of PropertyHelper<T> from this answer.
Here's the code:
public abstract class RepositoryBase<T> where T : DomainEntityBase
{
public long GetIndexOf<TUnique, TWhere>(T entity, Expression<Func<T, TUnique>> uniqueSelector, Expression<Func<T, TWhere>> whereSelector, TWhere whereValue) where TWhere : DomainEntityBase
{
if (entity == null || entity.Id == Guid.Empty)
{
return -1;
}
var entityType = typeof(T).Name;
var keyField = PropertyHelper<T>.GetProperty(uniqueSelector).Name;
var keyValue = uniqueSelector.Compile()(entity);
var innerWhere = string.Empty;
if (whereSelector != null)
{
// Builds a column name that adheres to our naming conventions!
var filterField = PropertyHelper<T>.GetProperty(whereSelector).Name + "Id";
if (whereValue == null)
{
innerWhere = string.Format(" where [{0}] is null", filterField);
}
else
{
innerWhere = string.Format(" where [{0}] = :filterValue", filterField);
}
}
var innerQuery = string.Format("(select [{0}], row_number() over (order by {0}) as RowNum from [{1}]{2}) X", keyField, entityType, innerWhere);
var outerQuery = string.Format("select RowNum from {0} where {1} = :keyValue", innerQuery, keyField);
var query = _session
.CreateSQLQuery(outerQuery)
.SetParameter("keyValue", keyValue);
if (whereValue != null)
{
query = query.SetParameter("filterValue", whereValue.Id);
}
var sqlRowNumber = query.UniqueResult<long>();
// The row_number() function is one-based. Our index should be zero-based.
sqlRowNumber -= 1;
return sqlRowNumber;
}
public long GetIndexOf<TUnique>(T entity, Expression<Func<T, TUnique>> uniqueSelector)
{
return GetIndexOf(entity, uniqueSelector, null, (DomainEntityBase)null);
}
public long GetIndexOf<TUnique, TWhere>(T entity, Expression<Func<T, TUnique>> uniqueSelector, Expression<Func<T, TWhere>> whereSelector) where TWhere : DomainEntityBase
{
return GetIndexOf(entity, uniqueSelector, whereSelector, whereSelector.Compile()(entity));
}
}
public abstract class DomainEntityBase
{
public virtual Guid Id { get; protected set; }
}
And you use it like so:
...
public class Book : DomainEntityBase
{
public virtual string Title { get; set; }
public virtual Category Category { get; set; }
...
}
public class Category : DomainEntityBase { ... }
public class BookRepository : RepositoryBase<Book> { ... }
...
var repository = new BookRepository();
var book = ... a persisted book ...
// Get the index of the book, sorted by title.
var index = repository.GetIndexOf(book, b => b.Title);
// Get the index of the book, sorted by title and filtered by that book's category.
var indexInCategory = repository.GetIndexOf(book, b => b.Title, b => b.Category);
As I said, this works for me. I'll definitely tweak it as I move forward. YMMV.
Now, if the OP has solved this himself, then I would love to see his solution! :-)

ICriteria has this 2 functions:
SetFirstResult()
and
SetMaxResults()
which transform your SQL statement into using ROW_NUMBER (in sql server) or limit in MySql.
So if you want 25 records on the third page you could use:
.SetFirstResult(2*25)
.SetMaxResults(25)

After trying to find an NHibernate based solution for this myself, I ultimately just added a column to the view I happened to be using:
CREATE VIEW vw_paged AS
SELECT ROW_NUMBER() OVER (ORDER BY Id) AS [Row], p.column1, p.column2
FROM paged_table p
This doesn't really help if you need complex sorting options, but it does work for simple cases.
A Criteria query, of course, would look something like this:
public static IList<Paged> GetRange(string search, int rows)
{
var match = DbSession.Current.CreateCriteria<Job>()
.Add(Restrictions.Like("Id", search + '%'))
.AddOrder(Order.Asc("Id"))
.SetMaxResults(1)
.UniqueResult<Paged>();
if (match == null)
return new List<Paged>();
if (rows == 1)
return new List<Paged> {match};
return DbSession.Current.CreateCriteria<Paged>()
.Add(Restrictions.Like("Id", search + '%'))
.Add(Restrictions.Ge("Row", match.Row))
.AddOrder(Order.Asc("Id"))
.SetMaxResults(rows)
.List<Paged>();
}

Related

Looping through Dapper.Net query results without the use of models

I am still new to C# and I am struggling to find a solution to my problem. My SQL dapper query returns a table (based on my understanding though it is not really a table if it is IEnumerable unlike what I am use to working with ADO and recordsets) with three columns col1, col2, and col3 and has multiple rows. I need to loop through this query result for each row and test the values (ie, a foreach loop where I check row(0).field1=5, row(1).field1 = 5 for each row, etc) do what I need to do. This seems so basic but I all the dapper tutorials I see do not show examples for this and if they do they seem to utilize class objects rather than accessing the results directly (if thats even possible or do you have to map the results to a model?) My code is as follows:
String query = "exec dbo.storeProcedure #jsonData, #mainDocJSON, #supportingDocsJSON";
IEnumerable queryResult;
using (var connection = new SqlConnection(connectionString))
{
queryResult = connection.Query(query, new { jsonData = jsonData, mainDocJSON = mainDocJSON, supportingDocsJSON = supportingDocsJSON });
}
I also end up returning IEnumerable results from the controller this code resides in so I send it back to the user in JSON using the following.
return Ok(queryResult);
connection.Query return a IEnumerable, why dont we create a class to map the set from ? Dapper is a micro-ORM, but still... ORM.
For ex: Your table return 3 column Id, Name, CreatedDate.
// declare a class to map the result first
public class ResultHolderDto
{
public int Id { get; set; }
public string Name { get; set; }
public DateTime CreatedDate { get; set; }
}
// query somewhere
// This will return IEnumerable<ResultHolderDto>, feel free to play around as normal
var queryResult = await connection.QueryAsync<ResultHolderDto>(query, new { jsonData = jsonData, mainDocJSON = mainDocJSON, supportingDocsJSON = supportingDocsJSON });
foreach(var item in queryResult)
{
var col1Value = queryResult.Id;
var col2Value = queryResult.Name;
var col3Value = queryResult.CreatedDate;
// Then do something with col1Value, col2Value, col3Value...
}

Using NHibernate to retrieve a set of records with a single query and composite keys

I am using Nhibernate and it's mapping-by-code flavour to retrieve a set of records using a list of composite keys. I am using composite keys like this:
public class PersonAccountKey : IKey
{
public virtual string PersonId { get; set; }
public virtual string AccountNo{ get; set; }
public override bool Equals(object obj)
{
if (obj == null)
return false;
var t = obj as PersonKey;
if (t == null)
return false;
if (PersonId == t.PersonId && AccountNo == t.AccountNo)
return true;
return false;
}
public override int GetHashCode()
{
return (PersonId).GetHashCode() + "|" + (AccountNo).GetHashCode();
}
}
With a list of PersonAccountKey objects, I am trying to get NHibernate to send a single query down to database. I would imagine the query would look like this:
Select PersonId, AccountNo, AccountNickName
From PersonAccount
Where (PersonId = '11' and AccountNo = '10001111')
or (PersonId = '22' and AccountNo = '10001150')
I'm not sure how to achieve this? I tried to use Criteria with composite keys, but I don't think it was meant to be used together. I am trying now Linq 2 NHibernate but am not really getting anywhere either.
Ideally, I would like a method that would take a IEnumerable of keys such as Session.Get<T>(IEnumerable<object>), but this doesn't exist from my searching.
Is this possible out of the box with NHibernate?
Cheers.
Unlike scalar Ids, there's no direct support to use use a list of composite keys as a query parameter. This is another example of why composite keys should be avoided.
Here's an easy workaround:
IEnumerable<PersonAccountKey> keys = GetKeys();
var query = session.CreateCriteria<PersonAccount>();
var keyCriterion = Restrictions.Disjunction();
foreach (var key in keys)
keyCriterion.Add(Restrictions.Eq("id", key));
query.Add(keyCriterion);
var result = query.List<PersonAccount>();
There is an article on MSDN about Enumerable.Contains which does what I think you want.
I don't know if NHibernate's linq supports this at all. It may refuse to do it with an exception.

NHibernate, get rowcount when criteria have a group by

I need to get the count of rows from a criteria query and the criteria got group by projections. (needed to get paging to work)
E.g.
projectionList.Add(Projections.GroupProperty("Col1"), "col1")
.Add(Projections.CountDistinct("Col2"), "Count");
I need to avoid CreateSQL, since I have a lot of criteria.. and the restrictions etc are complex.
Can you do a subcriteria (detached) and then select count(*) from .. ? Can't figure out how?
EDIT: I solved it by getting the sql from the criteria and then modifying it so that it now works! GetSql from criteria
Not entirely sure what you want, but something like this should work (if I understand your question properly):
var subQuery = DetachedCriteria.For<SomeClass>()
.Where(... add your conditions here ...);
var count = Session.CreateCriteria<SomeClass>()
.Where(Property.ForName("Col1").In(
CriteriaTransformer.Clone(subQuery).SetProjection(Projections.Property("Col1"))
.SetProjection(Projections.Count())
.FutureValue<int>();
var results = subQuery.GetExecutableCriteria(Session)
.SetProjection(Projections.GroupProperty("Col1"), "col1"),
Projections.CountDistinct("Col2"), "Count")
).List<object[]>();
Just to think a bit outside the box and remove the query complexity from NHiberate. You can make a View for the query in the database and then make a mapping for the view.
I think this can be done by using NH Multi Queries.
Here is some stuff about it: http://ayende.com/blog/3979/nhibernate-futures Example shows how we can run query and get results count of that query in one roundtrip to the database.
And here is good answer, which sounds similar to what you want to achieve: nhibernate 2.0 Efficient Data Paging DataList Control and ObjectDataSource in which they get the result page AND total records count in one roundtrip to the database.
Also, I doubt that it is possible to read pure ##rowcount value with NH without changing sql query, as ##rowcount is database specific thing.
My assumption would be that for your case it is not possible to avoid GetSql from criteria solution, unless you simplify your query or approach. Maybe it worth to try this as well.
If you can post bigger chunk of your code, probably someone will be able to figure this out.
I 've resolved this problem on the java version (Hibernate). The problem is that the RowProjection function is some like:
count(*)
That is an aggregate function: so if you create a 'group by' property your result is a list of the grouped row and for each row you have the total count of the group.
For me, with oracle database, to make it work i've create a custom projection that, instead of create function count(*), the function is
count(count(*))
and the property in the group by clause are not written in the select ... from statement. To do that it not that simple, the problem is that you have to provide all stack to create the right sql so, with the java version I've to subclasse 2 class:
SimpleProjection
ProjectionList
After that my query generated as:
select count(*), col1, col2 from table1 group by col1, col2
become
select count(count(*)) from table1 group by col1, col2
and the result are the total row given by
select col1, col2 from table1 group by col1, col2
(usable with pagination system)
I post here the java version of the classes, if are useful for you:
public class CustomProjectionList extends ProjectionList {
private static final long serialVersionUID = 5762155180392132152L;
#Override
public ProjectionList create() {
return new CustomProjectionList();
}
public static ProjectionList getNewCustomProjectionList() {
return new CustomProjectionList();
}
#Override
public String toSqlString(Criteria criteria, int loc, CriteriaQuery criteriaQuery) throws HibernateException {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < getLength(); i++) {
Projection proj = getProjection(i);
String sqlString = proj.toSqlString(criteria, loc, criteriaQuery);
buf.append(sqlString);
loc += getColumnAliases(loc, criteria, criteriaQuery, proj).length;
if (i < (getLength() - 1) && sqlString != null && sqlString.length() > 0)
buf.append(", ");
}
return buf.toString();
}
private static String[] getColumnAliases(int loc, Criteria criteria, CriteriaQuery criteriaQuery, Projection projection) {
return projection instanceof EnhancedProjection ?
( ( EnhancedProjection ) projection ).getColumnAliases( loc, criteria, criteriaQuery ) :
projection.getColumnAliases( loc );
}
}
public class CustomPropertyProjection extends SimpleProjection {
private static final long serialVersionUID = -5206671448535977079L;
private String propertyName;
private boolean grouped;
#Override
public String[] getColumnAliases(int loc, Criteria criteria, CriteriaQuery criteriaQuery) {
return new String[0];
}
#Override
public String[] getColumnAliases(int loc) {
return new String[0];
}
#Override
public int getColumnCount(Criteria criteria, CriteriaQuery criteriaQuery) {
return 0;
}
#Override
public String[] getAliases() {
return new String[0];
}
public CustomPropertyProjection(String prop, boolean grouped) {
this.propertyName = prop;
this.grouped = grouped;
}
protected CustomPropertyProjection(String prop) {
this(prop, false);
}
public String getPropertyName() {
return propertyName;
}
public String toString() {
return propertyName;
}
public Type[] getTypes(Criteria criteria, CriteriaQuery criteriaQuery)
throws HibernateException {
return new Type[0];
}
public String toSqlString(Criteria criteria, int position, CriteriaQuery criteriaQuery)
throws HibernateException {
return "";
}
public boolean isGrouped() {
return grouped;
}
public String toGroupSqlString(Criteria criteria, CriteriaQuery criteriaQuery)
throws HibernateException {
if (!grouped) {
return super.toGroupSqlString(criteria, criteriaQuery);
}
else {
return StringHelper.join( ", ", criteriaQuery.getColumns( propertyName, criteria ) );
}
}
}
public class CustomRowCountProjection extends SimpleProjection {
/**
*
*/
private static final long serialVersionUID = -7886296860233977609L;
#SuppressWarnings("rawtypes")
private static List ARGS = java.util.Collections.singletonList( "*" );
public CustomRowCountProjection() {
super();
}
public String toString() {
return "count(count(*))";
}
public Type[] getTypes(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException {
return new Type[] {
getFunction( criteriaQuery ).getReturnType( null, criteriaQuery.getFactory() )
};
}
public String toSqlString(Criteria criteria, int position, CriteriaQuery criteriaQuery) throws HibernateException {
SQLFunction countSql = getFunction( criteriaQuery );
String sqlString = countSql.toString() + "(" + countSql.render( null, ARGS, criteriaQuery.getFactory() ) + ") as y" + position + '_';
return sqlString;
}
protected SQLFunction getFunction(CriteriaQuery criteriaQuery) {
SQLFunction function = criteriaQuery.getFactory()
.getSqlFunctionRegistry()
.findSQLFunction( "count" );
if ( function == null ) {
throw new HibernateException( "Unable to locate count function mapping" );
}
return function;
}
}
Hope this help.

Does Dynamic Linq support BindingList<T>?

Question
I'm trying to use the Dynamic Linq Sample from Microsoft with BindingList<T> objects. But it looks like the Dynamic Linq will only work with IQueryable. What's the deal here, why doesn't BindingList<T> implement IQueryable. And is there a way around this?
Background Detail: I have many data sets that I need to dynamically filter at run time. Here is an example:
BindingList<MyObject> list = new BindingList<MyObject>();
MyObject selectedObj = list.FirstOrDefault(o => o.Name == "Master P")
// then later ...
MyObject selectedObj = list.FirstOrDefault(o => o.City == "Boston")
I am trying to make these queries dynamic, so the user can choose from all properties of MyObject to use in the query.
There is an Extension method on BindingList; AsQueryable(). So you can use
list.AsQueryable();
But if you want to search on all criteria could you create a search that uses an instance of MyObject as the search criteria and then generated a result set based on the Criteria in the object using standard link.
For example:
public List<MyObject> Search(MyObject SearchCriteria)
{
BindingList<MyObject> list = new BindingList<MyObject>();
list.Add(new MyObject("Test", "Boston"));
list.Add(new MyObject("Test2", "Atlanta"));
IEnumerable<MyObject> results = list.AsEnumerable();
if (!String.IsNullOrEmpty(SearchCriteria.Name))
results = results.Where(l => l.Name.Contains(SearchCriteria.Name));
if (!String.IsNullOrEmpty(SearchCriteria.City))
results = results.Where(l => l.City.Contains(SearchCriteria.City));
return results.ToList();
}
So in the following, Results1 will have 2 results and Results 2 will have only 1.
List<MyObject> results1 = Search(new MyObject("Test", ""));
List<MyObject> results2 = Search(new MyObject("Test", "Boston"));
I used a simple structure for MyObject as an example in this:
public class MyObject
{
public MyObject(string name, string city)
{
this.Name = name;
this.City = city;
}
public string Name { get; set; }
public string City { get; set; }
}

How to update only one field using Entity Framework?

Here's the table
Users
UserId
UserName
Password
EmailAddress
and the code..
public void ChangePassword(int userId, string password){
//code to update the password..
}
Ladislav's answer updated to use DbContext (introduced in EF 4.1):
public void ChangePassword(int userId, string password)
{
var user = new User() { Id = userId, Password = password };
using (var db = new MyEfContextName())
{
db.Users.Attach(user);
db.Entry(user).Property(x => x.Password).IsModified = true;
db.SaveChanges();
}
}
You can tell entity-framework which properties have to be updated in this way:
public void ChangePassword(int userId, string password)
{
var user = new User { Id = userId, Password = password };
using (var context = new ObjectContext(ConnectionString))
{
var users = context.CreateObjectSet<User>();
users.Attach(user);
context.ObjectStateManager.GetObjectStateEntry(user)
.SetModifiedProperty("Password");
context.SaveChanges();
}
}
In Entity Framework Core, Attach returns the entry, so all you need is:
var user = new User { Id = userId, Password = password };
db.Users.Attach(user).Property(x => x.Password).IsModified = true;
db.SaveChanges();
You have basically two options:
go the EF way all the way, in that case, you would
load the object based on the userId provided - the entire object gets loaded
update the password field
save the object back using the context's .SaveChanges() method
In this case, it's up to EF how to handle this in detail. I just tested this, and in the case I only change a single field of an object, what EF creates is pretty much what you'd create manually, too - something like:
`UPDATE dbo.Users SET Password = #Password WHERE UserId = #UserId`
So EF is smart enough to figure out what columns have indeed changed, and it will create a T-SQL statement to handle just those updates that are in fact necessary.
you define a stored procedure that does exactly what you need, in T-SQL code (just update the Password column for the given UserId and nothing else - basically executes UPDATE dbo.Users SET Password = #Password WHERE UserId = #UserId) and you create a function import for that stored procedure in your EF model and you call this function instead of doing the steps outlined above
i'm using this:
entity:
public class Thing
{
[Key]
public int Id { get; set; }
public string Info { get; set; }
public string OtherStuff { get; set; }
}
dbcontext:
public class MyDataContext : DbContext
{
public DbSet<Thing > Things { get; set; }
}
accessor code:
MyDataContext ctx = new MyDataContext();
// FIRST create a blank object
Thing thing = ctx.Things.Create();
// SECOND set the ID
thing.Id = id;
// THIRD attach the thing (id is not marked as modified)
db.Things.Attach(thing);
// FOURTH set the fields you want updated.
thing.OtherStuff = "only want this field updated.";
// FIFTH save that thing
db.SaveChanges();
While searching for a solution to this problem, I found a variation on GONeale's answer through Patrick Desjardins' blog:
public int Update(T entity, Expression<Func<T, object>>[] properties)
{
DatabaseContext.Entry(entity).State = EntityState.Unchanged;
foreach (var property in properties)
{
var propertyName = ExpressionHelper.GetExpressionText(property);
DatabaseContext.Entry(entity).Property(propertyName).IsModified = true;
}
return DatabaseContext.SaveChangesWithoutValidation();
}
"As you can see, it takes as its second parameter an expression of a
function. This will let use this method by specifying in a Lambda
expression which property to update."
...Update(Model, d=>d.Name);
//or
...Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);
( A somewhat similar solution is also given here: https://stackoverflow.com/a/5749469/2115384 )
The method I am currently using in my own code, extended to handle also (Linq) Expressions of type ExpressionType.Convert. This was necessary in my case, for example with Guid and other object properties. Those were 'wrapped' in a Convert() and therefore not handled by System.Web.Mvc.ExpressionHelper.GetExpressionText.
public int Update(T entity, Expression<Func<T, object>>[] properties)
{
DbEntityEntry<T> entry = dataContext.Entry(entity);
entry.State = EntityState.Unchanged;
foreach (var property in properties)
{
string propertyName = "";
Expression bodyExpression = property.Body;
if (bodyExpression.NodeType == ExpressionType.Convert && bodyExpression is UnaryExpression)
{
Expression operand = ((UnaryExpression)property.Body).Operand;
propertyName = ((MemberExpression)operand).Member.Name;
}
else
{
propertyName = System.Web.Mvc.ExpressionHelper.GetExpressionText(property);
}
entry.Property(propertyName).IsModified = true;
}
dataContext.Configuration.ValidateOnSaveEnabled = false;
return dataContext.SaveChanges();
}
New EF Core 7 native feature — ExecuteUpdate:
Finally! After a long wait, EF Core 7.0 now has a natively supported way to run UPDATE (and also DELETE) statements while also allowing you to use arbitrary LINQ queries (.Where(u => ...)), without having to first retrieve the relevant entities from the database: The new built-in method called ExecuteUpdate — see "What's new in EF Core 7.0?".
ExecuteUpdate is precisely meant for these kinds of scenarios, it can operate on any IQueryable instance, and lets you update specific columns on any number of rows, while always issuing a single UPDATE statement behind the scenes, making it as efficient as possible.
Usage:
Let's take OP's example — i.e. updating the password column of a specific user:
dbContext.Users
.Where(u => u.Id == someId)
.ExecuteUpdate(b =>
b.SetProperty(u => u.Password, "NewPassword")
);
As you can see, calling ExecuteUpdate requires you to make calls to the SetProperty method, to specify which property to update, and also what new value to assign to it.
EF Core will translate this into the following UPDATE statement:
UPDATE [u]
SET [u].[Password] = "NewPassword"
FROM [Users] AS [u]
WHERE [u].[Id] = someId
Also, ExecuteDelete for deleting rows:
There's also a counterpart to ExecuteUpdate called ExecuteDelete, which, as the name implies, can be used to delete a single or multiple rows at once without having to first fetch them.
Usage:
// Delete users that haven't been active in 2022:
dbContext.Users
.Where(u => u.LastActiveAt.Year < 2022)
.ExecuteDelete();
Similar to ExecuteUpdate, ExecuteDelete will generate DELETE SQL statements behind the scenes — in this case, the following one:
DELETE FROM [u]
FROM [Users] AS [u]
WHERE DATEPART(year, [u].[LastActiveAt]) < 2022
Other notes:
Keep in mind that both ExecuteUpdate and ExecuteDelete are "terminating", meaning that the update/delete operation will take place as soon as you call the method. You're not supposed to call dbContext.SaveChanges() afterwards.
If you're curious about the SetProperty method, and you're confused as to why ExectueUpdate doesn't instead receive a member initialization expression (e.g. .ExecuteUpdate(new User { Email = "..." }), then refer to this comment (and the surrounding ones) on the GitHub issue for this feature.
Furthermore, if you're curious about the rationale behind the naming, and why the prefix Execute was picked (there were also other candidates), refer to this comment, and the preceding (rather long) conversation.
Both methods also have async equivalents, named ExecuteUpdateAsync, and ExecuteDeleteAsync respectively.
In EntityFramework Core 2.x there is no need for Attach:
// get a tracked entity
var entity = context.User.Find(userId);
entity.someProp = someValue;
// other property changes might come here
context.SaveChanges();
Tried this in SQL Server and profiling it:
exec sp_executesql N'SET NOCOUNT ON;
UPDATE [User] SET [someProp] = #p0
WHERE [UserId] = #p1;
SELECT ##ROWCOUNT;
',N'#p1 int,#p0 bit',#p1=1223424,#p0=1
Find ensures that already loaded entities do not trigger a SELECT and also automatically attaches the entity if needed (from the docs):
Finds an entity with the given primary key values. If an entity with the given primary key values is being tracked by the context, then it is returned immediately without making a request to the database. Otherwise, a query is made to the database for an entity with the given primary key values and this entity, if found, is attached to the context and returned. If no entity is found, then null is returned.
I'm late to the game here, but this is how I am doing it, I spent a while hunting for a solution I was satisified with; this produces an UPDATE statement ONLY for the fields that are changed, as you explicitly define what they are through a "white list" concept which is more secure to prevent web form injection anyway.
An excerpt from my ISession data repository:
public bool Update<T>(T item, params string[] changedPropertyNames) where T
: class, new()
{
_context.Set<T>().Attach(item);
foreach (var propertyName in changedPropertyNames)
{
// If we can't find the property, this line wil throw an exception,
//which is good as we want to know about it
_context.Entry(item).Property(propertyName).IsModified = true;
}
return true;
}
This could be wrapped in a try..catch if you so wished, but I personally like my caller to know about the exceptions in this scenario.
It would be called in something like this fashion (for me, this was via an ASP.NET Web API):
if (!session.Update(franchiseViewModel.Franchise, new[]
{
"Name",
"StartDate"
}))
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.NotFound));
Entity framework tracks your changes on objects that you queried from database via DbContext. For example if you DbContext instance name is dbContext
public void ChangePassword(int userId, string password){
var user = dbContext.Users.FirstOrDefault(u=>u.UserId == userId);
user.password = password;
dbContext.SaveChanges();
}
I know this is an old thread but I was also looking for a similar solution and decided to go with the solution #Doku-so provided. I'm commenting to answer the question asked by #Imran Rizvi , I followed #Doku-so link that shows a similar implementation. #Imran Rizvi's question was that he was getting an error using the provided solution 'Cannot convert Lambda expression to Type 'Expression> [] ' because it is not a delegate type'. I wanted to offer a small modification I made to #Doku-so's solution that fixes this error in case anyone else comes across this post and decides to use #Doku-so's solution.
The issue is the second argument in the Update method,
public int Update(T entity, Expression<Func<T, object>>[] properties).
To call this method using the syntax provided...
Update(Model, d=>d.Name, d=>d.SecondProperty, d=>d.AndSoOn);
You must add the 'params' keyword in front of the second arugment as so.
public int Update(T entity, params Expression<Func<T, object>>[] properties)
or if you don't want to change the method signature then to call the Update method you need to add the 'new' keyword, specify the size of the array, then finally use the collection object initializer syntax for each property to update as seen below.
Update(Model, new Expression<Func<T, object>>[3] { d=>d.Name }, { d=>d.SecondProperty }, { d=>d.AndSoOn });
In #Doku-so's example he is specifying an array of Expressions so you must pass the properties to update in an array, because of the array you must also specify the size of the array. To avoid this you could also change the expression argument to use IEnumerable instead of an array.
Here is my implementation of #Doku-so's solution.
public int Update<TEntity>(LcmsEntities dataContext, DbEntityEntry<TEntity> entityEntry, params Expression<Func<TEntity, object>>[] properties)
where TEntity: class
{
entityEntry.State = System.Data.Entity.EntityState.Unchanged;
properties.ToList()
.ForEach((property) =>
{
var propertyName = string.Empty;
var bodyExpression = property.Body;
if (bodyExpression.NodeType == ExpressionType.Convert
&& bodyExpression is UnaryExpression)
{
Expression operand = ((UnaryExpression)property.Body).Operand;
propertyName = ((MemberExpression)operand).Member.Name;
}
else
{
propertyName = System.Web.Mvc.ExpressionHelper.GetExpressionText(property);
}
entityEntry.Property(propertyName).IsModified = true;
});
dataContext.Configuration.ValidateOnSaveEnabled = false;
return dataContext.SaveChanges();
}
Usage:
this.Update<Contact>(context, context.Entry(modifiedContact), c => c.Active, c => c.ContactTypeId);
#Doku-so provided a cool approach using generic's, I used the concept to solve my issue but you just can't use #Doku-so's solution as is and in both this post and the linked post no one answered the usage error questions.
Combining several suggestions I propose the following:
async Task<bool> UpdateDbEntryAsync<T>(T entity, params Expression<Func<T, object>>[] properties) where T : class
{
try
{
var entry = db.Entry(entity);
db.Set<T>().Attach(entity);
foreach (var property in properties)
entry.Property(property).IsModified = true;
await db.SaveChangesAsync();
return true;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("UpdateDbEntryAsync exception: " + ex.Message);
return false;
}
}
called by
UpdateDbEntryAsync(dbc, d => d.Property1);//, d => d.Property2, d => d.Property3, etc. etc.);
Or by
await UpdateDbEntryAsync(dbc, d => d.Property1);
Or by
bool b = UpdateDbEntryAsync(dbc, d => d.Property1).Result;
I use ValueInjecter nuget to inject Binding Model into database Entity using following:
public async Task<IHttpActionResult> Add(CustomBindingModel model)
{
var entity= await db.MyEntities.FindAsync(model.Id);
if (entity== null) return NotFound();
entity.InjectFrom<NoNullsInjection>(model);
await db.SaveChangesAsync();
return Ok();
}
Notice the usage of custom convention that doesn't update Properties if they're null from server.
ValueInjecter v3+
public class NoNullsInjection : LoopInjection
{
protected override void SetValue(object source, object target, PropertyInfo sp, PropertyInfo tp)
{
if (sp.GetValue(source) == null) return;
base.SetValue(source, target, sp, tp);
}
}
Usage:
target.InjectFrom<NoNullsInjection>(source);
Value Injecter V2
Lookup this answer
Caveat
You won't know whether the property is intentionally cleared to null OR it just didn't have any value it. In other words, the property value can only be replaced with another value but not cleared.
_context.Users.UpdateProperty(p => p.Id, request.UserId, new UpdateWrapper<User>()
{
Expression = p => p.FcmId,Value = request.FcmId
});
await _context.SaveChangesAsync(cancellationToken);
Update Property is an extension method
public static void UpdateProperty<T, T2>(this DbSet<T> set, Expression<Func<T, T2>> idExpression,
T2 idValue,
params UpdateWrapper<T>[] updateValues)
where T : class, new()
{
var entity = new T();
var attach = set.Attach(entity);
attach.Property(idExpression).IsModified = false;
attach.Property(idExpression).OriginalValue = idValue;
foreach (var update in updateValues)
{
attach.Property(update.Expression).IsModified = true;
attach.Property(update.Expression).CurrentValue = update.Value;
}
}
And Update Wrapper is a class
public class UpdateWrapper<T>
{
public Expression<Func<T, object>> Expression { get; set; }
public object Value { get; set; }
}
I was looking for same and finally I found the solution
using (CString conn = new CString())
{
USER user = conn.USERs.Find(CMN.CurrentUser.ID);
user.PASSWORD = txtPass.Text;
conn.SaveChanges();
}
believe me it work for me like a charm.
public async Task<bool> UpdateDbEntryAsync(TEntity entity, params Expression<Func<TEntity, object>>[] properties)
{
try
{
this.Context.Set<TEntity>().Attach(entity);
EntityEntry<TEntity> entry = this.Context.Entry(entity);
entry.State = EntityState.Modified;
foreach (var property in properties)
entry.Property(property).IsModified = true;
await this.Context.SaveChangesAsync();
return true;
}
catch (Exception ex)
{
throw ex;
}
}
public void ChangePassword(int userId, string password)
{
var user = new User{ Id = userId, Password = password };
using (var db = new DbContextName())
{
db.Entry(user).State = EntityState.Added;
db.SaveChanges();
}
}