EF Core 5.x + OData 7.5.6 - $Top doesn't insert TOP inside of EF Query - entity-framework-core

I'm thinking I missed something very simply but here is what I'm trying to todo. I have a .NET Core 5 project with EF Core 5 + OData 7.5.6. Everything appears to be working except for INSERT a TOP command in the generated SQL Query. Here is my controller. Very simple.
[EnableQuery]
[ApiController]
[Route("odata/[controller]")]
public class ConferenceHistoryController : ControllerBase
{
private readonly cdr_database_2Context _db;
public ConferenceHistoryController(cdr_database_2Context db)
{
_db = db;
}
[HttpGet]
public IEnumerable<_000701CallDataRecord> GetConferenceList()
{
// Return Full List
var query = _db._000701CallDataRecords;
var qs = query.ToQueryString();
return query.ToList();
}
}
When I send in my request to:
https://localhost:44355/odata/ConferenceHistory/?$select=RecordId,version&$top=5
The resulting SQL query is:
SELECT [0].[endDateTime], [0].[id], [0].[organizer], [0].[ParticipantCount], [0].[participants], [0].[PoorCall], [0].[type], [0].[version]
FROM [000701_CallDataRecord] AS [0]
As you can see, it's missing both the TOP and the SELECT commands. I have the following in my Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// Add CORS to Project
services.AddCors(options =>
{
options.AddDefaultPolicy(builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
});
// Add OData
services.AddOData();
services.AddControllers();
services.AddDbContext<cdr_database_2Context>(options =>
options.UseSqlServer(Configuration.GetConnectionString("CDRConnection"))
);
// Add Swagger Support
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "ODataAPI", Version = "v1" });
});
SetOutputFormatters(services);
}
And
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
// Use HTTPS & Routing
app.UseHttpsRedirection();
app.UseRouting();
// Auth???
app.UseAuthorization();
// Swagger Configure
app.UseSwagger();
app.UseSwaggerUI((config) =>
{
config.SwaggerEndpoint("/swagger/v1/swagger.json", "Swagger Odata Demo Api");
});
// Setup Endpoint for EDM
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.EnableDependencyInjection();
endpoints.Select().Expand().Filter().OrderBy().MaxTop(50).Count();
endpoints.MapODataRoute("odata", "odata", GetEdmModel());
});
}
And
private IEdmModel GetEdmModel()
{
// Add OData - EDM Definitions Below
var odataBuilder = new ODataConventionModelBuilder();
odataBuilder.EntitySet<WeatherForecast>("WeatherForecast");
odataBuilder.EntitySet<_000701CallDataRecord>("ConferenceHistory");
return odataBuilder.GetEdmModel();
}
Just looking for some direction on where I could have gone wrong. Everything else seems to be working really well.

Your controller needs to derive from ODataController not ControllerBase
You need to decorate GetConferenceList() with [Queryable]

OK, I figured out where the issue was in my above. It was with using the .ToList and the IEumerable. So if I changed this:
[HttpGet]
public IEnumerable<_000701CallDataRecord> GetConferenceList()
{
// Return Full List
var query = _db._000701CallDataRecords;
var qs = query.ToQueryString();
return query.ToList();
}
To:
[HttpGet]
public IEnumerable<_000701CallDataRecord> GetConferenceList()
{
// Return Full List
var query = _db._000701CallDataRecords;
var qs = query.ToQueryString();
return query;
}
It works. It's also worth noting that the .ToQueryString() doesn't show the OData/EF insertion of the TOP command. But if I enable .EnableSensitiveDataLogging() in the Startup.cs file, then I clearly see it being inserted into it.
Apparently, calling .ToList() will cause it to process and ignore any of the fancy OData commands. That was the only change and all was good then.

Related

How to know the user edited?

