Generic Repository Pattern Soft Delete - c#

I have create a Generic Repository (Using EF 6.1.1), which I am using in several projects and it works very well.
I have implemented a 'soft' delete feature, where we mark data as deleted, but do not actually remove it from the database.
I can then filter all my queries as all entities inherit from a base entity which has the IsDeleted property. This is all works nicely, but it obviously does not filter out any of the 'soft deleted' child entities.
I am unsure how to go about doing this in a generic way, as I dont want to have to over code a solution into every respoitory, that really defeats the reason for having a generic repo.
this is an example of my current Generic Repo
public sealed class MyRepository<T> : IRepository<T> where T : BaseEntity
{
public String CurrentUser { get; set; }
private readonly MyObjectContext context;
private readonly Configuration configuration = ConfigurationManager.GetConfiguration();
private IDbSet<T> entities;
private IDbSet<T> Entities
{
get { return entities ?? (entities = context.Set<T>()); }
}
public MyRepository(MyObjectContext context, String userName = null)
{
this.context = context;
var providerManager = new DataProviderManager(configuration);
var dataProvider = (IDataProvider)providerManager.LoadDataProvider();
dataProvider.InitDatabase();
CurrentUser = userName;
}
public void Dispose()
{
//do nothing at the moment
}
public T GetById(Guid id)
{
return Entities.FirstOrDefault(x => x.Id == id && !x.IsDeleted);
}
public IQueryable<T> GetAll()
{
return Entities.Where(x => !x.IsDeleted);
}
public IQueryable<T> Query(Expression<Func<T, bool>> filter)
{
return Entities.Where(filter).Where(x => !x.IsDeleted);
}
public void Delete(T entity)
{
if (configuration.HardDelete)
{
HardDelete(entity);
}
else
{
SoftDelete(entity);
}
}
private void HardDelete(T entity)
{
try
{
if (entity == null)
{
throw new ArgumentNullException("entity");
}
Entities.Attach(entity);
Entities.Remove(entity);
}
catch (DbEntityValidationException ex)
{
var msg = string.Empty;
foreach (var validationErrors in ex.EntityValidationErrors)
foreach (var validationError in validationErrors.ValidationErrors)
msg += Environment.NewLine + string.Format("Property: {0} Error: {1}", validationError.PropertyName, validationError.ErrorMessage);
var fail = new Exception(msg, ex);
throw fail;
}
}
private void SoftDelete(T entity)
{
entity.IsDeleted = true;
Update(entity);
}
}
Any help on this would be great.
Thanks

Someone has built a global filter, you can try it and install it from nuget EntityFramework.Filters.
https://github.com/jbogard/EntityFramework.Filters
Here is an example how to use it.
public abstract class BaseEntity
{
public int Id { get; set; }
public bool IsDeleted { get; set; }
}
public class Foo : BaseEntity
{
public string Name { get; set; }
public ICollection<Bar> Bars { get; set; }
}
public class Bar : BaseEntity
{
public string Name { get; set; }
public int FooId { get; set; }
public Foo Foo { get; set; }
}
public class AppContext : DbContext
{
public DbSet<Foo> Foos { get; set; }
public DbSet<Bar> Bars { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// Registers and configures it first.
DbInterception.Add(new FilterInterceptor());
var softDeleteFilter = FilterConvention.Create<BaseEntity>("SoftDelete",
e => e.IsDeleted == false); // don't change it into e => !e.IsDeleted
modelBuilder.Conventions.Add(softDeleteFilter);
}
}
Then you can enable it in your repository constructor or somewhere after db context instance is created because the filters are disabled by default.
using (var db = new AppContext())
{
db.EnableFilter("SoftDelete");
var foos = db.Foos.Include(f => f.Bars).ToArray(); // works on Include
}
using (var db = new AppContext())
{
db.EnableFilter("SoftDelete");
var foos = db.Foos.ToArray();
foreach (var foo in foos)
{
var bars = foo.Bars; // works on lazy loading
}
}
using (var db = new AppContext())
{
db.EnableFilter("SoftDelete");
var foos = db.Foos.ToArray();
foreach (var foo in foos)
{
db.Entry(foo).Collection(f => f.Bars).Load(); // works on manual loading
}
}
This filter is not needed anymore.
public IQueryable<T> Query(Expression<Func<T, bool>> filter)
{
return Entities.Where(filter);//.Where(x => !x.IsDeleted);
}
As long as you have enabled it.
public MyRepository(MyObjectContext context, String userName = null)
{
this.context = context;
if (!configuration.HardDelete)
{
this.context.EnableFilter("SoftDelete");
}
}

