Unit-testing EF6 non-query operations - entity-framework

I'm using Entity Framework 6 and I have a class with a method that adds some records to the database:
interface IRecordsContext
{
DbSet<MyRecord> MyRecords { get; }
int SaveChanges();
}
class MyService
{
public MyService(IRecordsContext ctx)
{
_context = ctx;
}
private readonly IRecordsContext _context;
public void AddRecords(int count)
{
_context.MyRecords.AddRange(
from id in Enumerable.Range(1, count)
select new MyRecord { Value = id }
);
_context.SaveChanges();
}
}
Now I am using Moq library to create unit tests:
void AddRecords_ShouldAddThemToDatabase()
{
var contextMock = new Mock<IRecordsContext>();
// ...
}
How can I write a test that ensures that the Records collection now contains 10 extra records using mocks, without modifying any actual database?
I'm also eager to listen to opinions on whether this architecture is un-testable and how it should be refactored.

Have one variable that is set to .Count() of the DbSet before the method is run, and check that against the value of .Count() afterwards.

Related

How can update a column by itself on db Level with Entity Framework and Unit of Work?

I'm using Entity Framework and Unit of Work.
I have a decimal column OrderBalance in the Person table and I have an Order table. I want to update orderbalance column by itself at the db level to support concurrent order creations.
I want to insert an order and update OrderBalance column with atomocity (all or nothing).
public override void Create(Order order)
{
_orderReposiory.Add(order);
var person = _personRepository.GetById(order.PersonId);
person.OrderBalance += order.Amount*order.Price;
_personRepository.Edit(person);
_unitOfWork.Commit();
}
As you can see, '+=' process is on object level. How can I do this on db level without breaking atomicity?
I'm using ExeceutSqlCommand with transactionscope and it's work.
public class PersonRepository : GenericRepository<Person>, IPersonRepository
{
public void UpdateOrderBalance(decimal amount,long personId)
{
Entities.Database.ExecuteSqlCommand("Update Person set OrderBalance=OrderBalance+#p0 where id=#p1", amount,personId);
}
}
I have changed my Create Method to this
public override void Create(Order order)
{
using (var scope = new System.Transactions.TransactionScope())
{
_orderReposiory.Add(order);
AddOrderBalancePerson(order);
_unitOfWork.Commit();
scope.Complete();
}
}
private void AddOrderBalancePerson(Order order)
{
_personRepository.UpdateOrderBalance(order.Amount*order.Price, order.PersonId);
}
Entities in PersonRepository and UnitofWork are using same Dbcontext
You need to use the same DbContext instance in both repositories. If you do then you can wrap any number of inserts/updates in a transaction which will give you all or nothing. EF statements will automatically enlist in any transaction pending.
using (var tran = dbContext.Database.BeginTransaction())
{
// your updates here
}

How can I return the ID of the Inserted Record in Entity Framework Generic Insert Method?

Here is the generic insert method. I need your suggestion to return the ID of the inserted record.
public static void Create<T>(T entity) where T : class
{
using (var context = new InformasoftEntities())
{
DbSet dbSet = context.Set<T>();
dbSet.Add(entity);
context.SaveChanges();
}
}
Arturo Martinex is correct in his comment.
Entity framework fixes up the ID's during SaveChanges so it's already updated in the entity you passed in to the method.
To do specifically what you ask you could change your generic constraint from class to a new abstract class that all your entities inherit, which defines the key in that class.
public static int Create<T>(T entity) where T : BaseEntity
{
using (var context = new InformasoftEntities())
{
DbSet dbSet = context.Set<T>();
dbSet.Add(entity);
context.SaveChanges();
return entity.Id;
}
}
public abstract class BaseEntity
{
int Id { get; set;}
}
This technique is more useful in an InsertOrUpdate method
Another way to work with keys inside generic methods is to interrogate the MetaData as described here:
The key to AddOrUpdate
You need a little modification:
You need to create an IHasAutoID that implemented by Entity
public interface IHasAutoID {
int getAutoId();
}
In Entity Class
public class EntityA : IHasAutoID {
public int getAutoId() {
return pk; // Return -1 If the entity has NO Auto ID
}
}
In your Create function
public static int Create<T>(T entity) where T : class
{
using (var context = new InformasoftEntities())
{
DbSet dbSet = context.Set<T>();
dbSet.Add(entity);
context.SaveChanges();
if (entity is IHasAutoID) {
return ((IHasAutoID)entity).getAutoId();
}
return -1; // entity is NOT IHasAutoID)
}
}
NOTES:
If you are sure all tables have Auto ID with named "Id". You don't need to create Interface IHasAutoID. In Create function, after SaveChanges, You use REFLECTION to get value of Id property, but this way is not recommended!
public async Task<int> Add(TEntity entity)
{
await _context.Set<TEntity>().AddAsync(entity);
await Save();
return Task.FromResult(entity).Id;
}