I am using Blazor server-side with ASP.NET Core 5 and EF Core 5. I would like that when a record is updated than the ModifiedBy and CreatedBy are generated automatically.
I have overriden SaveChangeAsync as follows:
public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
ApplyInterfaces();
return base.SaveChangesAsync(cancellationToken);
}
private void ApplyInterfaces()
{
var userId = _httpContextAccessor?.HttpContext?.User?.Identity?.Name;
var currentUsername = !string.IsNullOrEmpty(userId)? userId: "Anonymous";
foreach (ICreatedBy entity in ChangeTracker.Entries().Where(x => x.Entity is ICreatedBy).Where(x => x.State == EntityState.Added).Select(x=> x.Entity))
{
entity.CreatedBy = currentUsername;
entity.CreatedDate = DateTime.Now;
}
foreach (IModifiedBy entity in ChangeTracker.Entries().Where(x=> x.State == EntityState.Modified).Where(x => x.Entity is IModifiedBy).Select(x => x.Entity))
{
entity.ModifiedBy = currentUsername;
entity.ModifiedDate = DateTime.Now;
}
}
_httpContextAccessor is injected to the DBContext. I added the services
services.AddHttpContextAccessor();
Everything works fine on IIS Express. But when I publish to IIS _httpContextAccessor.HttpContext is null.
Where did I miss something?
You should not use IHttpContextAccessor in Blazor apps (source).
It's not clear from your code sample if this code is in a Blazor component/page or in a database access library. If it's in the database layer you should modify SaveChangesAsync to add a string username parameter, which you can then pass to ApplyInterfaces.
Getting the user should be the responsibility of the Blazor page/component, using either AuthenticationStateProvider or an AuthorizeView component.
#page "/example"
#inject AuthenticationStateProvider auth;
<button #OnClick="Save">Save</button>
#code
{
async Task Save()
{
var state = await auth.GetAuthenticationStateAsync();
var username = state.User.Identity?.Name ?? "[anon]";
await someObject.SaveChangesAsync(username);
}
}

Cannot Create A DbSet for ApplicationUser