I was having the same problem but my method to solve was little different i used a genric base interface IGenricInterface with IsDeleted as property
public int DeletebyId(string Id)
{
var Ent = (IGenricInterface)_sitecontext.Set<TEntity>().Find(Id);
Ent.IsDeleted = 1;
}
and for hardDelete
_sitecontext.Set<TEntity>().Remove(Ent);
This is on ID but offcourse you can do it on EnTity as well

Related

Cannot acces a disposed object

I'm trying to change some of my linq codes to sp but in one my functions which is
private async Task getNodes()
{
List<TreeViewNode> nodes = new List<TreeViewNode>();
var activities = await _spConnections.GetActiveListBySpAsync(User.Identity.GetCookieUserCompId<int>());
//Loop and add the Parent Nodes.
foreach (var activity in activities)
{
var model1 = await _activitiesService.GetByIdWithFormsAsync(activity.ID);
var model = model1.Forms.OrderBy(f => f.Order).ToList();
var preformid = model1.PreFormId;
nodes.Add(new TreeViewNode { id = activity.ID.ToString(), parent = "#", text = activity.Name });
var Forms = await _spConnections.GetSpMainActiveFormListAsync(activity.ID);
// Loop and add the Child Nodes.
foreach (var form in Forms)
{
DigitalContentEnum enumType = EnumHelper<DigitalContentEnum>.Parse(form.Type);
string typeName = EnumHelper<DigitalContentEnum>.GetDisplayValue(enumType);
nodes.Add(new TreeViewNode { id = form.ActivitiesID.ToString() + "-" + form.ID.ToString(), parent = form.ActivitiesID.ToString(), text = form.Title + " <span class='text-danger'>(" + typeName + ") </span> "});
}
if (preformid != null)
{
var preformtags = await _activitiesService.GetByIdWithPreFormsAsync(preformid);
var preformModel = preformtags.Forms.OrderBy(x => x.Order).ToList();
foreach (var foorm in preformModel)
{
DigitalContentEnum enumType = EnumHelper<DigitalContentEnum>.Parse(foorm.Type);
string typeName = EnumHelper<DigitalContentEnum>.GetDisplayValue(enumType);
nodes.Add(new TreeViewNode { id = foorm.ActivitiesID.ToString() + "-" + foorm.ID.ToString(), parent = model1.ID.ToString(), text = foorm.Title + " <span class='text-danger'>(" + typeName + ") </span> " });
}
}
}
//Serialize to JSON string.
var result = JsonConvert.SerializeObject(nodes);
ViewBag.Json = result;
}
So as you see var activities is being filled by running a sp and it does work
but when it reaches
var model1 = await _activitiesService.GetByIdWithFormsAsync(activity.ID);
The Error:
Cannot access a disposed object. A common cause of this error is disposing a context that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling Dispose() on the context, or wrapping the context in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'Context'.
appears
activitesservice is my repository which the runs the following :
public async Task<Activity> GetByIdWithFormsAsync(long id)
{
return await _activities.Include(x => x.Forms).FirstOrDefaultAsync(x => x.ID == id);
}
and my sp is:
public async Task<List<Form>> GetSpMainActiveFormListAsync(long actId)
{
var Check = new List<Form>();
using (_unitOfWork)
{
using (var cmd = _unitOfWork.ExecuteCreateConnection())
{
cmd.CommandText = "SpGetMainActiveForm";
cmd.CommandType = System.Data.CommandType.StoredProcedure;
var param1 = cmd.CreateParameter();
param1.ParameterName = "actId";
param1.Value = actId;
cmd.Parameters.Add(param1);
_unitOfWork.ExecuteOpenConnection();
using (var result = cmd.ExecuteReader())
{
if (result.HasRows)
{
Check = (List<Form>)CommonFunctions.Conversions.MapToList<Form>(result);
}
}
_unitOfWork.ExecuteCloseConnection();
}
}
return Check;
}
UnitOfWork is my repository of my Context which is
public interface IUnitOfWork:IDisposable
{
EntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class;
DbSet<TEntity> Set<TEntity>() where TEntity : class;
int SaveChanges(bool acceptAllChangesOnSuccess);
int SaveChanges();
void ExecuteSqlCommand(string query);
DbCommand ExecuteCreateConnection();
void ExecuteOpenConnection();
void ExecuteCloseConnection();
void ExecuteSqlCommand(string query, params object[] parameters);
Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken());
Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken());
}
and my context is:
public class Context : DbContext, IUnitOfWork
{
public Context(DbContextOptions<Context> options)
: base(options)
{
}
public virtual DbSet<Admin> Admin { set; get; }
public virtual DbSet<Activity> Activities { set; get; }
public virtual DbSet<ActivityCategory> ActivityCategories { set; get; }
public virtual DbSet<ActivitiesPic> ActivitiesPics { set; get; }
public virtual DbSet<ActivityRecord> ActivityRecords { set; get; }
public virtual DbSet<Answer> Answers { set; get; }
public virtual DbSet<Company> Companies { set; get; }
public virtual DbSet<Form> Form { set; get; }
public virtual DbSet<NazerOrTarah> NazerOrTarah { set; get; }
//Added in 1398/12/13
public virtual DbSet<ReportDynamicTemplate> ReportDynamicTemplate { set; get; }
public virtual DbSet<ReportTemplateForms> ReportTemplateForms { set; get; }
public virtual DbSet<User> Users { set; get; }
protected override void OnModelCreating(ModelBuilder builder)
{
builder.HasDefaultSchema("dbo");
builder.Entity<Activity>()
.HasIndex(a => a.CompID);
builder.Entity<ActivityRecord>()
.HasIndex(a => new { a.ActivitiesID, a.NazerID });
builder.Entity<Answer>()
.HasIndex(a => new { a.ActRecordsID, a.FormID });
builder.Entity<Form>()
.HasIndex(a => new { a.ActivitiesID, a.ParentId });
// it should be placed here, otherwise it will rewrite the following settings!
foreach (var relationship in builder.Model.GetEntityTypes().SelectMany(e => e.GetForeignKeys()))
{
relationship.DeleteBehavior = DeleteBehavior.Restrict;
}
base.OnModelCreating(builder);
}
public void MarkAsChanged<TEntity>(TEntity entity) where TEntity : class
{
Update(entity);
}
public void ExecuteSqlCommand(string query)
{
Database.ExecuteSqlCommand(query);
}
public DbCommand ExecuteCreateConnection()
{
return Database.GetDbConnection().CreateCommand();
}
public void ExecuteOpenConnection()
{
Database.OpenConnection();
}
public void ExecuteCloseConnection()
{
Database.CloseConnection();
}
public void ExecuteSqlCommand(string query, params object[] parameters)
{
Database.ExecuteSqlCommand(query, parameters);
}
}
What am I doing wrong?