How can I use a stored procedure + repository + unit of work patterns in Entity Framework?

I have MVC web application project with Entity Framework code first. In this project I am going to use generic repository and unit of work patterns. Plus I want to use stored procedures for get list by and get-list methods.
How can I use stored procedures with generic repository and unit of work patterns?
To your generic repository add
public IEnumerable<T> ExecWithStoreProcedure(string query, params object[] parameters)
{
return _context.Database.SqlQuery<T>(query, parameters);
}
And then you can call it with any unitofwork/repository like
IEnumerable<Products> products =
_unitOfWork.ProductRepository.ExecWithStoreProcedure(
"spGetProducts #bigCategoryId",
new SqlParameter("bigCategoryId", SqlDbType.BigInt) { Value = categoryId }
);
You shouldn't be trying to use SPs with UoW/Repository pattern, because they are hard to control in code and often don't map back to the same entity type. UoW and Repository pattern are better suited to using ADO.NET directly and not Entity Framework, as EF is already a Repository pattern. I would suggest CQRS as a better pattern when using SPs. Elaborating on the answer by #sunil and my comment on it, I created a class specifically for handling stored procedures. It's easy to mock and test, too.
public class ProcedureManager : IProcedureManager
{
internal DbContext Context;
public ProcedureManager(DbContext context)
{
Context = context;
}
//When you expect a model back (async)
public async Task<IList<T>> ExecWithStoreProcedureAsync<T>(string query, params object[] parameters)
{
return await Context.Database.SqlQuery<T>(query, parameters).ToListAsync();
}
//When you expect a model back
public IEnumerable<T> ExecWithStoreProcedure<T>(string query)
{
return Context.Database.SqlQuery<T>(query);
}
// Fire and forget (async)
public async Task ExecuteWithStoreProcedureAsync(string query, params object[] parameters)
{
await Context.Database.ExecuteSqlCommandAsync(query, parameters);
}
// Fire and forget
public void ExecuteWithStoreProcedure(string query, params object[] parameters)
{
Context.Database.ExecuteSqlCommand(query, parameters);
}
}
For Generic Repository Add this :
public IEnumerable<TEntity> GetdataFromSqlcommand(string command, System.Data.SqlClient.SqlParameter[] parameter)
{
StringBuilder strBuilder = new StringBuilder();
strBuilder.Append($"EXECUTE {command}");
strBuilder.Append(string.Join(",", parameter.ToList().Select(s => $" #{s.ParameterName}")));
return Context.Set<TEntity>().FromSql(strBuilder.ToString(), parameter);
}
And you just need to send Stored Procedure name and the array of parameters :
public IEnumerable<MainData> GetMainData(Param query)
{
var param1 = new SqlParameter("param1", query.param1);
var param2 = new SqlParameter("param2", query.param2);
return GetdataFromSqlcommand("StoredProcedurename", parameter: new[] { param1, param2 }).ToList();
}
If you are using .net core 3.1, you have to make work around
You will create a class that will carry result of stored procedure
You will create another partial class from DBcontext and put inside it the previous class
You will create IStoredProcedure interface and implement it in stored procedure using generic
Inject your stored procedure class in startup class
Don't forget to make your result class fields, same as result form stored procedure
Execute the stored procedure
Implementation:
(1) first step
public class TaskPercents
{
public long Id { get; set; }
public long SchoolRepId { get; set; }
public string Name { get; set; }
}
(2) second step
public partial class SchoolsPartnershipDBContext : DbContext
{
public virtual DbSet<TaskPercents> TaskPercents { get; set; }
}
(3) third step
public interface IStoredProcedure<T>
{
public List<T> ExecuteStored(string query);
}
{
private SchoolsPartnershipDBContext _context;
public StoredProcedure(SchoolsPartnershipDBContext Context)
{
_context = Context;
}
public List<T> ExecuteStored(string query)
{
//Context = new SchoolsPartnershipDBContext();
var r = _context.Set<T>().FromSqlRaw(query);
return r.ToList();
// return null;
}
}
Last step
var result = _storedProcedure.ExecuteStored("TaskExecPercentForSchoolRep");
return result.ToList();

Using the same dbcontext for different models

