How can I resolve a service based upon generic type argument in Castle Windsor? - entity-framework

I have an installer like this:
public void Install(IWindsorContainer container, IConfigurationStore store) {
//Services
container.Register(
Classes.FromAssemblyNamed(ASSEMBLY_NAME)
.BasedOn<IService>()
.WithServiceFirstInterface()
.LifestyleTransient());
//Repository
container.Register(
Component.For(typeof(IRepository<>))
.ImplementedBy(typeof(Repository<>))
.LifestyleTransient());
//Contexts
container.Register(
Component.For(typeof(Context<IGlobalObject>))
.ImplementedBy(typeof(GlobalContext<>)).LifestyleTransient());
}
The repository is an open generic, and it has a Context constructor injected, which is a wrapper around an EF DbContext, but takes a type argument to indicate the database it needs to connect to. The idea is that I have several DbContexts as I need to connect to multiple databases, and I want windsor to resolve the appropriate DBcontext based upon the type argument passed to the repository.
The repositories type argument is constrained to the following (GlobalObject and GlobalContext refer to types associated with 1 such database):
public interface IGlobalObject : IObject
{}
public interface IObject
{
int Key { get; set; }
}
However, Windsor cannot resolve the context, and I cannot work out why? It is registered and in the container, but it cannot resolve.
EDIT:
Code for GlobalContext:
public class GlobalContext<T> : Context<T>
where T : IGlobalObject
{
private const string GLOBAL_CSTR = "Global";
public GlobalContext() : base(ConfigurationManager.ConnectionStrings[GLOBAL_CSTR].ConnectionString) {}
public DbSet<Company> Companies { get; set; }
public DbSet<ConnectionString> ConnectionStrings { get; set; }
public DbSet<Server> Servers { get; set; }
}
Context:
//Wrapper around dbcontext which enforces type
public abstract class Context<T> : DbContext where T : IObject
{
protected Context() {}
protected Context(string connectionString) : base(connectionString){}
}
Edit 2:
If i specify the concrete types for every scenario it works, so it is clearly something to do with matching on the interface.
//Contexts
container.Register(
Component.For(typeof(Context<Server>))
.ImplementedBy(typeof(GlobalContext<Server>)).LifestyleTransient());

This looks like a problem to me:
//Contexts
container.Register(
Component.For(typeof(Context<IGlobalObject>))
.ImplementedBy(typeof(GlobalContext<>)).LifestyleTransient());
Here you're saying - when somebody asks for Context inject a GlobalContext<> - the problem being how is windsor meant to know what the generic argument for GlobalContext is.
Its hard to see without seeing your GlobalContext object, but should it be:
container.Register(
Component.For(typeof(Context<>))
.ImplementedBy(typeof(GlobalContext<>)).LifestyleTransient());

This isn't really a direct answer to your question. But I feel the approach may be wrong.
Considering you repositories are implemented by a generic base Repository<> I cant see a clean way of relating the generic Type to the correct context. I think you may need to switch to 'flavoured' repositories with explicit contexts injected into them and/or be more verbose in the way you register your contexts.

Related

Entity Framework Set with Generic Class

Ok, I might be punching above my pay grade here, but I'm trying to create a generic CRUD routine for and EF project. I've got most of it working but I'm flailing around on one point.
Normally you do something like this to add an entity through a context-
DBContext.MyClass.Add( p ); // p = instance of MyClass
That works fine, but since in a global method to handle all adds regardless of what class they are I'm passing in a Model as an object it would look more like this-
DBContext<whateverobject>.Add(whateverobject); // my objects is an object param passed into the method
I've tried doing a bunch of typeofs and there where T : class stuff but I'm having no luck. Any pointing in the right direction would help me out.
I'm using EF Core 2 so my options might also be more limited than EF 6.
Thanks.
The method you're looking for is DbContext's Set<T>()
Your generic repository for your generic CRUD would look something like this:
public class Repo<T> where T: class
{
private readonly DbSet<T> _set;
public Repo(DbContext dbContext)
{
_set = dbContext.Set<T>();
}
public void Add(T entity) => _set.Add(entity);
}
This example includes a maybe unusual thing:
where T: class: we have to specify that T has to be a reference type because DbSet<T> expects T to be a reference type
For generic querying you might want to use extension methods.
In order to implement a ById method you'd have to specify that the type T must have an Id property using an interface. That would look something like this:
public interface IEntity
{
int Id { get; set; }
}
public class User : IEntity
{
public int Id { get; set; }
}
public static class DbSetExtensions
{
public static T ById<T>(this DbSet<T> dbSet, int id) where T: class =>
dbSet.FirstOrDefault(entity => entity.Id == id);
}