ASP .NET Core disposing UnitOfWork before getting all data

I'm implementing a generic repository + Unit of work pattern along with a WebApi project. I'm having problems with getting one entity and including the collection of another enttity that refers to it.
I have the following entities mapped through code first:
public class Ingredient : BaseEntity
{
public string Name { get; set; }
public string Amount { get; set; }
public Guid RecipeId { get; set; }
public virtual Recipe Recipe { get; set; }
}
public class Recipe : BaseEntity
{
public string Name { get; set; }
public string Description { get; set; }
public string ImagePath { get; set; }
public virtual ICollection<Ingredient> Ingredients { get; set; }
}
This is my unit of work:
public class UnitOfWork<TContext> : IRepositoryFactory, IUnitOfWork<TContext>, IUnitOfWork where TContext : DbContext
{
private readonly TContext context;
private Dictionary<Type, object> repositories;
public UnitOfWork(TContext context)
{
this.context = context ?? throw new ArgumentNullException(nameof(context));
}
public TContext Context => context;
public IRepository<TEntity> GetRepository<TEntity>() where TEntity : BaseEntity
{
if (repositories == null)
{
repositories = new Dictionary<Type, object>();
}
var type = typeof(TEntity);
if (!repositories.ContainsKey(type))
{
repositories.Add(type, new Repository<TEntity>(context));
}
return (IRepository<TEntity>)repositories[type];
}
public int Commit()
{
return context.SaveChanges();
}
public void Dispose()
{
context?.Dispose();
}
}
And my generic repository:
public class Repository<T> : IRepository<T> where T : BaseEntity
{
protected readonly DbContext dbContext;
protected readonly DbSet<T> dbSet;
public Repository(DbContext context)
{
dbContext = context;
dbSet = dbContext.Set<T>();
}
public T GetEntity(Guid id)
{
return dbSet.Find(id);
}
public T GetEntity(Guid id, params Expression<Func<T, object>>[] includeProperties)
{
IEnumerable<string> properties = GetProperties(includeProperties);
IQueryable<T> queryable = dbSet;
foreach (var property in includeProperties)
{
queryable = dbSet.Include(property);
}
return queryable.FirstOrDefault(x => x.Id == id);
}
[...]
private static IEnumerable<string> GetProperties(Expression<Func<T, object>>[] includeProperties)
{
List<string> includelist = new List<string>();
foreach (var item in includeProperties)
{
MemberExpression body = item.Body as MemberExpression;
if (body == null)
throw new ArgumentException("The body must be a member expression");
includelist.Add(body.Member.Name);
}
return includelist.AsEnumerable();
}
}
The controller is injecting the RecipeService. In the controller I have this method:
[HttpGet("{id}", Name = "Get")]
public IActionResult Get(Guid id)
{
var recipe = recipeService.GetRecipe(id);
if (recipe == null)
{
return NotFound();
}
return Ok(recipe);
}
The recipe service injects the IUnitOfWork and has the following method:
public Recipe GetRecipe(Guid id)
{
return repository.GetEntity(id, r => r.Ingredients);
}
Also I have the services registered as follows:
services.AddScoped<IRepositoryFactory, UnitOfWork<TContext>>();
services.AddScoped<IUnitOfWork, UnitOfWork<TContext>>();
services.AddScoped<IUnitOfWork<TContext>, UnitOfWork<TContext>>();
services.AddScoped<IRecipeService, RecipeService>();
My problem is when I'm getting a specified recipe(along with its ingredients) i got a "The connection has been restarted" error (in firefox). While debugging I can see that I have the recipe with its ingredients. But when returning Ok(recipe) and mapping the result to the entities, the IUnitOfWork is disposed after getting the first ingredient.
Anyone can help me out? Thanks
The problem was I was having a circular reference i wasn't getting any exception.
I fixed by adding the following in the ConfigureServices method of the Startup class:
services.AddMvc().AddJsonOptions(options =>
{
options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
});

