replace a returned property with another property from a different model linking by Id - asp.net-core

Hi I'm not quite sure how to do this.
I have this .NetCore EntityFrame controller that gets a list of characters.
It works when I simply get the list of characters by doing this:
return await _context.Character.ToListAsync();
This returns the name, class, race, age, armorPoints, hitPoints, and spiritAnimalId for each character in the database.
But now, instead of simply returning just the spiritAnimalId, I want to return the actual name of the animal.
So I started writing some code, but quickly lost the ability to figure out how to return the name of the spirit animal.
Here is what I have:
// GET: api/Characters
[HttpGet]
public async Task<ActionResult<IEnumerable<Character>>> GetCharacters()
{
var characters= await _context.Character.ToListAsync();
foreach(Character c in characters)
{
var a = _context.SpiritAnimal.FindAsync(c.spiritAnimalId);
var name = a.Result.Name;
}
return charcters;
}
So I'm lost as to how to return the list of projects and replace the spiritAnimalId with it's name.
NEW CODE:
// GET: api/Characters
[HttpGet]
public async Task<ActionResult<List<Character>>> GetCharacters()
{
var characters = await _context.Character
.Select(x => new
{
x.Id,
x.Name,
x.Race,
x.Class,
x.Age,
SpiritAnimalName = x.SpiritAnimalName.Name
})
.ToListAsync();
return characters;
}
NEW ERROR:
Cannot implicitly convert type 'System.Collections.Generic.List<<anonymous type
to 'Microsoft.AspNetCore.Mvc.ActionResult<System.Collections.Generic.List
Thanks!

If you don't already, you should have a navigation property on your Character class like:
public SpiritAnimal SpiritAnimal { get; set; }
Then, when you make your query, you should include this relationship, so it's joined in the same query:
var characters = await _context.Character.Include(x => x.SpiritAnimal).ToListAsync();
As you're iterating over the characters, you can simply access: character.SpiritAnimal.Name. If you're wanting to only return the name from the query, you'll need to use Select to project into another class or an anonymous object:
var characters = await _context.Character
.Select(x => new
{
CharacterName = x.Name,
SpiritAnimalName = x.SpiritAnimal.Name
}
.ToListAsync();
When the Select expression utilizes a property on a related entity, you don't need to explicitly load it; EF is smart enough to realize it needs to do a join to satisfy the return.

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...
}

Return a list of elements that are NOT in two previous lists

I have 2 lists of IDs and I need to return a list with the products that aren't in any of those lists:
public IEnumerable<Produto> GetProdutosIdNotInFamily(Guid produtoId)
{
var produtosPai = GetListaPaisId(produtoId);
var produtosFilho = GetListaFilhosId(produtoId);
var prod = _dbContext.Produtos
.Where(u => !produtosPai.Any(p => p.ProdutoFilhoId == u.Id)
&& !produtosFilho.Any(p => p.ProdutoFilhoId == u.Id));
return prod;
}
You can do this in two ways -- One using Contains and other using Any like you provided in your snippet in the post.
Using Contains Method
If you want to use Contains() method, you may be pulling out all the product Ids into a collection and apply LINQ on top of it and get the list that is not part of both your reference lists. Sample code is as shown below
// This is the sample model I am dealing with
public class Dummy
{
public int Id { get; set; }
public string Name { get; set; }
}
// Assuming the below call returns list of 'Dummy' objects
var products = _dbContext.Produtos;
// list1 & list2 are populated in your case already through the method calls
var exclusionList1 = list1.Select(x => x.Id).ToList<int>();
var exclusionList2 = list2.Select(x => x.Id).ToList<int>();
var myList = products.Where(x => !exclusionList1.Contains(x.Id) && !exclusionList1.Contains(x.Id)).ToList();
Contains is an instance method and takes an object as a parameter and the time complexity depends on the collection you're using this on.
Using Any
Just like Where, Any is an extension method. It takes a delegate as a parameter which gives you greater flexibility and control with respect to what you would want to do.
Applying Any to your scenario is as shown below:
var products = _dbContext.Produtos;
var exclusionList1 = GetListaPaisId(produtoId);
var exclusionList2 = GetListaFilhosId(produtoId);
var prod = _dbContext.Produtos.Where(x => !exclusionList1.Any(z => x.Id == z.Id) &&
!exclusionList2.Any(z => x.Id == z.Id)).ToList();
You can choose your approach based on the context under which you are performing this operation.

RavenDB: How to properly query/filter a nested value from a MultiMapIndex?