inject behaviour on manual created objects

I'm working with autofac. So far i resolve all my dependencies with constructor injection.
There is a case where i get stuck:
Considering the given customer class:
public class Customer : ICustomer
{
public string Name { get; set; }
private int ExternId { get; set; }
public IExternalIdProvider externalIdProvider { get; set; }
public Customer()
{
this.externalIdProvider = new ConcreteIdProvider(this);
}
public BevorSave()
{
this.ExternId = externalIdProvider.GetNextId();
}
}
In Order to create a new customer object based on a request or gui action. I use the new Operator. However - There is an IdProvider within the CustomerClass i want to inject. (as property).
If the Customer would be resolved by the ioC Container i would use a configuration like:
builder.RegisterType<ConcreteIdProvider>().As<IExternalIdProvider>();
builder.RegisterType<Customer>().As<ICustomer>()
.OnActivated(ae =>
{
IExternalIdProvider idProvider =
ae.Context.Resolve<IExternalIdProvider>(TypedParameter.From(ae.Instance));
ae.Instance.externalIdProvider = idProvider;
});
My Question is: How can I inject the behaviour of the ExternalIdProvider in the Customer? (using autofac)
This article shows a sample, how this would be done with a service locator:
http://blogs.msdn.com/b/simonince/archive/2008/06/30/dependency-injection-is-dead.aspx
Thanks for your help.
You should reconsider having behavior on your entities. Having behavior in your entities forces you to do dependency injection on them, and this leads to an awkward situation, which you already noticed. Take a look at this related SO question and Mark Seemann's great answer.
So instead of having these operations on the Customer class, move them to an repository class. Other patterns to look at are unit of work, commands and queries.

How to fix "Properties whose types are collection of primitives or complex types are not supported" with EF DbContext?