How to update subset of fields using Entity Framework?

I have multiple objects with subset of fields from the table. In some cases I should update only one field. How to do this properly in Entity Framework 6.0? The following code throws error due to database constraints because AddOrUpdate tries to replace all fields but FieldName with empty values.
public static TheField Set(TheField f)
{
using (var dbContext = new MyModel())
{
dbContext.MyEntity.AddOrUpdate(new MyEntity()
{
ForeignId = f.ForeignId,
FieldName = f.FieldName,
});
dbContext.SaveChanges();
return f;
}
}
It would be nice to have an extension
public static class MyExtension
{
public static void AddOrUpdateSchema<TEntity, TKey>(this IDbSet<TEntity> set, TKey id, string schema,
params TEntity[] entities) where TEntity : class
{
// ...
}
}
and then use it
public class MyEntity
{
[Key]
public int ForeignId { get; set; }
[UpdateSchema("Schema")]
public string FieldName { get; set; }
// ...
}
public class MyEntityView
{
public int ForeignId { get; set; }
public string FieldName { get; set; }
public static MyEntityView Set(MyEntityView f)
{
using (var dbContext = new MyModel())
{
dbContext.MyEntity.AddOrUpdateSchema(f.ForeignId, "Schema", new MyEntity()
{
FieldName = f.FieldName,
});
dbContext.SaveChanges();
return f;
}
}
}
Or maybe Entity Framework already has functionality for this task?
Set State property to EntityState.Modified
db.Entry(entity).State = EntityState.Modified;
Please check if below code works for you:
using (var dbContext = new MyModel())
{
if (dbContext.MyEntities.Any(e => e.ForeignId == f.ForeignId))
{
dbContext.MyEntities.Attach(f);
dbContext.ObjectStateManager.ChangeObjectState(f, EntityState.Modified);
}
else
{
dbContext.MyEntities.AddObject(f);
}
dbContext.SaveChanges();
}

EF saving entity references