My application has a requirement that is should be able to filter/search for Pairs by the Number of the related Contact.
A Pair always has a reference to a Contact stored, but the number of the contact is not, and will not, be stored in the reference. So I tried to create a custom index for this, because the Pair and Contact are stored in different collections.
A simplified example of the index looks like this.
public class Pairs_Search : AbstractMultiMapIndexCreationTask<Pairs_Search.Result>
{
public class Result
{
public string Id { get; set; }
public string Workspace { get; set; }
public ContactResult Contact { get; set; }
public bool HasContactDetails { get; set; }
}
public class ContactResult
{
public string Id { get; set; }
public string Name { get; set; }
public int Number { get; set; }
}
public Pairs_Search()
{
AddMap<Pair>(pairs => pairs
.Select(p => new
{
p.Id,
p.Workspace,
Contact = new
{
p.Contact.Id,
p.Contact.Name,
Number = 0
},
// Mark this items as WITHOUT contact details.
HasContactDetails = false,
}
)
);
AddMap<Contact>(contacts => contacts
.Select(c => new
{
Id = (string) null,
Workspace = (string) null,
Contact = new
{
c.Id,
Name = c.DisplayName,
c.Number
},
// Mark this items as WITH contact details.
HasContactDetails = true,
}
)
);
Reduce = results => results
// First group by the contact ID. This will
// create a group with 2 or more items. One with the contact
// details, and one or more with pair details.
// They are all marked by a boolean flag 'HasContactDetails'.
.GroupBy(x => x.Contact.Id)
// We are going to enrich each item in the current group, that is
// marked as 'HasContactDetails = false', with the contact number.
// We need that so that we can filter on it later.
.Select(group =>
group
.Select(i => new
{
i.Id,
i.Workspace,
Contact = new
{
i.Contact.Id,
i.Contact.Name,
// Does the current item have the contact details?
Number = i.HasContactDetails
// Yes, in this case we use the previously set contact number.
? i.Contact.Number
// No, find the item with the contact details and grab the number.
: group.Single(x => x.HasContactDetails).Contact.Number
},
// Pass on the flag that indicates wheter or not
// this item has the contact details. We are going
// to need it later.
i.HasContactDetails
}
)
// We don't need the items with the contact details
// anymore, so filter them out.
.Where(x => !x.HasContactDetails)
)
// Flatten all the small lists to one big list.
.SelectMany(x => x);
// Mark the following fields of the result as searchable.
Index(x => x.Contact.Number, FieldIndexing.Search);
}
}
I've setup a full example that reproduces the issues I am having. You can find the example here.
Creating the index works fine. Querying the index works fine also as it properly matched the pair and contact and enriched the index result with the number of the contact. But when I try to use a .Where() or .Search() on the nested Number property it fails to properly filter the result dataset from the index.
The index without any filtering works as you can see in below code example (also available in the full example).
private static async Task ThisOneWorks()
{
using (var session = Store.OpenAsyncSession())
{
var results = await session
.Query<Pairs_Search.Result, Pairs_Search>()
.ToListAsync();
LogResults("ThisOneWorks()", results);
}
// Output:
// ThisOneWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWorks(): Pair 'Hermione Granger' with number '71'
// ThisOneWorks(): Pair 'Albus Dumbledore' with number '72'
}
Filtering on a non-nested value also works (also available in the full example). As you can see it filters out the one with a different workspace.
private static async Task ThisOneWithWorkspaceFilterWorks()
{
using (var session = Store.OpenAsyncSession())
{
var results = await session
.Query<Pairs_Search.Result, Pairs_Search>()
.Where(x => x.Workspace == "hogwarts")
.ToListAsync();
LogResults("ThisOneWithWorkspaceFilterWorks()", results);
}
// Output:
// ThisOneWithWorkspaceFilterWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWithWorkspaceFilterWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWithWorkspaceFilterWorks(): Pair 'Hermione Granger' with number '71'
}
When I try to filter/search on the Workspace and Number properties I would expect two results that are related to the contact Harry Potter. But instead I just get an empty dataset back.
private static async Task ThisOneWithWorkspaceAndNumberFilterDoesntWork()
{
using (var session = Store.OpenAsyncSession())
{
var results = await session
.Query<Pairs_Search.Result, Pairs_Search>()
.Where(x => x.Workspace == "hogwarts")
.Where(x => x.Contact.Number == 70)
.ToListAsync();
LogResults("ThisOneWithWorkspaceAndNumberFilterDoesntWork()", results);
}
// Output:
// ThisOneWithWorkspaceAndNumberFilterDoesntWork(): EMPTY RESULTS!
}
Can anyone tell me what I am doing wrong here? Any help would be greatly appreciated!
The way to go about it is to store ContactResult in a different collection,
which is what is called a related document in this case,
and when you create the index then you 'Index the Related Document'
Learn from the demo example in:
https://demo.ravendb.net/demos/csharp/related-documents/index-related-documents
The example is for a basic map index but the principle is the same for Multi-Map.
Remove the public class ContactResult from the index class
and define the index with something like:
select new Result
{
....
Number = LoadDocument<Contact>(Pair.Contact).Number
....
}