I have a project containing POCO entities. A database context has been created for it using Entity Framework 4.2 and code first. This works fine, but the context needs to be exposed as an OData service which does not work.
Browsing to the OData service gives this error:
The property 'DataSubmissionItems' on type
'Lifecycle.ProgramReportSubmission.Model.ProgramReportSubmission' is
not a valid property. Properties whose types are collection of
primitives or complex types are not supported.
The data service class looks like:
public class ExceptionReportDataService : DataService<ExceptionReportEntitiesContext>
{
// This method is called only once to initialize service-wide policies.
public static void InitializeService(DataServiceConfiguration config)
{
config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);
config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
config.UseVerboseErrors = true;
}
}
The EF context class looks like:
public class ExceptionReportEntitiesContext : DbContext
{
public DbSet<ExceptionReport> ExceptionReports { get; set; }
public ExceptionReportEntitiesContext()
: base(DynamicConfig.GetAppSettingValue("DB_CONN_STRING_LIFECYCLE"))
{
}
}
The POCO entities look like:
namespace WBRT.ProgramData.Lifecycle.ExceptionReportModel
{
public class ExceptionReport
{
public virtual Guid ExceptionReportID { get; set; }
public virtual Lifecycle.ProgramReportSubmission.Model.ProgramReportSubmission ReportSubmission { get; set; }
}
}
namespace Lifecycle.ProgramReportSubmission.Model
{
public class ProgramReportSubmission
{
public Guid ProgramReportSubmissionId { get; set; }
public virtual ICollection<DataSubmissionItem> DataSubmissionItems { get; set; }
}
public class DataSubmissionItem
{
public Guid DataSubmissionItemId { get; set; }
}
}
What I've tried:
Setting DataServiceKey on the DataSubmissionItem class
Setting ProxyCreationEnabled to false on the ExceptionReportEntitiesContext constructor as well as in the data service
Overriding OnModelCreating and removing the IncludeMetadataConvention
Overriding OnModelCreating and setting modelBuilder.Entity<ProgramReportSubmission.Model.ProgramReportSubmission>().Ignore(prs => prs.DataSubmissionItems);
Note: I can't introduce a dependency on the EntityFramework DLL in the POCO entities project as this affects referencing projects that still run .NET 3.5.
Anyone know how to resolve this error?
THe RTM version of WCF DS doesn't support these kind of properties. But the latest CTP does. http://blogs.msdn.com/b/astoriateam/archive/2011/10/13/announcing-wcf-data-services-oct-2011-ctp-for-net-4-and-silverlight-4.aspx.
On the other hand, the fact that you get such an error probably means that WCF DS doesn't recognize the provider as EF, and istead works with it as with a reflection provider. So even the latest CTP won't really fix that problem.
WCF DS currently only recognizes EF provider if the T in DataService is ObjectContext or derived type. The typical workaround for EF Code First is to define the service as DataService and then override the CreateDataSource method on it and return the ObjectContext implementation from your DbContext. See this article about how to do that (it's about EF 4.1, but I think the same will apply to 4.2 as well): http://social.technet.microsoft.com/wiki/contents/articles/5234.aspx. You can skip down to the part about WCF DS.

Asp.Net Mvc templated helpers with interface types

I would like to use the Asp.net MVC templated helpers functionality to generate a standard UI for my objects throughout my application, but I've run into a significant issue:
I do not directly pass class types from my controllers into their views. Instead, I pass interface types.. with the actual implementation of the Model wrapped up in a Mongo or NHibernate specific class in an indirectly referenced project.
For discussion, my objects look like:
public interface IProductRepository {
IProduct GetByName(string name);
}
public interface IProduct {
string Name { get; set; }
}
public class NHibernateProductRepository : IProductRepository {
public IProduct GetByName(string name) {
/* NHibernate Magic here */
return nhibernateFoundProduct;
}
}
public class NHibernateProduct : IProduct {
public virtual Name { get; set; }
}
public class ProductController : Controller {
public ProductController(IProductRepository productRepo) {
_ProductRepo = productRepo;
}
public ActionResult Index(string name) {
IProduct product = _ProductRepo.GetByName(name);
return View(product);
}
}
Is it possible to use interface types with the Editor.For() syntax? Are there any problems or sticking points that I need to be aware of?
I have an EditorTemplate\IProduct.ascx file available. At this time, I can't seem to get that template to be rendered without hardcoding the "IProduct" name into the Editor.For() call. I would prefer this type of 'Convention over Configuration'....
The templates helpers will use the runtime type of the object for the name. In this case you should name the file NHibernateProduct.ascx
If you don't know the name of the type at design time than you could write a helper method that would inspect the object instance and walk the list of interfaces that a particular type is implementing and return a name based on that. Then you would call the appropriate override to EditorFor that takes the string "templateName" parameter.
I have decided to use an approach with a ViewModel native to the Web project that implements the IProduct interface.

Invoke method on WCF RIA Services

I can't make this seemingly simple call on my DomainService compile. I keep getting 'Operation named 'ComposeNewOrder' does not conform to the required signature. Parameter types must be an entity type or one of the predefined serializable types.'
Am I missing something here, should I be doing it in another way or is it just unsupported? (I'm using WCF RIA services 1.0 for VS2010)
public class ComposedOrder
{
[Key]
public Order Order { get; set; }
public OrderPart[] Parts { get; set; }
}
public class MyDomainService{
...
[Invoke]
public void ComposeNewOrder(ComposedOrder co)
{
//implementation
}
...
}
I have CRUD operations defined for Order and OrderPart which are entities from my EntityFramework model.
Invoke operations cannot take Entity types (such as your ComposedOrder) as parameters. You can only use data types, such as int, string etc. You could pass in the key of your ComposedOrder and load it using that.
I have actually written an invoke method passing it a entity argument and it works.