i have some simple Export/Import Scenario that i can't figur out why this not work. in my scenario i have one WPF app and 2 ClassLibrary, in Classlib1 i have one interface named ITestEx1 as below :
public interface ITestEx1
{
string Name {get; set;}
}
and 1 derived class Named (TestEx1) as below :
using System.ComponentModel.Composition;
[Export(typeof(ITestEx1))]
public class TestEx1 : ITestEx
{
public Name {get; set;}
}
as you can see this class exported as type of ITestEx1, now in Classlib2 i refrenced Classlib1 and have one class as below :
using System.ComponentModel.Composition;
using Classlib1;
public class TestMEF
{
[Import(typeof(ITestEx1))]
public ITestEx1 TestE {get; set;}
}
and in main WPF application i refrenced both Classlib1 and ClassLib2 and in constructor of MainWindow.xaml i wrote this code for initializing MEF :
private CompositionContainer _container;
...
public MainWindow()
{
InitializeComponent();
var catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(typeof(MainWindow).Assembly));
catalog.Catalogs.Add(new AssemblyCatalog(typeof(TestEx1).Assemble));
_container = new CompositionContainer(catalog)
_container.ComposeParts(this);
}
... and in button click i have this :
{
...
var aa = new TestMEF();
aa.TestE.Name = "abc"; // Error, object null refrence
}
Please Help Me to solve this problem
If you create TestMEF() yourself the imports won't be satisfied. You have to request it from the container:
var aa = _container.GetExport<ITestEx>();
Alternatively, you could add the following property to the MainWindow class, and it would get populated when you called _container.ComposeParts(this) or _container.SatisfyImportsOnce(this):
[Import]
public ITestEx AA { get; set; }
You have to grab the instance out of the container, not create it yourself. In this case, you would need a factory (in the container) to create the objects on-the-fly.
Related
READ THE EDIT!
I have two Entities :
public class Principal {
public Guid Id { get; private set; }
public Collection<Dependant> Dependants { get; init; } = new();
public Principal() { }
}
public class Dependant{
public Guid Id { get; private set; }
public Guid PrincpalId { get; private set; }
public Principal Principal{ get; private set; }
public Dependant() { }
}
I access Principal through a repository :
internal class PrincipalsRepository {
private readonly DbSet<Princpal> db;
public PrincipalsRepository (DbSet<Princpal> db) {
this.db = db;
}
public async Task AddAsync(Principal p) {
await this.db.AddAsync(p).ConfigureAwait(false);
}
public async Task<Principal>> GetByIdAsync(Guid id) {
//Notice how there's no Include here!
return await db
.FirstOrDefaultAsync(p => p.Id == id)
.ConfigureAwait(false);
}
}
I configure them like this :
public void Configure(EntityTypeBuilder<Principal > builder) {
builder
.ToTable("Principals")
.HasKey(p => p.Id);
builder
.Navigation(p => p.Dependants)
.AutoInclude(false); //THIS!!!!!
builder
.OwnsMany(p =>
p.Dependants,
navBuilder => {
navBuilder.ToTable("Dependants");
navBuilder.Property<Guid>("Id"); //Important: without this EF would try to use 'int'
navBuilder.HasKey("Id");
navBuilder
.WithOwner(v => v.Principal)
.HasForeignKey(v => v.PrincipalId);
}
);
}
The repo is used in a DbContext:
//PLEASE NOTE: This code might seem a bit broken to you because it's a trimmed down copy-paste from the real code.
public abstract class MyDatabase<TContext> : DbContext
where TContext : DbContext {
public PrincipalsRepository PrincipalsRepository = new PrincipalsRepository (DbPrincipals);
//This is exposed for unit tests
public DbSet<Principal> DbPrincipals { get; set; }
public MyDatabase(DbContextOptions<TContext> options)
: base(options) {
}
}
I configure an in-memory Db :
//PLEASE NOTE: Not everything is detailed here. It's a copy paste from a bigger code base)
private static Database CreateDatabase() {
var _connection = new SqliteConnection("Filename=:memory:");
_connection.Open();
_contextOptions = new DbContextOptionsBuilder<MyDatabase>()
.UseSqlite(_connection)
.Options;
var context = new MyDatabase(_contextOptions);
return context;
}
I run a unit test where I insert an Principal entity with a Dependant:
// Step 1 : Init
using var context = CreateDatabase();
var repo = new PrincipalsRepository(context.DbPrincipals);
// Step 2 : Insertion
var p = new Principal();
p.Dependants.Add(new Dependant());
await context.PrincipalsRepo.AddAsync(p).ConfigureAwait(false);
await context.SaveChangesAsync().ConfigureAwait(false);
// Step 3 : Read back
var p2 = context.PrincipalsRepo.GetByIdAsync(p.Id).ConfigureAwait(false);
And then...
Assert.Empty(p2!.Dependants); //The unit test fails because I can see that the Dependant has been loaded
What am I doing wrong? Why is it loaded despite me saying "AutoInclude(false)" ?
Note: After adding AutoInclude(false), creating a new migration changed the Db's model snapshot, but the migration itself was empty. Is that normal???
EDIT:
Like #DavidG and #Gert Arnold suggested in the comments, apparently I need to instantiate a brand new DbContext to do the test, because EF is somehow smart enough to pick up that p2 is the "same" as p, and... populates its navigation links (i.e. does the auto Include) without me asking?!?
I absolutely don't understand what's the logic here (in terms of behaviour consistency).
When I change the test and query p2 from a brand new DbContext instance, it works as I would expect it. I.e. it does find the Principal (p2) but its Dependants collection is empty.
Is this documented anywhere, in one form or another? Even as an implicit sentence that seems obvious on some Microsoft help page?
I have a base project, which has some basic entities. Let's use BaseUser as one. In the child project, it references base project and User class inherits from that BaseUser. The only difference is the User class has List<Blogs>, so no additional properties, no change in mapping.
When I query Users, it does not find any because they are created as BaseUser and the query has discriminator value of "User". I don't want to query "BaseUser" because I want the relational property of List<Blogs>.
Is there any way to tell EF to basically treat these classes as one? Is there a better way to handle the split? (obviously base project has no concept of blogs, so cannot move the List to the base)
Some sample classes as requested
/* Base Project (nuget package created) */
public class BaseUser {
public int UserId { get; set; }
public int Name { get; set; }
}
public class BaseContext : DbContext {
public DbSet<BaseUser> BaseUsers {get;set;}
}
public class BaseDataInstaller {
BaseContext _ctx;
public BaseDataInstaller( BaseContext ctx ){
_ctx = ctx;
}
public void Install(){
_ctx.BaseUsers.Add( new BaseUser { Name="Demo User 1" } );
_ctx.BaseUsers.Add( new BaseUser { Name="Demo User 2" } );
_ctx.SaveChanges();
}
}
/* Child Project (consumes nuget package)*/
public class User : BaseUser {
List<Blogs> Blogs { get; set; }
}
public class ProjectContext : BaseContext {
public DbSet<User> Users { get; set; }
}
public class SomeService {
ProjectContext _ctx;
public BaseDataInstaller(ProjectContext ctx){
_ctx = ctx;
}
//Finds 0 users
public void PrintUsers(){
var users = _ctx.Users.ToList();
users.ForEach( u=> Console.WriteLine(u.Name) );
}
//Finds Users
public void PrintBaseUsers(){
var users = _ctx.BaseUsers.ToList();
users.ForEach( u=> Console.WriteLine(u.Name) );
}
}
Comparing the SQL generated, there is a discriminator added
where Discriminator = 'BaseUser' or where Discriminator = 'User'
There are no properties which are different between the two, just the relationship with the blogs.
So is there a way to either make both have the same Discriminator value or another way to solve this?
UPDATE 1
The discriminator only appears if the DbContext knows about BOTH entities. if it only knows about the one, it is happy to map onto the table. Even if the child inherits from the base, it still doesn't need a discriminator. So I think the challenge is to re-work the base so the context doesn't know about the base. This does feel like a workaround though. Maybe the structure should change:
instead of User : BaseUser use a property
User
- int ChildUserId {get; set;}
- BaseUser BaseUser {get; set;}
- SomeObject SomeNavProperty etc
it will mean a new table for each inherited project, but would allow the project to add it's own specific data too...
After I found out that navigation properties does not populate after insert operation in EF Code first , I decide to create a method to reload the navigations. Lets think we have two classes :
public class Book
{
public int Id {get ; set;}
public string Title {get ; set;}
public virtual ICollection<Page> Pages {get ; set;}
}
public class Page
{
public int Id {get ; set;}
public string Content {get ; set;}
public int BookId {get ; set;}
public virtual Book Book {get ; set;}
}
and in the following code I have :
DbContext db = new DbContext()
Page p = new Page();
p.BookId = 1;
p.Content = "Sample Content";
db.Pages.Add(p);
db.SaveChanges();
db.Reload(p, rec => rec.Book); // Here is the wanted method
I tried this one , but I failed
public class DbContext : DbContext
{
///I know this method is wrong, I have no idea to fix it
public T Reload<T>(T Obj, param Func<T,I_DONT_KNOW_WHAT_I_SHOULD_PUT_HERE>[] predicates )
{
foreach(var item in predicates)
{
this.Entry<T>(Obj).Reference(item).Load();
}
}
}
Is there anybody out there to help me complete this method ?
Im note sure I follow what you are trying to do.
But my first though is to recommend
The basic reload form is:
Context.Entry<TPoco>(poco).Reload();
You can also use Include form
var Qpocolist = Context.Set<TPoco>().Include(t=>t.Navprop).Where(t => t.Navprop.xyz=="xyz");
but perhaps it is just passing an array of prop expression you wanted a tip on.
You need to declare the method as generic for Poco and Property.
public void MyFancySetNavs<TPoco, TProp>(TPoco obj, params Expression<Func<TPoco, TProp>>[] navProps) {
foreach (var navPropExp in navProps) {
// do ya thing...
}
}
I change my code to this , so the navigators populate correctly. So I do not need such mentioned method
DbContext db = new DbContext()
Page p = db.Pages.Create();
p.BookId = 1;
p.Content = "Sample Content";
db.Pages.Add(p);
db.SaveChanges();
db.Reload(p, rec => rec.Book);
I have been trying to use MEF on a new project and am having some difficulty getting imports to work, which I cannot explain why. I am following the samples on MSDN and elsewhere, but they are not working for me. This is using MEF 4.0 in a .NET 4 project.
I have defined a simple contract:
public interface ICommand
{
int Execute(string[] args);
}
I implemented some parts and added some metadata:
[Export(typeof(ICommand))]
[ExportMetadata("Name", "init")]
public class InitCommand : ICommand { ... }
[Export(typeof(ICommand))]
[ExportMetadata("Category", "service")]
[ExportMetadata("Name", "start")]
public class StartServiceCommand : ICommand { ... }
I defined the following metadata interface:
public interface ICommandMetadata
{
[DefaultValue(null)]
string Category { get; }
string Name { get; }
}
In my main program, I am creating a catalog-based export provider and then trying to compose the main program object:
internal class Program
{
[ImportMany]
private IEnumerabe<Lazy<ICommand, ICommandMetadata>> commands;
private static int Main(string[] args)
{
var assembly = Assembly.GetExecutingAssembly();
var assemblyExportProvider = new CatalogExportProvider(new AssemblyCatalog(assembly));
var compositionContainer = new CompositionContainer(assemblyExportProvider);
assemblyExportProvider.SourceProvider = compositionContainer;
var batch = new CompositionBatch();
batch.AddPart(this);
compositionContainer.Compose(batch);
return 0;
}
}
When I run the above code, this works as expected and both parts are resolved. When I go a step further and create a custom export attribute, the code stops working. Here's my custom metadata attribute and updated parts:
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
[MetadataAttribute]
public sealed class CommandAttribute : ExportAttribute
{
public CommandAttribute() : base(typeof(ICommand))
{
}
[DefaultValue(null)]
public string Category { get; set; }
public string Name { get; set; }
}
[Command(Name = "init")]
public class InitCommand : ICommand { ... }
[Command(Category = "service", Name = "start")]
public class StartServiceCommand : ICommand { ... }
By applying the custom export attribute, my commands collection in my program is an empty array. I played around with this some more, and I found that if I change the properties in the metadata interface to arrays of strings, the importing works again:
public interface ICommandMetadata
{
[DefaultValue(null)]
string[] Category { get; }
string[] Name { get; }
}
Can anyone tell me if I am doing something wrong? Why does this only work if I use arrays in the metadata interface? Is there a way to make this work without using arrays for the properties?
Thanks in advance for your help.
I am trying the EF5 CodeFirst and cannot get the simple setup to work ;(
I have two classes Foo and Bar where Bar represent lookup table.
public class Foo
{
public int Id { get; set; }
public string Name { get; set; }
public virtual Bar Bar { get; set; }
}
public class Bar
{
public int Id { get; set; }
public string Description { get; set; }
}
public class MyDbContext : DbContext
{
static MyDbContext()
{
Database.SetInitializer<MyDbContext>(null);
}
public MyDbContext(): base("testEF"){}
public DbSet<Foo> Foos { get; set; }
public DbSet<Bar> Bars { get; set; }
}
Now I have created a static class that serves as DataAccess Layer - in real-world application it will be on different physical tier
public static class DataAccess
{
public static Bar GetBarById(int id)
{
using (var db = new MyDbContext())
{
return db.Bars.SingleOrDefault(b => b.Id == id);
}
}
public static Foo InsertFoo(Foo foo)
{
using (var db = new MyDbContext())
{
db.Foos.Add(foo);
db.SaveChanges();
}
return foo;
}
}
I am initializing the DB with seed method:
internal sealed class Configuration : DbMigrationsConfiguration<testEF.MyDbContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(testEF.MyDbContext context)
{
context.Bars.AddOrUpdate(
new Bar { Description = "Bar_1" },
new Bar { Description = "Bar_2" }
);
}
}
This creates two records in Bars table. So far so good...
Here is my Main function
static void Main(string[] args)
{
var bar1 = DataAccess.GetBarById(1);
var foo = new Foo
{
Name = "Foo_1",
Bar = bar1
};
DataAccess.InsertFoo(foo);
}
After the app runes there is a record in the Foos table:
Id Name Bar_Id
1 Foo_1 3
Why Bar_Id is 3? The EF actually inserted new record to Bars table!
Id Description
1 Bar_1
2 Bar_2
3 Bar_1
What I am doing wrong?
UPDATE:
I have found a workaround - to attach Bar property prior to inserting the record:
public static Foo InsertFoo(Foo foo)
{
using (var db = new MyDbContext())
{
db.Bars.Attach(foo.Bar);
db.Foos.Add(foo);
db.SaveChanges();
}
return foo;
}
It is working now but this is more like a hack than a valid solution...
In real-world application the complexity of the objects could become a huge problem.
I am open to better solutions
The problem is that bar1 comes from a different data context. Your InsertFoo method implicitly adds it to the second context by building a relationship with the Foo. You want these two to share a context. So use a single context for the whole scope of the Main method.
The complexity you mention (which I agree with you) is caused by using a static class for your data access component. It forces you to separate your DBContext's across method calls. Instead of doing it that way, why not create a normal class, and build the context in the constructor.
With this, you don't need to attach foo.Bar anymore.
public class DataAccess
{
private MyDbContext _context;
public DataAccess(){
_context = new MyDbContext();
}
public Bar GetBarById(int id)
{
return _context.Bars.SingleOrDefault(b => b.Id == id);
}
public Foo InsertFoo(Foo foo)
{
_context.Foos.Add(foo);
_context.SaveChanges();
return foo;
}
}
There are many ways you can build on and enhance this. You could create an interface for MyDbContext called IDbContext and using a DI framework inject it into this class. Similarly, you could do the same for the DataAccess class and inject that into wherever it's needed.