版本信息:
我正在使用C#4.5,Entity Framework 6.0和MEF。
代码和单元测试
我创建了一个测试项目来解释这个问题: https://skydrive.live.com/redir?resid=E3C97EC293A34048!2234
请打开UnitTest项目并尝试运行TestIfItWorks()单元测试。
问题
我想将非泛型DbSet转换为其通用版本,但我收到以下异常:InvalidCastException: Cannot create a DbSet<IUser> from a non-generic DbSet for objects of type 'User'
:
var nonGeneric = context.Set(typeof(User));
var generic = nonGeneric.Cast<IUser>(); //Exception in here
User类正在实现IUser,所以你会认为强制转换不应该是一个问题,除非DbSet代码仅限于具体的类(我希望不是否则我需要创建一个围绕非泛型DbSet的包装器来将其转换为通用DbSet或找到当前DbSet实现的替代方法。)
如果您想知道为什么我使用接口,即使它们目前不受Microsoft支持,我会给您一些解释(希望这会过滤掉“不要那样做”而不是提供解决方案的响应):
我正在使用MEF和EntityFramework创建一个松散耦合的数据层引擎,通过它我可以为每个项目提供实体(及其相应的配置)。我一直在广泛使用Interfaces来定义引擎。使用MEF在运行时发现元数据和上下文中实体的具体实现。
摘自代码
[TestMethod]
public void TestIfItWorks()
{
//TODO: Please open the App.Config and change the PluginsPath to match the Plugins folder in your machine.
using (var dbContext = new MyContext()) //Please ignore this line for now. This was UnitOfWork which I replaced with Context to create a simple unit test
{
dbContext.Setup(); //Please ignore this line for now. This was part of UnitOfWork which I moved to here to create a simple unit test
//The purpose of all these is to be able to read and write user info from/to database while User class is defined in an external assembly
//but we can import it by MEF using IUser interface.
//Failed Attempt# 1: Use User class directly! This doesnt work because User is in an external class which we dont have reference to
//var failedAttempt1 = dbContext.Set<User>();
//Failed Attempt# 2: But the good thing is that we have access to IUser and its exports
//then lets get a DbSet<IUser> instead
var failedAttempt2 = dbContext.Set<IUser>();
try
{
var throwsException2 = failedAttempt2.FirstOrDefault();
}
catch (InvalidOperationException ex)
{
//InvalidOperationException:
// The entity type IUser is not part of the model for the current context.
// It also didnt work when I tried to define a class that inherits from EntityTypeConfiguration<IUser>at TestImplementation
}
//Ok then lets do it differently this time. Lets get User type (that we know we have good configuration for)
//from our Container and ask Context to give us the nonGeneric version
var userImplementationType = Logic.Instance.GetExportedTypes<IUser>().FirstOrDefault();
Assert.IsNotNull(userImplementationType, "We havn't been able to load TestImplementation into catalog. Please ensure the PluginsPath is set correctly at App.Config");
var nonGeneric = dbContext.Set(userImplementationType);
//
// This is working so far, we can add and remove records from database using
// the nonGeneric version of DbSet. You can uncomment the following code block provide a unique ID
// and test it yourself.
//
var newUser = Logic.Instance.New<IUser>();
newUser.Id = "99";
newUser.UserName = "Aidin Sadighi";
nonGeneric.Add(newUser);
try
{
dbContext.SaveChanges();
}
catch (DbUpdateException ex)
{
//This is OK because most probably this is a duplicate user. Just increase the Id to make it unique.
}
//Failed Attempt#3: Cast non generic DbSet to generic
try
{
//TODO: I need to fix this. Help me please
var genericSet = nonGeneric.Cast<IUser>();
}
catch (InvalidCastException ex)
{
//Cannot create a DbSet<IUser> from a non-generic DbSet for objects of type 'User'.
throw;
}
}
}
答案 0 :(得分:10)
为此,我实际上建议使用反射。在DbContext的构造函数中,您可以将属性设置为函数指针:
method = this.GetType().GetMethod("Set", new Type[0]).MakeGenericMethod(typeof(UserImplementation));
然后您可以使用以下方法调用此方法:
method.Invoke(this, new object[0]);
这应该返回DbSet<UserImplementation>
类型的对象,然后可以调用.Cast&lt;&gt;()方法。
答案 1 :(得分:2)
替换
nonGeneric.Cast<IUser>();
通过
Enumerable.Cast<IUser>(nonGeneric);
答案 2 :(得分:0)
好的,我对实体框架一无所知,但是从查看文档
http://msdn.microsoft.com/en-us/library/gg696521%28v=vs.103%29.aspx
DbSet<TEntity> item = DbContext.Set<TEntity>;
所以实际上你的代码与此相同:
DbSet<User> nonGeneric = context.Set<User>();
并获得IUser
DbSet<IUser> nonGeneric = context.Set<User>();
或者
var generic = nonGeneric.Cast<DbSet<IUser>>();
答案 3 :(得分:0)
由于我无法将通用DbSet转换为我使用的类型化DbSet,因此无法使用类型化IQueryable来完成我需要的DbSet相同的功能。
以下是一个扩展程序,可以帮助您:
public static IQueryable<T> GetIQueryableByTableName<T>(this DbContext context, string tableName)
{
var type = Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(t => t.Name == tableName);
if (type == null)
{
throw new Exception("GetIQueryableByTableName received an invalid table name.");
}
return context.GetType().GetMethod("Set", new Type[0]).MakeGenericMethod(type).Invoke(context, new object[0]) as IQueryable<T>;
}