I have the following registration
builder.Register<Func<Type, IRequestHandler>>(
c => request => (IRequestHandler)c.Resolve(request));
Basically I am trying to register a factory method that resolves an instance of IRequestHandler from a given type.
This works fine until the version 2.4.3.700. But now I am getting a the following error..
Cannot access a disposed object.
Object name: 'This resolve operation has already ended. When
registering components using lambdas,
the IComponentContext 'c' parameter to
the lambda cannot be stored. Instead,
either resolve IComponentContext again
from 'c', or resolve a Func<> based
factory to create subsequent
components from.'.
UPDATE
I was trying to limit autofac's exposure to the rest of the projects in the solution. Nick, thanks for the hint, now my registration looks like this...
builder.Register<Func<Type,IRequestHandler>>(c =>
{
var handlers = c.Resolve<IIndex<Type,RequestHandler>>();
return request => handlers[request];
});
The c in this expression is a temporary, so this code while previously functional, is broken. Autofac 2.4.5 detects this problem while earlier versions silently ignored it.
To fix the issue, explicitly resolve IComponentContext:
builder.Register<Func<Type, IRequestHandler>>(c => {
var ctx = c.Resolve<IComponentContext>();
return request => (IRequestHandler)ctx.Resolve(request));
});
The functionality you're emulating here might be better represented using keys and indexes, e.g. see Interrupted chain of IoC or http://code.google.com/p/autofac/wiki/TypedNamedAndKeyedServices.
I had a similar problem as the user6130. I wanted to avoid using IIndex in my class implementation and pass in a service resolver into my constructor instead.
So now I have my service implementation with the following constructor:
public MvcMailer(Converter<string, MailerBase> mailerResolver)
{
_resolver = mailerResolver;
}
I wanted to used keyed services without directly relying on the Autofac namespace. I was getting the same error until I restructured the configuration as such.
1) Scan for all my mailer implementations and index via class name (could be improved)
builder.RegisterAssemblyTypes(System.Reflection.Assembly.GetExecutingAssembly())
.Where(t => t.Name.EndsWith("Mailer")).Keyed<Mvc.Mailer.MailerBase>(t => t.Name.Replace("Mailer", "").ToLower());
2) Register the converter in Autofac config
builder.Register<System.Converter<string,Mvc.Mailer.MailerBase>>(c => {
var all = c.Resolve<Autofac.Features.Indexed.IIndex<string,Mvc.Mailer.MailerBase>>();
return delegate(string key)
{
return all[key];
};
});
3) Register like other types of components and let Autofac handle the Converter injection
builder.RegisterType<Mailers.MvcMailer>().As<Mailers.IMailer>();
Related
I have this value object
public class ProductReference : ValueObject
{
protected ProductReference(){}
public ProductReference(string value){}
public string Value{get; protected set;}
}
I use it in my entity as :
public class Product : Entity<long>
{
protected Product(){}
public ProductReference Reference{get; protected set;}
}
In the OnModelCreating of my DbContext I defined :
modelBuilder.Entity<Product>(entity => {
entity.Property(a => a.Reference)
.HasColumnName("Reference")
.HasConversion(
a => a.Value,
s => new ProductReference (s);
});
When I do :
await dbcontext.Products.Where(p=>p.Reference.Value.Contains("some text")).toArrayAsync();
I get an exception
Expression cannot be converted to a valid SQL statement
I know for sure there is a way to create a custom expression converter, but I cannot find a good, simple and EF Core 3.1 compatible example to deal with my issue and that explain me clearly the concepts I miss.
I found this very interesting project
https://github.com/StevenRasmussen/EFCore.SqlServer.NodaTime
but it is too advanced for me to reproduce it for only my use case.
[EDIT] the ValueObject ans Entity are from
CSharpFunctionalExtensions nuget package, I dont think they are really relevant in my question.
I am not completely sure if i understand correctly what you want to accomplish, but you could try to configure your ProductReference as an Owned Entity Type.
Here you would transform the following code from:
modelBuilder.Entity<Product>(entity => {
entity.Property(a => a.Reference)
.HasColumnName("Reference")
.HasConversion(
a => a.Value,
s => new ProductReference (s);
});
to
modelBuilder.Entity<Product>(entity => {
entity.OwnsOne(a => a.Reference, referenceBuilder => {
referenceBuilder.Property(p => p.Value).HasColumnName("Reference");
});
});
With that your select statement should work.
It could be that you have to play around with the properties of your class ProductReference, or use some modifiers of the fluent API.
So first for some context on what is happening here behind the scenes and why its not gonna work even for build in simple converters like BoolToZeroOneConverter.
The problem here is that you are calling when converting the new ProductReference(s). This is method where you can do whatever you want in it. For example if use it in a Select statement it will again fail. For example:
await dbcontext.Products
.Select(x=>new ProductReference(x.Value))
.toArrayAsync();
The reason is obvious, it won't be able to translate. But why it cant transform it to a query?
Because you are passing a constructor. Inside this constructor you could be doing API calls or using Reflections to set the variables to your object, pretty much anything. That of course is not able to be translated in an SQL query.
Converters are generally used for in memory but they can be used for databse operations as well. This would mean that you will need something like this:
await dbcontext.Products
.Select(x=>new ProductReference() // empty constructor that does nothing
{
Property1 = x.Property1 // I don't know how the constructor maps them
})
.toArrayAsync();
Using this type of expression allow you to actually transalte the expression to an SQL statement and not making the conversion on the SQL DB and not in memory.
Now in your specific case using:
.HasConversion(
a => a.Value,
s => new ProductReference (){};
});
Should fix your issues but I fail to understand why would you want to initialize or convert a ProductReference to a ProductReference.
I'm trying to use LinqKit AsExpandable in my EfCore2.0 project and I am running into this problem where the Includes doesn't work.
In attempting to debug this I have downloaded the LinqKit source from github and have replaced the Nuget references in my project with Project references.
When debugging with the LinqKit project, I noticed that the my call to Include wasn't hitting a breakpoint I set on ExpandableQueryOfClass<T>.Include.
I did some further testing and noticed the breakpoint is hit if I first cast to ExpandableQueryOfClass (this is an internal class in LinqKit which I made public, so I can't do the cast if I'm referencing the Nuget package).
Is this a bug in LinqKit or am I doing something wrong?
Here is my test code.
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Internal.DAL.Db;
using Internal.Models.Customer;
using LinqKit; // Referencing LinqKit.Microsoft.EntityFrameworkCore
using Xunit;
namespace Internal.EntityFramework.Tests
{
public class UnitTest1
{
private DbContextOptionsBuilder<DataContext> _ctxBuilder =>
new DbContextOptionsBuilder<DataContext>().UseSqlServer(Connection.String);
[Fact]
public async Task SuccessTest()
{
using (var ctx = new DataContext(_ctxBuilder.Options))
{
var query =
(
// this cast is the only difference between the methods
(ExpandableQueryOfClass<Order>)
ctx.Orders
.AsExpandable()
)
.Include(r => r.Customer)
.Take(500);
var responses = await query.ToListAsync();
// this succeeds
Assert.All(responses, r => Assert.NotNull(r.Customer));
}
}
[Fact]
public async Task FailTest()
{
using (var ctx = new DataContext(_ctxBuilder.Options))
{
var query = ctx.Orders
.AsExpandable()
.Include(r => r.Customer)
.Take(500);
var responses = await query.ToListAsync();
// this fails
Assert.All(responses, r => Assert.NotNull(r.Customer));
}
}
}
}
Edit 2018-05-15: There is an open issue on the LinqKit github repository.
I'm not sure whether this is LINQKit or EF Core fault (definitely it's not yours).
For sure it's caused by the EF Core Include / ThenInclude implementations which all include a check for source.Provider is EntityQueryProvider and do nothing in case it's false.
I'm not sure what's the idea of EF Core designers - probably custom query providers to inherit from EntityQueryProvider, but at the same time the class is part of the infrastructure and marked as not supposed to be used.
I also have no idea how LINQKit is planning to address it, but as you noticed the current implementation definitely is broken/not working. At the moment it looks to me more like a WIP.
The only workaround I see at this time is to apply AsExpandable() after includes.
Simplest example of this, I get a collection and try to output it via Web API:
// GET api/items
public IEnumerable<Item> Get()
{
return MyContext.Items.ToList();
}
And I get the error:
Object of type
'System.Data.Objects.ObjectQuery`1[Dcip.Ams.BO.EquipmentWarranty]'
cannot be converted to type
'System.Data.Entity.DbSet`1[Dcip.Ams.BO.EquipmentWarranty]'
This is a pretty common error to do with the new proxies, and I know that I can fix it by setting:
MyContext.Configuration.ProxyCreationEnabled = false;
But that defeats the purpose of a lot of what I am trying to do. Is there a better way?
I would suggest Disable Proxy Creation only in the place where you don't need or is causing you trouble. You don't have to disable it globally you can just disable the current DB context via code...
[HttpGet]
[WithDbContextApi]
public HttpResponseMessage Get(int take = 10, int skip = 0)
{
CurrentDbContext.Configuration.ProxyCreationEnabled = false;
var lista = CurrentDbContext.PaymentTypes
.OrderByDescending(x => x.Id)
.Skip(skip)
.Take(take)
.ToList();
var count = CurrentDbContext.PaymentTypes.Count();
return Request.CreateResponse(HttpStatusCode.OK, new { PaymentTypes = lista, TotalCount = count });
}
Here I only disabled the ProxyCreation in this method, because for every request there is a new DBContext created and therefore I only disabled the ProxyCreation for this case .
Hope it helps
if you have navigation properties and you do not want make them non virtual, you should using JSON.NET and change configuration in App_Start to using JSON not XML!
after install JSON.NET From NuGet, insert this code in WebApiConfig.cs in Register method
var json = config.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = Newtonsoft.Json.PreserveReferencesHandling.Objects;
config.Formatters.Remove(config.Formatters.XmlFormatter);
If you have navigation properties make them non virtual. Mapping will still work but it prevents the creation of Dynamic Proxy entities which cannot be serialized.]
Not having lazy loading is fine in a WebApi as you don't have a persistent connection and you ran a .ToList() anyway.
I just disabled proxy classes on a per needed basis:
// GET: ALL Employee
public IEnumerable<DimEmployee> Get()
{
using (AdventureWorks_MBDEV_DW2008Entities entities = new AdventureWorks_MBDEV_DW2008Entities())
{
entities.Configuration.ProxyCreationEnabled = false;
return entities.DimEmployees.ToList();
}
}
Add the following code in Application_Start function of Global.asax.cs:
GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings
.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
GlobalConfiguration.Configuration.Formatters
.Remove(GlobalConfiguration.Configuration.Formatters.XmlFormatter);
This instruct the API to serialize every response into JSON and remove XML responses.
In my case the object being returned had a property within it with a type that did not have an argumentless/default constructor. By adding a zero-argument constructor to that type the object could be serialized successfully.
I had the same problem and my DTO was missing an parameter less constructor.
public UserVM() { }
public UserVM(User U)
{
LoginId = U.LoginId;
GroupName = U.GroupName;
}
First constructor was missing.
I got this error message and it turns out the problem was that I had accidentally set my class to use the same serialized property name for two properties:
public class ResultDto
{
//...
[JsonProperty(PropertyName="DataCheckedBy")]
public string ActualAssociations { get; set; }
[JsonProperty(PropertyName="DataCheckedBy")]
public string ExpectedAssociations { get; set; }
//...
}
If you're getting this error and you aren't sending entities directly through your API, copy the class that's failing to serialize to LINQPad and just call JsonConvert.SerializeObject() on it and it should give you a better error message than this crap. As soon as I tried this it gave me the following error message: A member with the name 'DataCheckedBy' already exists on 'UserQuery+ResultDto'. Use the JsonPropertyAttribute to specify another name.
After disable Proxy Creation, use eager loading (Include()) to load the proxy object.
In my Project EntityCollection returned from the WebApi action method.
Configuration.ProxyCreationEnabled = false not applicable. I have tried the below approach it is working fine for me.
Control Panel.
2.Turn on Windows Features on or off
Choose Internet Information Service
Check all the World Wide Web Components it would be better to check all the components in IIS.
Install the components.
Go to (IIS) type inetmgr in command prompt.
select the published code in the Virtual directory.
Convert into application
Browse it the application.
The answer by #Mahdi perfectly fixes the issue for me, however what I noticed is that if my Newtonsoft.JSON is 11.0 version then it doesn't fix the issue, but the moment I update Newtonsoft.JSON to latest 13.0 it starts working.
I have just upgraded to MVC 3 and likewise need to upgrade Autofac.
The following code was working, but now fails with this error -
This resolve operation has already
ended. When registering components
using lambdas, the IComponentContext
'c' parameter to the lambda cannot be
stored. Instead, either resolve
IComponentContext again from 'c', or
resolve a Func<> based factory to
create subsequent components from.
public static IServiceLocator Locator;
public class ServiceA : IServiceA
{
}
public interface IServiceA
{
}
[Test]
public void TestAutofacServiceLocator()
{
// This resolve operation has already ended. When registering components using lambdas, the IComponentContext 'c' parameter to the lambda cannot be stored.
// Instead, either resolve IComponentContext again from 'c', or resolve a Func<> based factory to create subsequent components from.
var builder = new ContainerBuilder();
builder.RegisterType<ServiceA>().As<IServiceA>();
builder.Register(c => Locator = new AutofacServiceLocator(c)).As<IServiceLocator>().SingleInstance();
var container = builder.Build();
container.Resolve<IServiceLocator>();
var x = Locator.GetInstance<IServiceA>();
Assert.NotNull(x);
}
How should I resgister IServiceLocator?
I looked at the answer to question autofac registration issue in release v2.4.5.724 but I'm still confused.
I really should have read Nick's error message, the answer was in the message.
Fixed !!
builder.Register(c => Locator = new AutofacServiceLocator(c.Resolve()))
.As().SingleInstance();
In Autofac one can do the following to get all registered services:
IEnumerable<MyClass> all = Context.Resolve<IEnumerable<MyClass>>()
However, this does not include those which were registered as named services.
Looking at the Autofac source it seems this is because services are queried for resolution based on either a TypedService or a KeyedService.
Is there a way to resolve all services to an IEnumerable, irrespective of whether they were registered with a name or not?
The best option here is to register the items using both the key and the regular 'typed' service:
builder.Register<CupOfT>()
.As<IBeverage>()
.Keyed<IBeverage>("someKey");
You can then just resolve IEnumerable<IBeverage> to get the result you're after, while resolving them by key (or name) is also supported.
If you're concerned about maintaining a particular default registration for IBeverage just use PreseveExistingDefaults() on the others (or make sure your intended default is registered last).
HTH!
Nick
I've written a method which appears to work; I'd appreciate feedback if there's a built-in way to do this in Autofac. In the below example, the field _context is of type IComponentContext.
public IEnumerable<T> ResolveAll<T>()
{
// We're going to find each service which was registered
// with a key, and for those which match the type T we'll store the key
// and later supplement the default output with individual resolve calls to those
// keyed services
var allKeys = new List<object>();
foreach (var componentRegistration in _context.ComponentRegistry.Registrations)
{
// Get the services which match the KeyedService type
var typedServices = componentRegistration.Services.Where(x => x is KeyedService).Cast<KeyedService>();
// Add the key to our list so long as the registration is for the correct type T
allKeys.AddRange(typedServices.Where(y => y.ServiceType == typeof (T)).Select(x => x.ServiceKey));
}
// Get the default resolution output which resolves all un-keyed services
var allUnKeyedServices = new List<T>(_context.Resolve<IEnumerable<T>>());
// Add the ones which were registered with a key
allUnKeyedServices.AddRange(allKeys.Select(key => _context.ResolveKeyed<T>(key)));
// Return the total resultset
return allUnKeyedServices;
}
It would appear that you can combine the As<T>() methods and Named<T>() methods as shown below:
[TestMethod]
public void ResolveTests()
{
var builder = new ContainerBuilder();
builder.RegisterType<ClassA1>().As<IClassA>().Named<IClassA>("1");
builder.RegisterType<ClassA2>().As<IClassA>().Named<IClassA>("2");
builder.RegisterType<ClassA3>().As<IClassA>().Named<IClassA>("3");
var container = builder.Build();
var allInstances = container.Resolve<IEnumerable<IClassA>>();
allInstances.Count().Should().Be(3);
container.ResolveNamed<IClassA>("1").Should().BeAssignableTo<ClassA1>();
container.ResolveNamed<IClassA>("2").Should().BeAssignableTo<ClassA2>();
container.ResolveNamed<IClassA>("3").Should().BeAssignableTo<ClassA3>();
}