I have a DbContext that is empty. Mappings are created dynamically and the DbContext is used generically using Set();
The following is my generic DbContext.
/// <summary>
/// Object context
/// </summary>
public class MethodObjectContext : DbContext, IDbContext
{
private readonly IEventPublisher _eventPublisher;
public MethodObjectContext(string nameOrConnectionString, IEventPublisher eventPublisher)
: base(nameOrConnectionString)
{
_eventPublisher = eventPublisher;
}
public MethodObjectContext(DbConnection existingConnection, bool contextOwnsConnection, IEventPublisher eventPublisher)
: base(existingConnection, contextOwnsConnection)
{
_eventPublisher = eventPublisher;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
_eventPublisher.Publish(new ModelCreating(modelBuilder));
base.OnModelCreating(modelBuilder);
}
public new IDbSet<TEntity> Set<TEntity>() where TEntity : class
{
return base.Set<TEntity>();
}
}
I am trying write a unit test that will assert that the database is out of sync if I change the mappings (from the ModelCreating event).
The following is my test code.
[TestClass]
public class MigrationTests
{
private string _connectionString = string.Empty;
private string _testDb = string.Empty;
public MigrationTests()
{
_testDb = Path.Combine("C:\\", System.Reflection.Assembly.GetExecutingAssembly().GetName().Name.Replace(".", "") + ".sdf");
if (File.Exists(_testDb))
File.Delete(_testDb);
_connectionString = string.Format("Data Source={0};Persist Security Info=False;", _testDb);
Database.DefaultConnectionFactory = new SqlCeConnectionFactory("System.Data.SqlServerCe.4.0");
}
[TestMethod]
public void ThrowsErrorForOutOfDateDatabase()
{
// The initializer will handle migrating the database.
// If ctor param is false, auto migration is off and an error will be throw saying the database is out of date.
Database.SetInitializer(new MigrationDatabaseInitializer<MethodObjectContext>(false));
// Create the initial database and do a query.
// This will create the database with the conventions of the Product1 type.
TryQueryType<Product1>("Product");
// The next query will create a new model that has conventions for the product2 type.
// It has an additional property which makes the database (created from previous query) out of date.
// An error should be thrown indicating that the database is out of sync.
ExceptionAssert.Throws<InvalidOperationException>(() => TryQueryType<Product2>("Product"));
}
private void TryQueryType<T>(string tableName) where T : class
{
using (var context = new MethodObjectContext(_connectionString, new FakeEventPublisher(x => x.ModelBuilder.Entity<T>().ToTable(tableName))))
{
var respository = new EfRepository<T>(context);
var items = respository.Table.ToList();
}
}
}
My Product1 class is a POCO object, and my Product2 class is the same object with an additional db field.
My problem is that when I new() up the MethodObjectContext the second time and do a query, the ModelCreating method isn't called, causing me to get the following error.
The entity type Product2 is not part of the model for the current context.
Product2 would be a part of the context of the ModelCreating event was being called, but it is not. Any ideas?
NOTE: I am expecting errors since we are using the same connection string (sdf) and the db being created didn't create the additional field that my second call (Product2) requires.
My DbCompiledModel was being cached. The following flushed the cache.
private void ClearDbCompiledModelCache()
{
var type = Type.GetType("System.Data.Entity.Internal.LazyInternalContext, EntityFramework");
var cmField = type.GetField("CachedModels",System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic);
var cachedModels = cmField.GetValue(null);
cachedModels.GetType().InvokeMember("Clear", System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.InvokeMethod, null, cachedModels, null);
}

Entity Framework 4.1 bug in ObjectContext.SavingChanges handling (?)

