I have Entity Framework code which is supposed to be updating an entity called Asset. For some reason, it's not updating as it should. Debugging to see the root cause of this issue is difficult as I don't see any exceptions.
The problem is in the repository class where it always returns an
Asset could not be updated
message even if I pass in a valid Book or Asset object.
Model classes
public class Asset
{
public Guid Id { get; set; }
public string Name { get; set; }
}
public class Book : Asset
{
public string Isbn { get; set; }
public string Edition { get; set; }
public string Publisher { get; set; }
public virtual User User { get; set; }
}
Controller class
[System.Web.Http.Authorize]
public async Task<IHttpActionResult> Put([FromBody] BookViewModel model)
{
string result = null;
try
{
if (model.IsEmpty)
return BadRequest(ModelState);
result = await _assetAssetRepository.Update(Mapper.Map<Book>(model));
return Content(HttpStatusCode.Created, result);
}
catch (Exception ex)
{
return Content(HttpStatusCode.InternalServerError, result);
}
}
Repository class:
public class AssetRepository
{
private readonly GenAppContext _context = new GenAppContext();
public Task<string> Update(Asset asset)
{
try
{
var updateTask = Task<string>.Factory.StartNew(() =>
{
if (!(asset is Book))
return "Please return correct type of asset";
var _asset = _context.Assets.SingleOrDefault(x => x.Id == asset.Id);
if (_asset == null)
throw new ArgumentNullException(nameof(_asset));
_asset.Name = asset.Name;
((Book) _asset).Edition = ((Book) asset).Edition;
((Book) _asset).Publisher = ((Book) asset).Publisher;
((Book) _asset).Isbn = ((Book) asset).Isbn;
_context.Assets.AddOrUpdate(_asset);
return _context.SaveChanges() > 0
? "Asset has been updated successfully"
: "Asset could not be updated";
});
return updateTask;
}
catch (Exception ex)
{
return Task<string>.Factory.StartNew(() => ex.Message + " " + ex.StackTrace);
}
}
}
Related
According to Microsoft
the database context lifetime should be very short and any instantiated
objects should be disabled and removed from the ram as soon as they are no longer needed.
While I was developing an POS WPF application ,which based on caliburn.Micro framework and IOC container was implemented, I separated the project as following:
the entities classes & the context were putted in separated Class
Library project.
I implemented the repository and unit of work
design patterns in the main wpf project and i injected the context in
the unit of work class constructor.
I registered the context and the
unit of work into the ioc container "Autofac Container" with
InstancePerDependency life time for both of them which means a new instance will be created whenever an object of each on of them is
requested.
Finally ,i injected the unit of work interface into the ViewModel classes so i can load the proper entities for each VM in its constructor.
and the problems came here.
When I was trying to add a Product' Category and then I navigate to the Products VM to add a new product under this category ,but the VM didn't recognize that I had added new category.
when I traced the code I found that it happens with any VM. The VMs weren't feeling the changes except when I restart the program. I tried to fix it in many ways but no thing worked with me ,wo i decided to change the lifetime of the context to be "SingleInstance" which means one instance of the context will be created for whole the project.
I know it's a bad practice but there weren't any other choices.
The Questions are:
why did it happen?
what was the proper solution at this situation ?
does it also happen in the other workloads like mvc ,web apis or the Scoped lifetime handles it in the dot net core services container ?
was my separation structure correct and does it work fine for other projects or not?
Update:
This is my product entity:
public class Product
{
public Product()
{
ProductBarcodes = new HashSet<ProductBarcode>();
InventoriesProducts = new HashSet<InventoriesProducts>();
ProductUnits = new HashSet<ProductUnits>();
ProductsVariants = new HashSet<ProductsVariants>();
}
[Key]
public string Productkey { get; set; }
[Required,MaxLength(150), MinLength(3)]
public string ProductName { get; set; }
[MaxLength(500)]
public string Description { get; set; }
public string ImagePath { get; set; }
public decimal? SalesPrice { get; set; }
public decimal? Cost { get; set; }
public bool CanBeSold { get; set; }
public bool CanBePurchased { get; set; }
public ProductType ProductType { get; set; }
public long CategoryId { get; set; }
public virtual ProductCategory Category { get; set; }
[InverseProperty(nameof(ProductBarcode.Product))]
public virtual ICollection<ProductBarcode> ProductBarcodes { get; set; }
public virtual ICollection<InventoriesProducts> InventoriesProducts { get; set; }
public virtual ICollection<ProductUnits> ProductUnits { get; set; }
public virtual ICollection<ProductsVariants> ProductsVariants { get; set; }
}
This is my Category entity :
public class ProductCategory
{
[Key]
public long CategoryId { get; set; }
[Required, MaxLength(150), MinLength(3)]
public string Title { get; set; }
public IList<ProductCategory> SubCategories { get; set; }
public ProductCategory Parent { get; set; }
public IList<Product> Products { get; set; }
}
This project supports english and arabic languages so,i implemented the results messages as following:
I added the ITransactionResult generic interface that describe how any transaction result should be and it's generic to give me the ability to pass the entity type that i'm going to give transaction results about.
The interface has a UserMessageResource property of type Type this property usually was setted to Properties.Resources to search for a registered resource which contains the arabic or english message that will be displayed after the transaction.
Then i added two classes to implement this interface to describe two cases , when the Transaction ends successfully which was presented in SucessTransaction class and other wises were presented in FailedTransaction class.
ITransactionResult :
public interface ITransactionResult<T> where T : class
{
Exception ExceptionHappend { get; }
string UserMessage { get; }
string SourceType { get; }
bool Success { get; }
Type UserMessageResource { get; }
string UserMessageName { get; }
string[] Notes { get; }
}
SucessTransaction:
public class SuccessTransaction<T> : ITransactionResult<T> where T : class
{
public SuccessTransaction(string message,params string[] notes)
{
Success = true;
ExceptionHappend = null;
Notes = notes;
UserMessage = message;
}
public SuccessTransaction(Type UserMessageResource,string UserMessageName, params string[] notes):this(string.Empty,notes)
{
this.UserMessageResource = UserMessageResource;
this.UserMessageName = UserMessageName;
UserMessage = UserMessageResource.GetProperty(UserMessageName).GetValue(UserMessageResource).ToString();
}
public Exception ExceptionHappend { get; }
public string UserMessage { get; }
public bool Success {get;}
public string SourceType { get => nameof(T); }
public Type UserMessageResource { get; }
public string UserMessageName { get; }
public string[] Notes { get; }
}
FailedTransaction :
public class FailedTransaction<T> : ITransactionResult<T> where T : class
{
public FailedTransaction(string message,Exception exception,params string[] notes)
{
Success = false;
UserMessage = message;
ExceptionHappend = exception;
Notes = notes;
}
public FailedTransaction(Type UserMessageResource, string UserMessageName,Exception exception, params string[] notes)
:this(string.Empty,exception,notes)
{
this.UserMessageResource = UserMessageResource;
this.UserMessageName = UserMessageName;
UserMessage = UserMessageResource.GetProperty(UserMessageName).GetValue(UserMessageResource).ToString();
}
public Exception ExceptionHappend { get; }
public string UserMessage { get; }
public bool Success { get; }
public string SourceType { get => nameof(T); }
public Type UserMessageResource { get; }
public string UserMessageName { get; }
public string[] Notes { get; }
}
My Product repo :
public class ProductRepo : IProductRepo
{
private DbContext dbContext;
public Type DisplayResourceType { get; set; }
public ProductRepo(DbContext dbContext)
{
this.dbContext = dbContext;
}
public Product Add(Product entity, out ITransactionResult<Product> transactionResult)
{
try
{
var res = dbContext.Add(entity);
transactionResult = new SuccessTransaction<Product>(DisplayResourceType, "Adding_S");
return res.Entity;
}
catch (Exception ex)
{
transactionResult = new FailedTransaction<Product>(DisplayResourceType, "Adding_F", ex);
return entity;
}
}
public IEnumerable<Product> AddRange(IEnumerable<Product> entites, out ITransactionResult<Product> transactionResult)
{
try
{
dbContext.AddRange(entites);
transactionResult = new SuccessTransaction<Product>(DisplayResourceType, "AddingRang_S");
return entites;
}
catch (Exception ex)
{
transactionResult = new FailedTransaction<Product>(DisplayResourceType, "AddingRange_F", ex);
return entites;
}
}
public IEnumerable<Product> GetAll() => GetAllAsQueryable().ToList();
public IQueryable<Product> GetAllAsQueryable() => dbContext.Set<Product>();
public Product GetById(string id) => dbContext.Find<Product>(id);
public bool IsExist(string id, out Product entity)
{
entity = GetById(id);
return entity != null ? true : false;
}
public void Remove(Product entity, out ITransactionResult<Product> transactionResult)
{
try
{
var res = dbContext.Remove(entity);
transactionResult = new SuccessTransaction<Product>(DisplayResourceType, "Removing_S");
}
catch (Exception ex)
{
transactionResult = new FailedTransaction<Product>(DisplayResourceType, "Removing_F", ex);
}
}
public void RemoveRange(IEnumerable<Product> entities, out ITransactionResult<Product> transactionResult)
{
try
{
dbContext.AddRange(entities);
transactionResult = new SuccessTransaction<Product>(DisplayResourceType, "RemovingRange_S");
}
catch (Exception ex)
{
transactionResult = new FailedTransaction<Product>(DisplayResourceType, "RemovingRange_F", ex);
}
}
public Product Update(Product entity, out ITransactionResult<Product> transactionResult)
{
try
{
var res = dbContext.Update(entity);
transactionResult = new SuccessTransaction<Product>(DisplayResourceType, "Updating_S");
return res.Entity;
}
catch (Exception ex)
{
transactionResult = new FailedTransaction<Product>(DisplayResourceType, "Updating_F", ex);
return entity;
}
}
public IEnumerable<Product> UpdateRange(IEnumerable<Product> entities, out ITransactionResult<Product> transactionResult)
{
try
{
dbContext.UpdateRange(entities);
transactionResult = new SuccessTransaction<Product>(DisplayResourceType, "UpdatingRange_S");
return entities;
}
catch (Exception ex)
{
transactionResult = new FailedTransaction<Product>(DisplayResourceType, "UpdatingRange_F", ex);
return entities;
}
}
}
Category repo :
public class CategoryRepo : ICategoryRepo
{
private DbContext dbContext;
public Type DisplayResourceType { get; set; }
public CategoryRepo(DbContext dbContext)
{
this.dbContext = dbContext;
}
public IEnumerable<ProductCategory> GetAll() => GetAllAsQueryable().ToList();
public IQueryable<ProductCategory> GetAllAsQueryable() => dbContext.Set<ProductCategory>();
public ProductCategory GetById(long id) => dbContext.Find<ProductCategory>(id);
public ProductCategory Add(ProductCategory entity, out ITransactionResult<ProductCategory> transactionResult)
{
try
{
var res = dbContext.Add(entity);
transactionResult = new SuccessTransaction<ProductCategory>(DisplayResourceType, "Adding_S");
return res.Entity;
}
catch (Exception ex)
{
transactionResult = new FailedTransaction<ProductCategory>(DisplayResourceType, "Adding_F", ex);
return entity;
}
}
public IEnumerable<ProductCategory> AddRange(IEnumerable<ProductCategory> entites, out ITransactionResult<ProductCategory> transactionResult)
{
try
{
dbContext.AddRange(entites);
transactionResult = new SuccessTransaction<ProductCategory>(DisplayResourceType, "AddingRang_S");
return entites;
}
catch (Exception ex)
{
transactionResult = new FailedTransaction<ProductCategory>(DisplayResourceType, "AddingRange_F", ex);
return entites;
}
}
public ProductCategory Update(ProductCategory entity, out ITransactionResult<ProductCategory> transactionResult)
{
try
{
var res = dbContext.Update(entity);
transactionResult = new SuccessTransaction<ProductCategory>(DisplayResourceType, "Updating_S");
return res.Entity;
}
catch (Exception ex)
{
transactionResult = new FailedTransaction<ProductCategory>(DisplayResourceType, "Updating_F", ex);
return entity;
}
}
public IEnumerable<ProductCategory> UpdateRange(IEnumerable<ProductCategory> entities, out ITransactionResult<ProductCategory> transactionResult)
{
try
{
dbContext.UpdateRange(entities);
transactionResult = new SuccessTransaction<ProductCategory>(DisplayResourceType, "UpdatingRange_S");
return entities;
}
catch (Exception ex)
{
transactionResult = new FailedTransaction<ProductCategory>(DisplayResourceType, "UpdatingRange_F", ex);
return entities;
}
}
public void Remove(ProductCategory entity, out ITransactionResult<ProductCategory> transactionResult)
{
try
{
var res = dbContext.Remove(entity);
transactionResult = new SuccessTransaction<ProductCategory>(DisplayResourceType, "Removing_S");
}
catch (Exception ex)
{
transactionResult = new FailedTransaction<ProductCategory>(DisplayResourceType, "Removing_F", ex);
}
}
public void RemoveRange(IEnumerable<ProductCategory> entities, out ITransactionResult<ProductCategory> transactionResult)
{
try
{
dbContext.AddRange(entities);
transactionResult = new SuccessTransaction<ProductCategory>(DisplayResourceType, "RemovingRange_S");
}
catch (Exception ex)
{
transactionResult = new FailedTransaction<ProductCategory>(DisplayResourceType, "RemovingRange_F", ex);
}
}
public bool IsExist(long id, out ProductCategory entity)
{
entity = GetById(id);
return entity != null ? true : false;
}
}
I have a simple project (ABP version: 3.1.2, Database: EF Core).
I run GetAsync:
var author = await _authorRepository.GetAsync(id, includeDetails: true);
But author.Films was not included. What may I have forgotten?
Author (AggregateRoot):
public class Author : FullAuditedAggregateRoot<Guid>
{
public string Name { get; private set; }
public DateTime BirthDate { get; set; }
public string ShortBio { get; set; }
public List<Film> Films { get; set; }
private Author()
{
Films = new List<Film>();
/* This constructor is for deserialization / ORM purpose */
}
internal Author(
Guid id,
[NotNull] string name,
DateTime birthDate,
[CanBeNull] string shortBio = null)
: base(id)
{
Name = name;
BirthDate = birthDate;
ShortBio = shortBio;
Films = new List<Film>();
}
}
Film (Entity):
public class Film : Entity<Guid>
{
public virtual Guid AuthorId { get; internal set; }
public string Name { get; set; }
}
SeedAsync in DataSeeder class (I checked whether data exists in database after DbMigrator ran, there are these data in tables as expected):
public async Task SeedAsync(DataSeedContext context)
{
if (await _authorRepository.GetCountAsync() == 0)
{
var authorId = _guidGenerator.Create();
await _authorRepository.InsertAsync(
new Author(authorId, "J. R. R. Tolkien", DateTime.Now.AddYears(-60), "bio1"),
autoSave: true
);
await _filmRepository.InsertAsync(
new Film { AuthorId = authorId, Name = "The Return of the King1" },
autoSave: true);
await _filmRepository.InsertAsync(
new Film { AuthorId = authorId, Name = "The Return of the King2" },
autoSave: true);
await _filmRepository.InsertAsync(
new Film { AuthorId = authorId, Name = "The Return of the King3" },
autoSave: true);
}
}
AuthorAppService:
public class AuthorAppService : BookStoreAppService, IAuthorAppService
{
private readonly IAuthorRepository _authorRepository;
private readonly AuthorManager _authorManager;
public AuthorAppService(
IAuthorRepository authorRepository,
AuthorManager authorManager)
{
_authorRepository = authorRepository;
_authorManager = authorManager;
}
public async Task<AuthorDto> GetAsync(Guid id)
{
var author = await _authorRepository.GetAsync(id, includeDetails: true);
return ObjectMapper.Map<Author, AuthorDto>(author);
}
}
From https://docs.abp.io/en/abp/latest/Best-Practices/Entity-Framework-Core-Integration:
Do create a IncludeDetails extension method for the IQueryable<TEntity> for each aggregate root which has sub collections.
...
Do override WithDetails method of the repository for aggregates root which have sub collections.
public static class AuthorEfCoreQueryableExtensions
{
public static IQueryable<Author> IncludeDetails(this IQueryable<Author> queryable, bool include = true)
{
if (!include)
{
return queryable;
}
return queryable
.Include(x => x.Films);
}
}
public class AuthorRepository : EfCoreRepository<IMyDbContext, Author, Guid>, IAuthorRepository
{
...
public override IQueryable<Author> WithDetails()
{
return GetQueryable().IncludeDetails(); // Uses the extension method defined above
}
}
i need to add some data in OptionRoleTable:
public class OptionRole
{
public int Id { get; set; }
public int RoleId { get; set; }
public int OptionsId { get; set; }
public virtual Role Role { get; set; }
public virtual Options Options { get; set; }
}
and this is Options Tabel:
public partial class Options
{
public int Id { get; set; }
public string OptionName { get; set; }
public string RouteFunctionName { get; set; }
public string Icon { get; set; }
public virtual ICollection<OptionRole> OptionRoles { get; set; }
}
i must check data not exist in OptionRole , when i using this code for add data in OptionRole :
public async Task<Options> findOptionsId(int optionId)
{
return await _option.FirstOrDefaultAsync(x => x.Id == optionId);
}
public async Task<bool> AddorUpdateOptions(int optionId, IList<int> selectedRoleValue)
{
List<OptionVM> optionVMs = new List<OptionVM>();
List<int> currentOptionValue = new List<int>();
var optionRole = await findOptionsId(optionId);
if (optionRole == null)
{
return false;
}
foreach (var item in selectedRoleValue)
{
var findRole = await _roleManager.FindByIdAsync(item);
var findOPR = optionRole.OptionRoles.FirstOrDefault(x => x.OptionsId== optionId && x.RoleId==item);
if (findOPR != null)
{
currentOptionValue.Add(item);
}
}
if (selectedRoleValue == null)
{
selectedRoleValue = new List<int>();
}
var newOptionRole = selectedRoleValue.Except(currentOptionValue).ToList();
foreach (var opRole in newOptionRole)
{
var findRole = await _roleManager.FindByIdAsync(opRole);
if (findRole != null)
{
optionRole.OptionRoles.Add(new OptionRole
{
OptionsId = optionRole.Id,
RoleId = findRole.Id
});
}
}
var removeOptionRole = currentOptionValue.Except(selectedRoleValue).ToList();
foreach (var remove in removeOptionRole)
{
var findOptionRole = _optionRoles.FirstOrDefault(x => x.Id == remove);
if (findOptionRole != null)
{
optionRole.OptionRoles.Remove(findOptionRole);
}
}
return Update(optionRole.OptionRoles);
}
I must have pass a class type of Options when i using this code . it show me this Error :
Severity Code Description Project File Line Suppression State
Error CS1503 Argument 1: cannot convert from 'System.Collections.Generic.ICollection' to 'StoreFinal.Entities.Entities.Identity.OptionRole' StoreFinal.Services C:\Users\Mr-Programer\Desktop\New folder\StoreFinal\StoreFinal.Services\Contracts\Identity\Service\ApplicationOptionRoleManager.cs 97 Active
Error in this line : return Update(optionRole.OptionRoles);
whats the problem ? how can i solve this problem ?
Edit :
Update Method :
public virtual bool Update(T entity)
{
try
{
Entities.Attach(entity);
return true;
}
catch (Exception)
{
return false;
}
}
Look at the Update Method signature:
public virtual bool Update(T entity);
It accepts a param type T which should be One Entity - Why One Entity -- because Entities.Attach() accepts only 1 Object. While what you are passing to it is:
return Update(optionRole.OptionRoles);
Where OptionRoles is of type: ICollection<OptionRole> --
For understandings sake, Change it to
return Update(optionRole.OptionRoles[0]);
or
return Update(optionRole.OptionRoles.First());
And then share the result.
i want insert object to sql server data base, i using EF 6.x. But when i execute Create method, this success and not show any error but in a database is empty :/.
public class ProductoEntity
{
public int ID { get; set; }
public string Descripcion { get; set; }
public string Categoria { get; set; }
public int Precio { get; set; }
public Producto toSql()
{
var sqlEntity = new Producto();
sqlEntity.Descripcion = this.Descripcion;
sqlEntity.Categoria = this.Categoria;
sqlEntity.Precio = this.Precio;
return sqlEntity;
}
public bool Create()
{
bool result = false;
try
{
StoreEntities context = new StoreEntities();
context.Producto.Add(toSql()); ;
result = true;
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
return result;
}
}
You need to call SaveChanges
StoreEntities context = new StoreEntities();
context.Producto.Add(toSql());
context.SaveChanges();
I have some test code asserting duplicate Users cannot be created through my UserRepository.
User.cs:
public class User
{
public int Id { get; set; }
public string AccountAlias { get; set; }
public string DisplayName { get; set; }
public string Email { get; set; }
public bool IsActive { get; set; }
}
UserRepository.cs:
public class UserRepository
{
public virtual async Task<User> CreateAsync(User entity)
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
if (await GetDuplicateAsync(entity) != null)
{
throw new InvalidOperationException("This user already exists");
}
return Create(entity);
}
public async Task<User> GetDuplicateAsync(User user)
{
if (user == null)
{
throw new ArgumentNullException("user");
}
return await (from u in Users
where u.AccountAlias == user.AccountAlias &&
u.Id != user.Id &&
u.IsActive
select u).FirstOrDefaultAsync();
}
}
UserRepositoryTests.cs:
public sealed class UserRepositoryTests : IDisposable
{
public UserRepositoryTests()
{
UserRepository = new UserRepository(new FooEntities()); // DbContext
// from EF
}
private UserRepository UserRepository { get; set; }
[Fact]
public void DuplicateUserCannotBeCreated()
{
var testUser = new User // This test user already exists in database
{
Id = 0,
AccountAlias = "domain\\foo",
DisplayName = "Foo",
Email = "foo#bar.com",
IsActive = true
};
Assert.Throws<InvalidOperationException>(async () =>
await UserRepository.CreateAsync(testUser));
}
public void Dispose()
{
if (UserRepository != null)
{
UserRepository.Dispose();
}
}
}
When I run this unit test, Xunit.Sdk.ThrowsException is thrown (i.e. my InvalidOperationException was not thrown):
Assert.Throws() Failure
Expected: System.InvalidOperationException
Actual: (No exception was thrown)
From the debugger, GetDuplicateAsync() was evaluated but when the LINQ query was executed, the result was never returned and thus no exception was thrown. Can anyone help?
xUnit's Assert.Throws (at least on version 1.9.2) is not async-aware. This was fixed in version 2, which now has an Assert.ThrowsAsync method.
So, you can either upgrade to xUnit 2 or create your own method to get it working:
public async static Task<T> ThrowsAsync<T>(Func<Task> testCode) where T : Exception
{
try
{
await testCode();
Assert.Throws<T>(() => { }); // Use xUnit's default behavior.
}
catch (T exception)
{
return exception;
}
return null;
}
await ThrowsAsync<InvalidOperationException>(async () => await UserRepository.CreateAsync(testUser));
From Haacked's gist.
XUnit now handle Assert.ThrowAsync by default
This works for me:
Assert.Throws<AbpValidationException>(() => _personAppService.CreatePersonAsync(new CreatePersonInput { Name = null }));
Just don't use async/await.