Orchard Cms Fetching UserPart Field Data in LazyField<T>

I've been Following this post To get my head around Lazy field of T, Which I think I understand, But I'm having trouble getting associated Field Data for a Part loaded this way
Aim - To show photo of blog post author on a blog post.
I want to add a content part "Content Author"
The part Editor should appear as a drop down list of orchard users.
(regardless of the content owner cms users should be able to pick the author)
I have added an image upload field to the User Content Type
I want to show the image of the user on the front end in the view for the Content Author Part
For the first part I have created the content type and used the lazy Filed of UserPart to get the username. However when I try and get the associated fields for the UserPart. There dosent seem to be any.
public class ContentAuthorRecord : ContentPartRecord
{
public virtual string AuthorEmail { get; set; }
}
public class ContentAuthorPart : ContentPart<ContentAuthorRecord>
{
internal readonly LazyField<UserPart> Owner = new LazyField<UserPart>();
public string AuthorEmail
{
get { return Record.AuthorEmail; }
set { Record.AuthorEmail = value; }
}
public UserPart Author
{
get { return Owner.Value; }
set { Owner.Value = value; }
}
public string AuthorName
{
get
{
if (Author == null)
return "Riders for health";
else
{
return Author.UserName;
}
}
}
}
public class ContentAuthorHandler :ContentHandler
{
private readonly IContentManager _contentManager;
public ContentAuthorHandler(IRepository<ContentAuthorRecord> repository, IContentManager contentManager)
{
_contentManager = contentManager;
OnActivated<ContentAuthorPart>(SetUpCustomPart);
Filters.Add(StorageFilter.For(repository));
}
private void SetUpCustomPart(ActivatedContentContext content, ContentAuthorPart part)
{
// Setup the getter of the lazy field
part.Owner.Loader(() => _contentManager.Query<UserPart, UserPartRecord>().List().FirstOrDefault(x => x.Email == part.AuthorEmail));
}
}
I would expect to be able to access the field with something like
(ImageUploadField.Fields.ImageUploadField)Author.Fields.FirstOrDefault(x
=> x.Name == "Photo");
form the within the part class
( although this makes every thing a bit brittle, hard coding a field name, but I'm not sure how eles to go about it)
Further Info
I have a HeaderPart with a Image field added via the cms (not in code) in the display handler I fetch the field like this
protected override DriverResult Display(HeaderPart part, string displayType, dynamic shapeHelper)
{
if (part.HeaderType == HeaderType.Full_width_hero_image)
{
var field = (ImageUploadField) part.Fields.FirstOrDefault(f => f.Name == "HeaderImage");
if (field != null)
{
return ContentShape("Parts_Header_ImageHero",
() => shapeHelper.Parts_Header_ImageHero(ImagePath: field.ImagePath, ImageTitle: field.FileName));
}
return null;
}
if (part.HeaderType == HeaderType.Full_width_hero_video)
{
return ContentShape("Parts_Header_VideoHero", () => shapeHelper.Parts_Header_VideoHero(VideoUrl: part.VideoUrl));
}
if (part.HeaderType == HeaderType.Body_width_video)
{
return ContentShape("Parts_Header_VideoBody", () => shapeHelper.Parts_Header_VideoBody(VideoUrl: part.VideoUrl));
}
return null;
}
This works, But I can do the same for a part loaded into a lazy field.
Cast to dynamic first, then the syntax becomes much simpler: ((dynamic)part.ContentItem).NameOfTheType.NameOfTheField.NameOfTheProperty
If you have added the fields to the User content type via the CMS interface, it may have added the fields to a different part to the one you expect. If you are adding fields to the User content type, by default it will have added the fields to a new part called 'User', not 'UserPart'. Try to following to search all parts in the content item:
(ImageUploadField.Fields.ImageUploadField)Author.ContentItem.Parts
.SelectMany(p => p.Fields).FirstOrDefault(f => f.Name == "Photo");
or directly from the 'User' part:
(ImageUploadField.Fields.ImageUploadField)Author.ContentItem.Parts
.First(p => p.PartDefinition.Name == p.ContentItem.ContentType).Fields
.FirstOrDefault(f => f.Name == "Photo");

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();
}
}