I have a problem with something that seems to be a bug in Entity Framework 4.1: I have added a handler on ObjectContext.SavingChanges which updates a property "LastModified" whenever an object is added to or modified in the database. Then I do the following:
Add two objects to the database, and submit (call SaveChanges())
Modify the first object that was added
Extract the two objects ordered by LastModified
The resulting objects are returned in the wrong order. Looking at the objects, I can see that the LastModified property has been updated. In other words, the SavingChanges event was fired properly. But looking in the database, the LastModified column has not been changed. That is, there is now a difference between EF's cached objects and the rows in the database.
I tried performing the same update to LastModified in an overridden "SaveChanges" method:
public override int SaveChanges()
{
SaveChangesHandler();//updating LastModified property on all objects
return base.SaveChanges();
}
Doing this caused the database to be updated properly and the queries returned the objects in proper order.
Here is an entire test program showing the error:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Reflection;
using System.Threading;
namespace TestApplication
{
class Program
{
private PersistenceContext context;
private static void Main(string[] args)
{
var program = new Program();
program.Test();
}
public void Test()
{
SetUpDatabase();
var order1 = new Order {Name = "Order1"};
context.Orders.Add(order1);
var order2 = new Order {Name = "Order2"};
context.Orders.Add(order2);
context.SaveChanges();
Thread.Sleep(1000);
order1 = GetOrder(order1.Id); // Modified 1.
order1.Name = "modified order1";
context.SaveChanges();
List<Order> orders = GetOldestOrders(1);
AssertEquals(orders.First().Id, order2.Id);//works fine - this was the oldest object from the beginning
Thread.Sleep(1000);
order2 = GetOrder(order2.Id); // Modified 2.
order2.Name = "modified order2";
context.SaveChanges();
orders = GetOldestOrders(1);
AssertEquals(orders.First().Id, order1.Id);//FAILS - proves that the database is not updated with timestamps
}
private void AssertEquals(long id1, long id2)
{
if (id1 != id2) throw new Exception(id1 + " != " + id2);
}
private Order GetOrder(long id)
{
return context.Orders.Find(id);
}
public List<Order> GetOldestOrders(int max)
{
return context.Orders.OrderBy(order => order.LastModified).Take(max).ToList();
}
public void SetUpDatabase()
{
//Strategy for always recreating the DB every time the app is run.
var dropCreateDatabaseAlways = new DropCreateDatabaseAlways<PersistenceContext>();
context = new PersistenceContext();
dropCreateDatabaseAlways.InitializeDatabase(context);
}
}
////////////////////////////////////////////////
public class Order
{
public virtual long Id { get; set; }
public virtual DateTimeOffset LastModified { get; set; }
public virtual string Name { get; set; }
}
////////////////////////////////////////////////
public class PersistenceContext : DbContext
{
public DbSet<Order> Orders { get; set; }
public PersistenceContext()
{
Init();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
}
public void Init()
{
((IObjectContextAdapter) this).ObjectContext.SavingChanges += SavingChangesHandler;
Configuration.LazyLoadingEnabled = true;
}
private void SavingChangesHandler(object sender, EventArgs e)
{
DateTimeOffset now = DateTimeOffset.Now;
foreach (DbEntityEntry entry in ChangeTracker.Entries()
.Where(entity => entity.State == EntityState.Added || entity.State == EntityState.Modified))
{
SetModifiedDate(now, entry);
}
}
private static void SetModifiedDate(DateTimeOffset now, DbEntityEntry modifiedEntity)
{
if (modifiedEntity.Entity == null)
{
return;
}
PropertyInfo propertyInfo = modifiedEntity.Entity.GetType().GetProperty("LastModified");
if (propertyInfo != null)
{
propertyInfo.SetValue(modifiedEntity.Entity, now, null);
}
}
}
}
I should add that the SavingChanges handler worked fine before we upgraded to EF4.1 and using Code-First (that is, it worked in EF4.0 with model-first)
The question is: Have I found a bug here, or have I done something wrong?
I'm not sure if this can be considered a Bug. What seems to happen is that the way you manipulate the LastModified property does not trigger INotifyPropertyChanged and thus the changes do not get populated to your Database.
To prove it use:
order2.Name = "modified order2";
((IObjectContextAdapter)context).ObjectContext.ObjectStateManager.GetObjectStateEntry(order2).SetModifiedProperty("LastModified");
To utilize this knowledge in your SavingChangesHandler:
private void SavingChangesHandler(object sender, EventArgs e)
{
DateTimeOffset now = DateTimeOffset.Now;
foreach (DbEntityEntry entry in ChangeTracker.Entries()
.Where(entity => entity.State == EntityState.Added || entity.State == EntityState.Modified))
{
SetModifiedDate(now, entry);
if (entry.State == EntityState.Modified)
{
((IObjectContextAdapter) this).ObjectContext.ObjectStateManager.GetObjectStateEntry(entry.Entity).SetModifiedProperty("LastModified");
}
}
}
Edit:
I looked into this a little more and you are correct. For some reason MS decided to not fire PropertyChanged events when using PropertyInfo.SetValue anymore. Only one way to find out if this is a bug or a design decision: File a bug report / Post to msdn Forums.
Though changing the property directly via CurrentValue seems to work fine:
private static void SetModifiedDate(DateTimeOffset now, DbEntityEntry modifiedEntity)
{
if (modifiedEntity.Entity == null)
{
return;
}
modifiedEntity.Property("LastModified").CurrentValue = now;
}