I have silverlight application.
This is an invoke operation (in Open Ria Service - WCF Ria Service open source version) with Entity Framework 6.
public List<VaultAmount> GetCurrentVaultAmounts(Guid vaultId)
{
return this.DbContext.VaultAmounts
.Include(v => v.VaultAmountQuantities)
.Include(v => v.VaultCurrency)
.Include(v => v.Vault)
.Include(v => v.VaultAmountQuantities.Select(vaq => vaq.VaultCurrency))
.Where(v => v.VaultId == vaultId && v.IsCurrent).ToList();
}
[Invoke]
public void UpdateVaultRemainders(List<VaultAmountQuantity> updatedQuantities, string comment, Guid userId,
string friendlyName,
Guid vaultAmountId, int currencyId)
{
VaultAmount vaultAmount =
this.DbContext.VaultAmounts
.Include("Vault")
.SingleOrDefault(va => va.VaultAmountId == vaultAmountId);
if (vaultAmount == null && vaultAmount.Vault == null) return;
//Get FromVault and ToVault with amounts and updatedQuantities
List<VaultAmount> currentAmounts = GetCurrentVaultAmounts(vaultAmount.Vault.VaultId);
//Vault vault = GetVaultWithCurrentAmountsAndQuantitiesById(vaultAmount.Vault.VaultId);
var helper = new RemainderVAHelper(currentAmounts, userId, friendlyName, currencyId, updatedQuantities,
comment,
BS2VaultEventTypes.Correction);
//................................
foreach (var amount in currentAmounts)
{
if (amount.IsCurrent == false)
{
DbEntityEntry<VaultAmount> entityEntry = this.DbContext.Entry(amount);
entityEntry.State = EntityState.Modified;
}
}
this.DbContext.SaveChanges();
}
In RemainderVAHelper class I am changing currentAmounts objects, set IsCurrent property to false.
But currentAmounts objects entityEntry.State are still Unchanged. Why? Yes, I can set their states as Modified (as I am doing in method), but I think it is not very good thing. Can you tell me why my objects state doesn't changes to Modified?
If you are not using change tracking proxies EF has no way of knowing you modified a property until you call DetectChanges or call SaveChanges (which calls DetectChanges)
Related
I was trying to implement a function that will let a user like a comment. If the user has already liked it, it can't be liked again and vice versa.
This is what it looks like:
public async Task<ActionResult<CommentResponse>> LikeComment(LikeComment like)
{
if (like.HasNullProperty())
return BadRequest("Missing properties!");
var comment = await commentService.GetCommentWithLikes((int) like.CommentId);
if(comment is null)
return NotFound($"No comment with id {like.CommentId} was found");
try
{
var userId = User.GetUserID();
comment = await commentService.LikeComment(comment, userId, (bool)like.Liked);
return comment is not null ? Ok(comment.GetCommentResponse((bool)like.Liked)) : StatusCode(304);
}
catch(Exception e)
{
return StatusCode(500, $"Error while trying to {((bool)like.Liked ? "like" : "dislike")} comment");
}
}
Relevant functions:
public async Task<Comment> GetCommentWithLikes(int id) => await blogContext.Comments.IncludeLikes().FirstOrDefaultAsync(x => x.Id == id);
public static IQueryable<Comment> IncludeLikes(this IQueryable<Comment> source)
=> source.Select(x => new Comment
{
Id = x.Id,
ArticleId = x.ArticleId,
CreatedById = x.CreatedById,
CreatedAt = x.CreatedAt,
Likes = x.LikedBy.Count,
Text = x.Text,
});
And the main like logic:
public async Task<Comment> LikeComment(Comment comment, string userId, bool liked)
{
var user = new User { Id = userId };
var hasLiked = await blogContext.Comments.Where(x => x.Id == comment.Id && x.LikedBy.Any(x => x.Id == user.Id)).FirstOrDefaultAsync() is not null;
Action action = null;
if (!hasLiked && liked)
{
action = () => comment.LikedBy.Add(user);
comment.LikedBy = new List<User>();
comment.Likes++;
}
else if (hasLiked && !liked)
{
action = () => comment.LikedBy.Remove(user);
comment.LikedBy = new List<User> { user };
comment.Likes--;
}
if (action is null)
return null;
blogContext.Attach(user);
blogContext.Attach(comment);
action();
await blogContext.SaveChangesAsync();
return comment;
}
The idea was to not load the whole likedBy relation, but still notify EF Core that i have added or removed one user. Therefore i modify the Comment, then attach it so EF Core tracks the changes to the likedBy relation. Interestingly, it works fine when liking a comment. However, when disliking, i get an rrror that the comment is already attached. Using .AsNoTracking() in the GetCommentsWithLikes function didn't help.
The instance of entity type 'Comment' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values.
This is the comment passed to the like func when linking (works):
This is the one when disliking (only diff is the like count...):
And this is it right before the failing attach:
Maybe someone knows the reason for this behaviour and can help me or suggest a different approach :)
Thanks
Using .AsNoTracking() in the GetCommentsWithLikes function didn't help
Due to the used projection, that function is already implicitly no tracking. It is the following call
var hasLiked = await blogContext.Comments
.Where(x => x.Id == comment.Id && x.LikedBy.Any(x => x.Id == user.Id))
.FirstOrDefaultAsync() is not null;
which is adding a Comment instance to the change tracker when the result is not null.
Since you don't need that instance and are just checking for existence, use the following instead which doesn't involve entity instances, but pure server side query:
var hasLiked = await blogContext.Comments
.AnyAsync(x => x.Id == comment.Id && x.LikedBy.Any(x => x.Id == user.Id));
I'm trying to build a reusable soft sync (find deleted records but update them, not delete them) method. I'm using EFCore.BulkExtensions for the UPSERT, but retrieving the missing records is taking a ton of time. Since the method is generic, I do not know the keys ahead of time and am attempting to use reflection to get the key values and build a hash for comparison. The problem is, using reflection means I have to pull the full dataset into memory and filter based on that which is proving to take a very long time.
Below is what I currently have
public static async Task SoftSyncAsync<TContext, TEntity>(this TContext #this, IList<TEntity> entities, string deletedFlagName = null)
where TEntity : class
where TContext : DbContext
{
// this method just creates a transaction and processes the bulk upsert (await context.BulkInsertOrUpdateAsync(entities, _config))
await ProcessAsync(#this, entities, OperationType.Upsert);
// this gets the property to update if the record is deleted
var isDeletedProperty = deletedFlagName != null
? typeof(TEntity)
?.GetProperties()
?.FirstOrDefault(p => p.Name.Equals(deletedFlagName, StringComparison.InvariantCultureIgnoreCase))
: typeof(TEntity)
?.GetProperties()
?.FirstOrDefault(p => (p.PropertyType == typeof(bool) || p.PropertyType == typeof(bool?))
&& (p.Name.Equals("Deleted", StringComparison.InvariantCultureIgnoreCase)
|| p.Name.Equals("IsDeleted", StringComparison.InvariantCultureIgnoreCase)
|| p.Name.Equals("IsDeletedFlag", StringComparison.InvariantCultureIgnoreCase)));
if (isDeletedProperty != null)
{
var entityType = #this?.Model?.FindEntityType(typeof(TEntity));
// this gets the property in the key. Might be 1 but could be many
var keyProperties = entityType
?.FindPrimaryKey()
?.Properties
?.Select(p => p.PropertyInfo)
?.ToList();
// this creates a hash set for the entities know are in the table (Not deleted)
var entityHashes = entities
?.Select(e => string.Join("_", keyProperties.Select(kp => kp.GetValue(e).ToString())).GetHashCode())
?.ToHashSet();
// THIS IS THE SLOW PIECE. Creates the hashes for records in the table and compares against the set above
var leftOverEntities = #this
?.Set<TEntity>()
?.AsNoTracking()
?.ToList()
?.Where(e => (bool)isDeletedProperty.GetValue(e) != true && !entityHashes.Contains(string.Join("_", keyProperties.Select(kp => kp.GetValue(e).ToString())).GetHashCode()))
?.ToList();
// Updates the "deleted" records
if (leftOverEntities?.Any() == true)
{
leftOverEntities?.ForEach(e => isDeletedProperty.SetValue(e, true));
await ProcessAsync(#this, leftOverEntities, OperationType.Update);
}
}
}
I've read a few places about using expressions to perform reflection operations but I couldn't find how to do that with composite keys
I have a problem with Entity Framework using the DefaultIfEmpty method. The following query is returning empty when it should return an offer that matches all criteria in the database.
If I remove one or both DefaultIfEmpty method calls it works, but with them it doesn't. I need those to prevend another problem in the query.
When I execute the generated SQL query directly on the database it works and it returns the offer.
I also made an Unit Test reproducing the same example and it also passes so it must be an Entity Framework issue.
Here's the query:
private static Expression<Func<Offer, bool>> AddFilter(Service criteria)
{
return offer => offer.Restrictions.
SelectMany(rest => rest.OperatorRange.DefaultIfEmpty(), (rest, alop) => new { Restriction = rest, OperatorRange = alop.Id }).
Where(alop => criteria.ServiceUseNet == null || alop.OperatorRange.ToUpper() == criteria.ServiceUseNet.ToUpper()).
SelectMany(rest => rest.Restriction.CallType.DefaultIfEmpty(), (rest, till) => new { Restriction = rest, CallType = till.Id }).
Any(till => criteria.UseServiceCoverage == null || till.CallType.ToUpper() == criteria.UseServiceCoverage.ToUpper());
}
Change it into two Any calls:
return offer => offer.Restrictions
.Any(rest
=> rest.OperatorRange
.Where(alop => criteria.ServiceUseNet == null
|| alop.OperatorRange.ToUpper() == criteria.ServiceUseNet.ToUpper())
.Any(till => criteria.UseServiceCoverage == null
|| till.CallType.ToUpper() == criteria.UseServiceCoverage.ToUpper()));
The predicate is supposed to test whether there are any OperatorRanges (meeting some criteria) having any CallTypes meeting some criteria. If there are no OperatorRanges, there won't be any CallTypes either, let alone matching CallTypes.
In this form, the predicate always returns true or false.
I've been a developer (also in a professional capacity) for a little while now and really never focused on clean / well structured code. As completely self taught I guess I'm missing some of the fundamentals. Reading books never fills the gaps. So I hope to get some great experience from this post.
So to the point, I have a method that returns an object (Campaign) based on conditional logic.
If I can get the object via the "CampaignViewMode" then it must have been "viewed" so therefore GET
ELSE Get last inserted
1, Get if it has been recently viewed (Cookie)
2, Else just get the last Inserted.
Pretty basic but the code has a serious "code smell" (repetition). In an ideal world I'd like to remove the conditional.
public Campaign GetDefaultCampaign()
{
Campaign campaign = null;
using (UserRepository userRepo = new UserRepository())
{
var user = userRepo.GetLoggedInUser();
if (user != null)
{
string campaignViewMode = "";
if (HttpContext.Current.Request.Cookies["CampaignViewMode"] != null)
{
campaignViewMode = HttpContext.Current.Request.Cookies["CampaignViewMode"].Value.ToString();
}
//Get Last worked on/viewed
campaign = _context.tbl_Campaign
.Where(x => x.Name == campaignViewMode & x.tbl_UserToCampaign
.Where(z => z.UserId == user.UserId & z.CampaignId == x.CampaignId)
.Select(u => u.UserId)
.FirstOrDefault() == user.UserId)
.Select(y => new Campaign()
{
CampaignId = y.CampaignId,
Name = y.Name,
WebName = y.WebName,
DateAdded = y.DateAdded
}).FirstOrDefault();
//Or get last inserted
if (campaign == null)
{
campaign = _context.tbl_Campaign
.Where(x => x.Name == campaignViewMode & x.tbl_UserToCampaign
.Where(z => z.UserId == user.UserId & z.CampaignId == x.CampaignId)
.Select(u => u.UserId)
.OrderByDescending(d => d.DateAdded).FirstOrDefault() == user.UserId)
.Select(y => new Campaign()
{
CampaignId = y.CampaignId,
Name = y.Name,
WebName = y.WebName,
DateAdded = y.DateAdded
}).FirstOrDefault();
}
}
}
return campaign;
}
Could you kindly point me in the right direction of removing the conditional or at the very last reduce the "smell" ?
Fully appreciate your time!
Regards,
There's alot going on here. Here's what I'd do.
Don't new up instances (as you do with Repository). Code against abstracts (IRepository) which is provided by a DI container which is injected into the class constructor.
Remove the duplication that maps your data model to your returned model (Select(x=> new Campaign()). Extract this out as a method or a separate responsibility entirely.
Remove the huge nested if(user!=null). Check for this right up front and return if it is null.
Refactor the two fetching operations behind an interface (IGetCampaigns) and create two classes; one that fetches the latest inserted, and one that fetches the last viewed/worked on. Inject one into the other to form a decorator chain, wired up by your DI container.
Probably a lot to take in if you're unfamiliar with these concepts; happy to go through this offline if you'd like.
I'm using EF4.3 with DbContext.
I have an entity that I store in cache, so I need to eager load the necessary data before converting to a list and popping it in cache.
My database is normalised so data is spread over several tables. The base entity is "User", a User may or may not be a "Subscriber" and a Subscriber can be one of 3 types "Contributor", "Member" or "Administrator"
At present the whole fetch is not very elegant due to my lack of knowledge in EF, Linq et al.
public static User Get(Guid userId)
{
Guard.ThrowIfDefault(userId, "userId");
var r = new CrudRepo<User>(Local.Items.Uow.Context);
var u = r.FindBy(x => x.UserId == userId)
.Include("BookmarkedDeals")
.Include("BookmarkedStores")
.SingleOrDefault();
if (u.IsNotNull() && u.IsActive)
{
if (u.IsAdmin)
{
u.GetAdministrator();
}
else if (u.IsContributor)
{
u.GetContributor();
}
else if (u.IsMember)
{
u.GetMember();
}
else
{
string.Format("Case {0} not implemented", u.UserRoleId)
.Throw<NotImplementedException>();
}
}
return u;
}
Each of the 'Get' methods gets a Subscriber entity plus the relevant Include() entities for the role type.
I'm pretty sure it can be done a whole lot more elegently than this but struggling with the initial thought process.
Anyone help?
UPDATED with example of one of the Get methods
public static void GetMember(this User user)
{
Guard.ThrowIfNull(user, "user");
var r = new ReadRepo<Subscriber>(Local.Items.Uow.Context);
user.Subscriber = r.FindBy(x => x.UserId == user.UserId)
.Include("Kudos")
.Include("Member.DrawEntries")
.Include("Member.FavouriteCategories")
.Include("Member.FavouriteStores")
.Single();
}
If your "User" entity is connected to your other entities you can use the Load method of the connected entity collection to get the related entities. For example if your "User" entity has a property "Subscriber" you could call u.Subscriber.Load() to get the related entity. Here is the related MSDN article
var u = r.FindBy(x => x.UserId == userId)
.Include("BookmarkedDeals")
.Include("BookmarkedStores")
.SingleOrDefault();
if(someCondition)
{
u = u.Include("something");
}
Don't have a place to test this, but have you tried that?