I have the following situation.
I'm building a course management MVC site where I have courses but also course iterations I call them as it happens sometime that a new student will join just for one iteration.
I have a model courseIterations which possess a ICollection of students. When starting the course I want to generate the next nine iterations with default students which subscribed to the course. The problem I have is that when I list the iterations the students where not saved. What am I doing wrong? Or is there a better approach in general?
public class CourseIteration : AuditableEntity<int>
{
public Course Course { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Date")]
public DateTime Date { get; set; }
public virtual ICollection<Student> Students { get; set; }
public virtual ICollection<Instructor> Instructors { get; set; }
}
My attempt to fill
private void FillIterations(Course course, int coursePeriod)
{
var defaultStudents = _studentRepository.GetAllByCourse(course.CourseID).ToList(); // load the existing Items
course.Iterations = new List<CourseIteration>();
for (int i = 0; i < coursePeriod; i++)
{
var item = new CourseIteration
{
Course = course,
Date = course.StartDate.AddDays(i * 7),
Instructors = course.Instructors,
Students = new List<Student>()
};
foreach (var student in defaultStudents)
{
item.Students.Add(student);
}
_courseIterationRepository.Add(item);
course.Iterations.Add(item);
_courseIterationRepository.Save();
_courseRepository.Save();
}
}
I use a Generic Repository
public abstract class GenericRepository : IGenericRepository
where T : BaseEntity
{
protected DbContext _entities;
protected readonly IDbSet _dbset;
public GenericRepository(DbContext context)
{
_entities = context;
_dbset = context.Set<T>();
}
public virtual IEnumerable<T> GetAll()
{
return _dbset.AsEnumerable<T>();
}
public IEnumerable<T> FindBy(System.Linq.Expressions.Expression<Func<T, bool>> predicate)
{
IEnumerable<T> query = _dbset.Where(predicate).AsEnumerable();
return query;
}
public virtual T Add(T entity)
{
return _dbset.Add(entity);
}
public virtual T Attach(T entity)
{
return _dbset.Attach(entity);
}
public virtual T Delete(T entity)
{
return _dbset.Remove(entity);
}
public virtual void Edit(T entity)
{
_entities.Set<T>().Attach(entity);
_entities.Entry(entity).State = EntityState.Modified;
Save();
}
public virtual void Save()
{
_entities.SaveChanges();
}
Custom Save function could be the problem?
public override int SaveChanges()
{
var modifiedEntries = ChangeTracker.Entries()
.Where(x => x.Entity is IAuditableEntity
&& (x.State == System.Data.Entity.EntityState.Added || x.State == System.Data.Entity.EntityState.Modified));
foreach (var entry in modifiedEntries)
{
IAuditableEntity entity = entry.Entity as IAuditableEntity;
if (entity != null)
{
string identityName = Thread.CurrentPrincipal.Identity.Name;
DateTime now = DateTime.UtcNow;
if (entry.State == System.Data.Entity.EntityState.Added)
{
entity.CreatedBy = identityName;
entity.CreatedDate = now;
}
else
{
base.Entry(entity).Property(x => x.CreatedBy).IsModified = false;
base.Entry(entity).Property(x => x.CreatedDate).IsModified = false;
}
entity.UpdatedBy = identityName;
entity.UpdatedDate = now;
}
}
return base.SaveChanges();
}

Test Entity Framework Find Method

I'm trying to test the that the GetSystem(int id) method in SystemService.cs returns the correct value, but can't seem to figure out how to get everything to play well together. It seems that no matter what I do, GetSystem() always returns null. This is using Entity Framework 6. If I change the body of GetSystem to read _context.Systems.SingleOrDefault(s => s.Id = id), then everything works properly, but I'd really like to use Find().
What is the proper way to test this? I'm using xUnit and Moq in this example. SystemServiceTests.cs shows the code that I'm currently using that isn't working.
SystemService.cs
namespace MyProject.Services
{
public class SystemService
{
private readonly MyContext _context;
public SystemService(MyContext context)
{
_context = context;
}
public Models.System GetSystem(int id)
{
return _context.Systems.Find(id);
}
}
}
SystemServiceTests.cs
namespace MyProject.Tests.Unit
{
public class SystemServiceTests
{
[Fact]
public void GetSystemReturnsFromContext()
{
var data = new List<Models.System> {
new Models.System { Id = 1, Name = "test 1" },
new Models.System { Id = 2, Name = "test 2" }
}.AsQueryable();
var mockContext = new Mock<MyContext>();
var mockSet = new Mock<MockableDbSetWithIQueryable<Models.System>>();
mockContext.Setup(c => c.Systems).Returns(mockSet.Object);
mockSet.Setup(m => m.Provider).Returns(data.Provider);
mockSet.Setup(m => m.Expression).Returns(data.Expression);
mockSet.Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.Setup(m => m.GetEnumerator()).Returns(data.GetEnumerator());
var service = new SystemService(mockContext.Object);
var system = service.GetSystem(1);
Assert.NotNull(system); // This is always null
}
}
}
MyContext.cs
namespace MyProject.Models
{
public class MyContext : DbContext
{
public MyContext()
: base("DefaultConnection")
{
}
public virtual DbSet<Models.System> Systems { get; set; }
}
}
System.cs
namespace MyProject.Models
{
public class System
{
public int Id { get; set; }
public string Name { get; set; }
}
}
MockableDbSetWithIQueryable.cs
namespace MyProject.Tests.Helpers
{
public abstract class MockableDbSetWithIQueryable<T> : DbSet<T>, IQueryable
where T : class
{
public abstract IEnumerator<T> GetEnumerator();
public abstract Expression Expression { get; }
public abstract Type ElementType { get; }
public abstract IQueryProvider Provider { get; }
}
}
PS. Some of the code for this, specifically MockableDbSetWithIQueryable was found at http://msdn.microsoft.com/en-US/data/dn314429
I was able to find the recommended way to test everything using Entity Framework 6. The resource for this recommendation is available at http://msdn.microsoft.com/en-US/data/dn314431.
In a nutshell, test classes need to be created for each bit that needs to be tested. What I ended up doing is the following:
TestDbSet.cs
public class TestDbSet<TEntity> : DbSet<TEntity>, IQueryable, IEnumerable<TEntity>
where TEntity : class
{
ObservableCollection<TEntity> _data;
IQueryable _query;
public TestDbSet()
{
_data = new ObservableCollection<TEntity>();
_query = _data.AsQueryable();
}
public override TEntity Add(TEntity item)
{
_data.Add(item);
return item;
}
public override TEntity Remove(TEntity item)
{
_data.Remove(item);
return item;
}
public override TEntity Attach(TEntity item)
{
_data.Add(item);
return item;
}
public override TEntity Create()
{
return Activator.CreateInstance<TEntity>();
}
public override TDerivedEntity Create<TDerivedEntity>()
{
return Activator.CreateInstance<TDerivedEntity>();
}
public override ObservableCollection<TEntity> Local
{
get
{
return _data;
}
}
Type IQueryable.ElementType
{
get { return _query.ElementType; }
}
Expression IQueryable.Expression
{
get { return _query.Expression; }
}
IQueryProvider IQueryable.Provider
{
get { return _query.Provider; }
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
return _data.GetEnumerator();
}
IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
{
return _data.GetEnumerator();
}
}
TestSystemDbSet.cs
class TestSystemDbSet : TestDbSet<Models.System>
{
public override Models.System Find(params object[] keyValues)
{
var id = (int)keyValues.Single();
return this.SingleOrDefault(s => s.Id == id);
}
}
TestContext.cs
public class TestContext: IContext
{
public TestContext()
{
this.Systems = new TestSystemDbSet();
}
public DbSet<Models.System> Systems { get; set; }
public int SaveChangesCount { get; private set; }
public int SaveChanges()
{
this.SaveChangesCount++;
return 1;
}
}
SystemServiceTests.cs
public class SystemServiceTests
{
[Fact]
public void GetSystemReturnsFromContext()
{
var context = new TestContext();
context.Systems.Add(new Models.System { Id = 1, Name = "System 1" });
context.Systems.Add(new Models.System { Id = 2, Name = "System 2" });
context.Systems.Add(new Models.System { Id = 3, Name = "System 3" });
var service = new SystemService(context);
var system = service.GetSystem(2);
Assert.NotNull(system);
Assert.Equal(2, system.Id);
Assert.Equal("System 2", system.Name);
}
}
SystemService.cs
public class SystemService : ISystemService
{
private readonly IContext _context;
public SystemService(IContext context)
{
_context = context;
}
public Models.System AddSystem(Models.System system)
{
var s = _context.Systems.Add(system);
_context.SaveChanges();
return s;
}
public Models.System GetSystem(int id)
{
return _context.Systems.Find(id);
}
}
ISystemService.cs
public interface ISystemService
{
Models.System AddSystem(Models.System system);
Models.System GetSystem(int id);
}
.Find() is returning null because that is the default value for System. The collection does not contain an item with id id.
.Find() is a method of List.
I suggest you use LINQ's FirstOrDefault()
The reason being, you can use lazy loading by returning an IQueryable

Categories