I have an asp.net core project that I am refactoring. Previously I had all my database logic contained within the project, however as we are now adding a WebAPI, I have moved the database logic to a separate .net core standard project, so it is shared between the two projects.
This seems to work fine in the new web api, however I am having issues in the original project, relating to signInManager and the ApplicationUser class.
All compiles just fine, however, I get the following error during runtime:
InvalidOperationException: Cannot create a DbSet for 'ApplicationUser' because this type is not included in the model for the context.`
I have also moved this ApplicationUser class to the new DAL project, and as far as I can see, i've updated all references to it (certainly enough to pass the compile time checks).
My startup.cs is as follows:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
// password policy settings
services.Configure<IdentityOptions>(options =>
{
// Password settings
options.Password.RequireDigit = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = false;
options.Password.RequireUppercase = true;
options.Password.RequireLowercase = false;
options.Password.RequiredUniqueChars = 6;
// Lockout settings
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(30);
options.Lockout.MaxFailedAccessAttempts = 10;
options.Lockout.AllowedForNewUsers = true;
// User settings
options.User.RequireUniqueEmail = true;
});
// Add session cookie
services.ConfigureApplicationCookie(options =>
{
// Cookie settings
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
// If the LoginPath isn't set, ASP.NET Core defaults
// the path to /Account/Login.
options.LoginPath = "/Account/Login";
// If the AccessDeniedPath isn't set, ASP.NET Core defaults
// the path to /Account/AccessDenied.
options.AccessDeniedPath = "/Account/AccessDenied";
options.SlidingExpiration = true;
// Disable redirect to login page for unauthorized requests to / api resources
options.Events.OnRedirectToLogin = context =>
{
if (context.Request.Path.StartsWithSegments("/api") && context.Response.StatusCode == StatusCodes.Status200OK)
{
context.Response.StatusCode = StatusCodes.Status401Unauthorized;
return Task.FromResult<object>(null);
}
else
{
context.Response.Redirect(context.RedirectUri);
return Task.FromResult<object>(null);
}
};
});
// Add application services.
services.AddTransient<IEmailSender, EmailSender>();
// add our Db handlers
services.AddScoped<DAL.Services.Interfaces.IServices, DAL.Services.Services>();
services.AddMvc();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider services)
{
if (env.IsDevelopment())
{
app.UseBrowserLink();
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
var cultureInfo = new CultureInfo("en-GB");
CultureInfo.DefaultThreadCurrentCulture = cultureInfo;
CultureInfo.DefaultThreadCurrentUICulture = cultureInfo;
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
I have a "using" statement at the top as follows:
using IceBowl.DAL;
using IceBowl.DAL.Models;
So the call to "AddIdentity" is passing in the right ApplicationUser - in fact, there is only one ApplicationUser class, I deleted the original.
The code seems to be having issues on the following line:
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
There's something it doesnt like about sign in manager, but I'm at a loss to explain what. All references have been updated and now point to the DAL that contains the datacontext and ApplicationUser class.
Any pointers?

Why am I getting error: "Cannot access disposed object" in .net core 2 with EF and AutoFac?

First 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: 'MemberContext'.
I have 3 projects, Domain, API and WebSPA app.
Domain has 2 modules, DomainModule and MediatorModule
public class DomainModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(typeof(MemberContext).Assembly)
.AsImplementedInterfaces()
.InstancePerLifetimeScope(); // via assembly scan
builder.RegisterType<MemberContext>().AsSelf()
.InstancePerLifetimeScope(); // or individually
}
}
public class MediatorModule : Autofac.Module
{
protected override void Load(ContainerBuilder builder)
{
// enables contravariant Resolve() for interfaces with single contravariant ("in") arg
builder
.RegisterSource(new ContravariantRegistrationSource());
// mediator itself
builder
.RegisterType<Mediator>()
.As<IMediator>()
.InstancePerLifetimeScope();
// request handlers
builder
.Register<SingleInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t =>
{
object o;
return c.TryResolve(t, out o) ? o : null;
};
})
.InstancePerLifetimeScope();
// notification handlers
builder
.Register<MultiInstanceFactory>(ctx =>
{
var c = ctx.Resolve<IComponentContext>();
return t => (IEnumerable<object>) c.Resolve(typeof(IEnumerable<>).MakeGenericType(t));
})
.InstancePerLifetimeScope();
}
}
In API project I have also 2 modules, ApplicationModule and again MediatorModule same as the one above.
public class ApplicationModule : Module
{
protected override void Load(ContainerBuilder builder)
{
builder.RegisterAssemblyTypes(typeof(Startup).Assembly)
.AsImplementedInterfaces()
.InstancePerLifetimeScope(); // via assembly scan
builder.RegisterType<MemberContext>().AsSelf().InstancePerLifetimeScope(); // or individually
}
}
No, when I debug I can see that member context gets newed up on each request, yet on second request, it throws above error. To make sure I am not going crazy, I modified constructor of dbcontext to create an id for context so i can verify they are different. What am I doing wrong?
public MemberContext(DbContextOptions<MemberContext> options) : base(options)
{
MemberContextId = Guid.NewGuid();
Console.WriteLine("member context created: " + MemberContextId);
}
Here is the startup in API
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy",
builder => builder.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
// .AllowCredentials()
);
});
services.AddMvc()
.AddControllersAsServices();//Injecting Controllers themselves thru DI
//For further info see: http://docs.autofac.org/en/latest/integration/aspnetcore.html#controllers-as-services
AddSwaggerGen(services);
//var connection = Configuration["ConnectionString"];
//services.AddDbContext<MemberContext>(options => options.UseSqlServer(connection),ServiceLifetime.Scoped);
services.AddEntityFrameworkSqlServer()
.AddDbContext<MemberContext>(options =>
{
options.UseSqlServer(Configuration["ConnectionString"]
//,sqlServerOptionsAction: sqlOptions =>
//{
// sqlOptions.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name);
// sqlOptions.EnableRetryOnFailure(maxRetryCount: 10, maxRetryDelay: TimeSpan.FromSeconds(30), errorNumbersToAdd: null);
//}
);
},
ServiceLifetime.Scoped //Showing explicitly that the DbContext is shared across the HTTP request scope (graph of objects started in the HTTP request)
);
var container = new ContainerBuilder();
container.Populate(services);
container.RegisterAssemblyModules(typeof(VIN.Members.Domain.Entities.Member).Assembly,
typeof(Startup).Assembly);
return new AutofacServiceProvider(container.Build());
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
//NOTE: must be before UseMVC !!!
app.UseCors("CorsPolicy");
app.UseMvc();
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
});
}
private void AddSwaggerGen(IServiceCollection services)
{
services.AddSwaggerGen(options =>
{
options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new Swashbuckle.AspNetCore.Swagger.Info
{
Title = "VIN Members HTTP API",
Version = "v1",
Description = "Members Service HTTP API",
TermsOfService = "Terms Of Service"
});
});
}
}
UPDATE:
What I am trying to do is delete a record. On client side code looks like this
onDelete(item: IMember) {
//TODO: replace this with dialog service component
if (window.confirm('Are sure you want to delete this member?')) {
//put your delete method logic here
this.service.deleteMember(item).subscribe(x => {
this.getMembers();
});
}
}
this delete request gets mapped to a controller that passes it to mediator
Controller
// DELETE api/members/5
[HttpDelete("{id}")]
public void Delete(Guid id)
{
var command = new DeleteMember.Command(id);
_mediator.Send(command).ConfigureAwait(false);
}
and finally handler
public class DeleteMember
{
public class Command : IRequest
{
public Command(Guid memberId)
{
Guard.NotNull(memberId, nameof(memberId));
MemberId = memberId;
}
public Guid MemberId { get; }
}
public class Handler : AsyncRequestHandler<Command>
{
private MemberContext _context;
public Handler(MemberContext context)
{
_context = context;
Console.WriteLine("Delete member context: " + context.MemberContextId);
}
protected override async Task HandleCore(Command cmd)
{
try
{
var member = await _context.FindAsync<Member>(cmd.MemberId);//.ConfigureAwait(false);
// if (member != null)
//// {
_context.Remove(member);
await _context.SaveChangesAsync().ConfigureAwait(false);
// }
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
}
}
As you can see there is no code that disposes that context. Scratching my head.
See this commented out check for member if null. That was throwing error as well, I commented it out just to see what will happen, and now it throws as SaveChangesAsync.
As request completes, context gets disposed. Since command handler uses SaveChangesAsync(), context is disposed before save completes. Culprit is controller method :). It should be async as well.
[HttpDelete("{id}")]
public async Task Delete(Guid id)
{
var command = new DeleteMember.Command(id);
await _mediator.Send(command).ConfigureAwait(false);
}
Your DbContext is scoped, meaning that Dependency Injection will return the same DbContext object every time one is asked for within the same HTTP request (in the case of ASP.NET).
That means that you should not be calling Dispose on your DbContext (otherwise that same object can't be used a second time). That seems to be what is happening to you, intentionally or not.
That does mean you should not be using using with it. Are you using using anywhere in your code against your DbContext?
I don't think you showed the line where the Exception is being thrown.
Update:
Try overriding Dispose in your MemberContext class. Something like this:
public override void Dispose() {
base.Dispose();
}
But just set a breakpoint there. When it breaks (if it does) check the stack trace and see what called it.
This can also be caused by having async void instead of async Task within WebAPI in my experience.

Exclude certain entities from second-level caching

I'm using EFCache to provide 2nd-level caching in my EF context.
I ran into a problem, where one of my entities is connected to a view which provides row-level security. So this view filters rows based on some parameters. When using 2nd-level cache, all users will get the same result!
I'm looking for a way to exclude certain entities from caching, any help is welcome.
This is my caching configuration:
class CacheConfiguration : DbConfiguration
{
public CacheConfiguration()
{
var transactionHandler = new CacheTransactionHandler(new InMemoryCache());
AddInterceptor(transactionHandler);
var cachingpolicy = new cachingpolicy();
Loaded +=
(sender, e) => e.ReplaceService<DbProviderServices>(
(s, _) => new CachingProviderServices(s, transactionHandler, cachingPolicy));
}
}
I found the answer in this blog post.
In order to exclude some entities, you need to create a caching policy and drive a class from CachingPolicy.
After overriding CanBeCached method, you can return false to prevent caching.
This is my working code:
class CacheConfiguration : DbConfiguration
{
public CacheConfiguration()
{
var transactionHandler = new CacheTransactionHandler(new InMemoryCache());
AddInterceptor(transactionHandler);
//var cachingPolicy = new CachingPolicy();
var cachingPolicy = new myCachingPolicy();
Loaded +=
(sender, e) => e.ReplaceService<DbProviderServices>(
(s, _) => new CachingProviderServices(s, transactionHandler, cachingPolicy));
}
}
public class myCachingPolicy : CachingPolicy
{
protected override bool CanBeCached(System.Collections.ObjectModel.ReadOnlyCollection<System.Data.Entity.Core.Metadata.Edm.EntitySetBase> affectedEntitySets, string sql, IEnumerable<KeyValuePair<string, object>> parameters)
{
string[] excludedEntities = {
"permView1",
"permView2",
"permView3"};
if (affectedEntitySets.Where(x => excludedEntities.Contains(x.Table)).Any())
{
return false;
}
else
{
return base.CanBeCached(affectedEntitySets, sql, parameters);
}
}
}

How do you mock adding items to a repository or DbContext using moq?

The examples I've seen for using moq for a repository only show how to mock things being returned. I have a somewhat strange requirement: when a query is executed, if a condition exists, a certain item should be added to the repository. I am wondering how to test this without querying the database. I know how to mock the condition existing, but then how do you setup the mock so that you can test that the certain item is added?
Try to use fake in memory repository instead of moq, for example universal generic repository for all entities:
public interface IInMemoryRepository<T> where T : class
{
IQueryable<T> GetAll();
void Create(T item);
void Update(T item);
T GetItem(Expression<Func<T, bool>> expression);
void Delete(T item);
}
public class InMemoryRepository<T> : IInMemoryRepository<T> where T : class
{
private int _incrementer = 0;
public Dictionary<int, T> List = new Dictionary<int, T>();
public IQueryable<T> GetAll()
{
return List.Select(x => x.Value).AsQueryable();
}
public void Create(T item)
{
_incrementer++;
item.GetType().GetProperties().First(p => p.Name == "Id").SetValue(item, _incrementer, null);
List.Add(_incrementer, item);
}
public void Update(T item)
{
var key = (int)item.GetType().GetProperties().First(p => p.Name == "Id").GetValue(item, null);
List[key] = item;
}
public T GetItem(Expression<Func<T, bool>> expression)
{
return List.Select(x => x.Value).SingleOrDefault(expression.Compile());
}
public void Delete(T item)
{
var key = (int)item.GetType().GetProperties().First(p => p.Name == "Id").GetValue(item, null);
List.Remove(key);
}
}
You would not mock the repository; you would have an alternate repository that would use an in-memory store instead of the database, then use IoC to select the correct repository implementation for tests/code.
This blog article might be of use, although my design has changed somewhat since I wrote the post and I really need to update it. I used teh generic repository pattern in a way that enables the DbContext to be mocked. This allows the data access layer to be tested 'right up to the edges'.
Times have changed -- since the release of Entity Framework 6 it has become much easier to mock database context and datasets. This article outlines the particulars.
Testing non-query scenarios
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System.Data.Entity;
namespace TestingDemo
{
[TestClass]
public class NonQueryTests
{
[TestMethod]
public void CreateBlog_saves_a_blog_via_context()
{
var mockSet = new Mock<DbSet<Blog>>();
var mockContext = new Mock<BloggingContext>();
mockContext.Setup(m => m.Blogs).Returns(mockSet.Object);
var service = new BlogService(mockContext.Object);
service.AddBlog("ADO.NET Blog", "http://blogs.msdn.com/adonet");
mockSet.Verify(m => m.Add(It.IsAny<Blog>()), Times.Once());
mockContext.Verify(m => m.SaveChanges(), Times.Once());
}
}
}
Testing query scenarios
Query testing is pretty sweet now, because you can build up test data sets in code and then execute your tests against them:
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
namespace TestingDemo
{
[TestClass]
public class QueryTests
{
[TestMethod]
public void GetAllBlogs_orders_by_name()
{
var data = new List<Blog>
{
new Blog { Name = "BBB" },
new Blog { Name = "ZZZ" },
new Blog { Name = "AAA" },
}.AsQueryable();
var mockSet = new Mock<DbSet<Blog>>();
mockSet.As<IQueryable<Blog>>().Setup(m => m.Provider).Returns(data.Provider);
mockSet.As<IQueryable<Blog>>().Setup(m => m.Expression).Returns(data.Expression);
mockSet.As<IQueryable<Blog>>().Setup(m => m.ElementType).Returns(data.ElementType);
mockSet.As<IQueryable<Blog>>().Setup(m => m.GetEnumerator()).Returns(0 => data.GetEnumerator());
var mockContext = new Mock<BloggingContext>();
mockContext.Setup(c => c.Blogs).Returns(mockSet.Object);
var service = new BlogService(mockContext.Object);
var blogs = service.GetAllBlogs();
Assert.AreEqual(3, blogs.Count);
Assert.AreEqual("AAA", blogs[0].Name);
Assert.AreEqual("BBB", blogs[1].Name);
Assert.AreEqual("ZZZ", blogs[2].Name);
}
}
}
You can do this by mocking the DbSet.Add() method, like so:
[Fact]
public void CreateBlog_saves_a_blog_via_context()
{
var data = new List<Blog>
{
new Blog { Name = "BBB" },
new Blog { Name = "ZZZ" },
new Blog { Name = "AAA" },
};
var mockSet = new Mock<DbSet<Blog>>();
mockSet.Setup(blogs => blogs.Add(It.IsAny<Blog>)).Returns<Blog>(blog =>
{
data.Add(blog);
return blog;
});
var mockContext = new Mock<BloggingContext>();
mockContext.Setup(m => m.Blogs).Returns(mockSet.Object);
var service = new BlogService(mockContext.Object);
var blog = service.AddBlog("_ADO.NET Blog", "http://blogs.msdn.com/adonet");
var blogs = service.GetAllBlogs();
mockSet.Verify(m => m.Add(It.IsAny<Blog>()), Times.Once());
mockContext.Verify(m => m.SaveChanges(), Times.Once());
Assert.NotNull(blog)
Assert.Equal(4, blogs.Count);
Assert.Equal("AAA", blogs(1).Name);
Assert.Equal("BBB", blogs(2).Name);
Assert.Equal("ZZZ", blogs(3).Name);
}
This is adapted from the documentation found here.