Let me explain my question through an example:
I have the following entities:
public class Project
{
public int ProjectId { get; set; }
public string ProjectName { get; set; }
public int? BranchId { get; set; }
public virtual Branch Branch { get; set; }
}
public class Branch
{
public int BranchId { get; set; }
public string BranchName { get; set; }
public virtual ICollection<Project> Projects { get; set; }
public Branch()
{
Projects = new ObservableCollection<Project>();
}
}
I have my DbContext as:
public class MyContext : DbContext
{
public DbSet<Project> Projects { get; set; }
public DbSet<Branch> Branches { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Branch>().HasMany<Project>(b => b.Projects).WithOptional(p => p.Branch).HasForeignKey(p => p.BranchId);
}
}
The Branch and Project entities are added / updated / deleted from different contexts. Project comes into existence without a Branch, but after some workflow, the Project needs to be attached to a Branch from the list of Branches given.
If I try this, it works fine:
private void ForeignKeyTest()
{
using (var db = new MyContext())
{
var project = db.Projects.Find(1);
project.BranchId = 8;
db.SaveChanges();
var msg = project.Branch.BranchName; //call to the Branch after save
MessageBox.Show(msg);
}
}
But, if I try this, I get a NullReference Exception:
private void ForeignKeyTest()
{
using (var db = new MyContext())
{
var project = db.Projects.Find(1);
project.BranchId = 8;
var msg = project.Branch.BranchName; //call to the Branch before save
db.SaveChanges();
MessageBox.Show(msg);
}
}
Now, I understand this is sort of expected behaviour because the Branch property of Project is not initialized. But my question is, how to force update the Branch after getting the BranchId before the Project is saved (some processing with the Project still remains at this point, so cannot save just to get to the Branch). Hope I explained myself clearly.
There are at least two way to accomplish the goal.
First is to explicitly force (re)loading the navigation property in question by using the DbReferenceEntry<TEntity, TProperty>.Load method:
var project = db.Projects.Find(1);
project.BranchId = 8;
db.Entry(project).Reference(e => e.Branch).Load(); // <--
var branchName = project.Branch.Name;
Second is to force updating all modified navigation properties by using the DbChangeTracker.DetectChanges method:
var project = db.Projects.Find(1);
project.BranchId = 8;
db.ChangeTracker.DetectChanges(); // <--
var branchName = project.Branch.Name;
Both methods work in your scenario, but the first is more reliable - works with or without proxies and also for new entities (e.g. db.Projects.Add).
Related
I have a Kalem Entity with a collection of DigerKalemMaliyetleri property, which is a collection of MaliyetBirimi objects. DigerKalemMaliyetleri is of JSON type and stored at the same table as a JSON column.
public class Kalem
{
public int Id { get; set; }
[Column(TypeName = "json")]
public ICollection<MaliyetBirimi> DigerKalemMaliyetleri { get; set; }
}
public class MaliyetBirimi
{
public int? DovizCinsi { get; set; }
public decimal? Maliyet { get; set; }
}
When I try to update entity with only DigerKalemMaliyetleri property changed:
DataContext.Entry<Kalem>(first).CurrentValues.SetValues(second);
SQL Update command isn't executed and database record is not updated.
How could I update the entity without explicitly setting DigerKalemMaliyetleri property?
Regards
I had the same problem, you cann't actually use SetValues to update navigation property, you nead instead use DataContext.Update(YourNewObj) and then DataContext.SaveChanges();, or if you want to use SetValues approach, you need:
-Get the exist entry
Kalem existObj = DataContext.Kalems.Find(YourNewObj.Id);
-Loop in navigations of updating entry and the existing one to set the values of updating entry:
foreach(var navObj in DataContext.Entry(YourNewObj).Navigations)
{
foreach(var navExist in DatatContext.Entry(existObj).Navigations)
{
if(navObj.Metadata.Name == navExist.MetaData.Name)
navExist.CurrentValue = navObj.CurrentValue;
}
}
-Update also changes of direct properties:
DataContext.Entry(existObj).CurrentValues.SetValues(YourNewObj);
-Save your Updating:
DataContext.SaveChanges();
You can also check if you need to load your Navigations before going in foreach loop, otherwise you will get an error.
Please if you see beter scenario, correct me.
It's hard to know exactly what you're doing without a complete code sample. Note also that you're trying to set all properties of first from second, including e.g. the Id, which is probably not what you want.
Here's a complete code sample which works for me:
await using (var ctx = new BlogContext())
{
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();
ctx.Kalem.Add(new()
{
DigerKalemMaliyetleri = new List<MaliyetBirimi>()
{
new() { DovizCinsi = 1, Maliyet = 2 }
}
});
await ctx.SaveChangesAsync();
}
await using (var ctx = new BlogContext())
{
var first = ctx.Kalem.Find(1);
var second = new Kalem
{
DigerKalemMaliyetleri = new List<MaliyetBirimi>()
{
new() { DovizCinsi = 3, Maliyet = 4 }
}
};
ctx.Entry(first).Property(k => k.DigerKalemMaliyetleri).CurrentValue = second.DigerKalemMaliyetleri;
await ctx.SaveChangesAsync();
}
public class BlogContext : DbContext
{
public DbSet<Kalem> Kalem { get; set; }
static ILoggerFactory ContextLoggerFactory
=> LoggerFactory.Create(b => b.AddConsole().AddFilter("", LogLevel.Information));
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseNpgsql(#"Host=localhost;Username=test;Password=test")
.EnableSensitiveDataLogging()
.UseLoggerFactory(ContextLoggerFactory);
}
public class Kalem
{
public int Id { get; set; }
[Column(TypeName = "json")]
public ICollection<MaliyetBirimi> DigerKalemMaliyetleri { get; set; }
}
public class MaliyetBirimi
{
public int? DovizCinsi { get; set; }
public decimal? Maliyet { get; set; }
}
I want to implement something similar to lazy loading, but don't understand how to implement that. I want to force entity framework core include navigation property for all queries for type which implements my interface
public interface IMustHaveOrganisation
{
Guid OrganisationId { get; set; }
Organisation Organisation { get; set; }
}
public class MyEntity : IMustHaveOrganisation {
public Guid OrganisationId { get; set; }
public virtual Organisation Organisation { get; set; }
}
Without lazy loading I need to add .Include(x=>x.Organisation) to each query literally , and I can't use implementation of lazy loading provided by Microsoft. I need kind of custom implementation of that with loading just one property.
Or even force DbContext somehow to Include that property, it also fine for me.
How can I achieve that?
You can make this work by rewriting the expression tree, before it gets translated by EF Core.
To make this work in a way, where you don't have to specify anything additional in the query, you can hook into the very beginning of the query pipeline and inject the Include() call as needed.
This can be done, by specifying a custom IQueryTranslationPreprocessorFactory implementation.
The following fully working console project demonstrates this approach:
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
namespace IssueConsoleTemplate
{
public class Organisation
{
public int OrganisationId { get; set; }
public string Name { get; set; }
}
public interface IMustHaveOrganisation
{
int OrganisationId { get; set; }
Organisation Organisation { get; set; }
}
public class MyEntity : IMustHaveOrganisation
{
public int MyEntityId { get; set; }
public string Name { get; set; }
public int OrganisationId { get; set; }
public virtual Organisation Organisation { get; set; }
}
public class CustomQueryTranslationPreprocessorFactory : IQueryTranslationPreprocessorFactory
{
private readonly QueryTranslationPreprocessorDependencies _dependencies;
private readonly RelationalQueryTranslationPreprocessorDependencies _relationalDependencies;
public CustomQueryTranslationPreprocessorFactory(
QueryTranslationPreprocessorDependencies dependencies,
RelationalQueryTranslationPreprocessorDependencies relationalDependencies)
{
_dependencies = dependencies;
_relationalDependencies = relationalDependencies;
}
public virtual QueryTranslationPreprocessor Create(QueryCompilationContext queryCompilationContext)
=> new CustomQueryTranslationPreprocessor(_dependencies, _relationalDependencies, queryCompilationContext);
}
public class CustomQueryTranslationPreprocessor : RelationalQueryTranslationPreprocessor
{
public CustomQueryTranslationPreprocessor(
QueryTranslationPreprocessorDependencies dependencies,
RelationalQueryTranslationPreprocessorDependencies relationalDependencies,
QueryCompilationContext queryCompilationContext)
: base(dependencies, relationalDependencies, queryCompilationContext)
{
}
public override Expression Process(Expression query)
{
query = new DependenciesIncludingExpressionVisitor().Visit(query);
return base.Process(query);
}
}
public class DependenciesIncludingExpressionVisitor : ExpressionVisitor
{
protected override Expression VisitConstant(ConstantExpression node)
{
// Call Include("Organisation"), if SomeEntity in a
// DbSet<SomeEntity> implements IMustHaveOrganisation.
if (node.Type.IsGenericType &&
node.Type.GetGenericTypeDefinition() == typeof(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable<>) &&
node.Type.GenericTypeArguments.Length == 1 &&
typeof(IMustHaveOrganisation).IsAssignableFrom(node.Type.GenericTypeArguments[0]))
{
return Expression.Call(
typeof(EntityFrameworkQueryableExtensions),
nameof(EntityFrameworkQueryableExtensions.Include),
new[] {node.Type.GenericTypeArguments[0]},
base.VisitConstant(node),
Expression.Constant(nameof(IMustHaveOrganisation.Organisation)));
}
return base.VisitConstant(node);
}
}
public class Context : DbContext
{
public DbSet<MyEntity> MyEntities { get; set; }
public DbSet<Organisation> Organisations { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
// Register the custom IQueryTranslationPreprocessorFactory implementation.
// Since this is a console program, we need to create our own
// ServiceCollection for this.
// In an ASP.NET Core application, the AddSingleton call can just be added to
// the general service configuration method.
var serviceProvider = new ServiceCollection()
.AddEntityFrameworkSqlServer()
.AddSingleton<IQueryTranslationPreprocessorFactory, CustomQueryTranslationPreprocessorFactory>()
.AddScoped(
s => LoggerFactory.Create(
b => b
.AddConsole()
.AddFilter(level => level >= LogLevel.Information)))
.BuildServiceProvider();
optionsBuilder
.UseInternalServiceProvider(serviceProvider) // <-- use our ServiceProvider
.UseSqlServer(#"Data Source=.\MSSQL14;Integrated Security=SSPI;Initial Catalog=62849896")
.EnableSensitiveDataLogging()
.EnableDetailedErrors();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<MyEntity>(
entity =>
{
entity.HasData(
new MyEntity {MyEntityId = 1, Name = "First Entity", OrganisationId = 1 },
new MyEntity {MyEntityId = 2, Name = "Second Entity", OrganisationId = 1 },
new MyEntity {MyEntityId = 3, Name = "Third Entity", OrganisationId = 2 });
});
modelBuilder.Entity<Organisation>(
entity =>
{
entity.HasData(
new Organisation {OrganisationId = 1, Name = "First Organisation"},
new Organisation {OrganisationId = 2, Name = "Second Organisation"});
});
}
}
internal static class Program
{
private static void Main()
{
using var context = new Context();
context.Database.EnsureDeleted();
context.Database.EnsureCreated();
var myEntitiesWithOrganisations = context.MyEntities
.OrderBy(i => i.MyEntityId)
.ToList();
Debug.Assert(myEntitiesWithOrganisations.Count == 3);
Debug.Assert(myEntitiesWithOrganisations[0].Name == "First Entity");
Debug.Assert(myEntitiesWithOrganisations[0].Organisation.Name == "First Organisation");
}
}
}
Even though no explicit Include() is being made in the query in Main(), the following SQL is being generated, that does join and retrieve the Organisation entities:
SELECT [m].[MyEntityId], [m].[Name], [m].[OrganisationId], [o].[OrganisationId], [o].[Name]
FROM [MyEntities] AS [m]
INNER JOIN [Organisations] AS [o] ON [m].[OrganisationId] = [o].[OrganisationId]
ORDER BY [m].[MyEntityId]
This should be a simple one involving EF Code first but I can't wrap my head around the documentation and all the examples I am finding are from older versions. I am working with the latest (4.1).
Anyway I have some models like:
public class Foo
{
public int ID { get; set; }
public Bar Bar { get; set; }
}
public class Bar
{
public int ID { get; set; }
public string Value { get; set; }
}
I used some scaffolding with Asp.Net MVC to create my controllers/repositories and when I create a 'Foo' object, it also creates a 'Bar' object even though I set the 'Bar' property from something stored in the database.
public class FooViewModel
{
public int ID { get; set; }
public int BarID { get; set; }
}
public ActionResult Create(FooViewModel foo)
{
var entity = new Foo()
{
ID = foo.ID,
Bar = _barRepository.Find(foo.BarID)
};
_fooRepository.InsertOrUpdate(entity);
_fooRepository.Save();
// more stuff
}
How can I use fluent syntax for EF in order to stop it from creating a new 'Bar' row in the database?
Update
Here is the generated repository code:
public void InsertOrUpdate(Foo foo)
{
if (foo.ID == default(int)) {
// New entity
context.Foo.Add(foo);
} else {
// Existing entity
context.Foo(foo).State = EntityState.Modified;
}
}
public void Save()
{
context.SaveChanges();
}
your _fooRepository and _barRepository need to share same DB context instance. If the are using two instances the Bar will be in added state.
The problem must be somewhere in your repository layer - using the same model directly with EF 4.1 produces the expected result - a new row in the Foos table with a bar FK column pointing to the existing Bar.
I have a parent object book, and a property of that object is publisher. Everytime I ad a book, it is adding a new publisher, even if the publisher already exists. Can someone tell me how to add the book and instead of adding the publisher again, just reference an existing one? The code i am using is below... Thanks in advance!
public class Book
{
public int BookID { get; set; }
public string Title { get; set; }
public string Description { get; set; }
public DateTime CreateDate { get; set; }
public virtual Publisher Publisher { get; set; }
}
public class Publisher
{
public int PublisherID { get; set; }
public string Address { get; set; }
}
public class SqlCEDataStore : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Publishers> Publishers { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.IncludeMetadataInDatabase = false;
}
}
public class TimeSinkRepository : IRepository<Book>
{
private static SqlCEDataStore context = new SqlCEDataStore();
public int Add(Book entity)
{
context.Books.Add(entity);
return context.SaveChanges();
}
}
var book = new Book()
{
Title = "New Title",
Description = "New Description",
CreateDate = DateTime.Now,
Publisher = new Publisher() { PublisherID = 1 }
};
var repository = new BookRepository();
var result = repository.Add(book);
The problem is in the line:
Publisher = new Publisher() { PublisherID = 1 }
Object context doesn't know that this is existing publisher. It is newly created entity so Object context will perform insert operation. You have to say object context that the publisher object is not newly created. One way to do that is modification of your Add method:
public int Add(Book entity)
{
context.Books.Add(entity);
// 0 means new one, other values mean existing one
if (entity.Publisher.PublisherID > 0)
{
context.ObjectStateManager.ChangeObjectState(entity.Publisher, EntityState.Unchanged);
}
context.SaveChanges();
}
It you can solve this by making sure the Publisher is attached to Publishers context before adding the Book entity (this way it knows it's a Publisher from the dbcontext and not a new one that it needs to add (again))
context.Publishers.Attach(book.Publisher); // This is only possible if the Publisher is not new
context.Books.Add(book);
the problem is in this line
Publisher = new Publisher() { PublisherID = 1 }
You should do a fetch method so something like this
- Get the Publisher you want from the context (eg where id = 1)
- Set the returned object as the publisher for your new book object
- The context should sort the rest out for you. when you save the book. (no need to mess with the object state manager)
Good luck, if you cant get this working put up some code of it and i will help you though it.
I'm new to EF 4.0, so maybe this is an easy question. I've got VS2010 RC and the latest EF CTP. I'm trying to implement the "Foreign Keys" code-first example on the EF Team's Design Blog, http://blogs.msdn.com/efdesign/archive/2009/10/12/code-only-further-enhancements.aspx.
public class Customer
{
public int Id { get; set;
public string CustomerDescription { get; set;
public IList<PurchaseOrder> PurchaseOrders { get; set; }
}
public class PurchaseOrder
{
public int Id { get; set; }
public int CustomerId { get; set; }
public Customer Customer { get; set; }
public DateTime DateReceived { get; set; }
}
public class MyContext : ObjectContext
{
public RepositoryContext(EntityConnection connection) : base(connection){}
public IObjectSet<Customer> Customers { get {return base.CreateObjectSet<Customer>();} }
}
I use a ContextBuilder to configure MyContext:
{
var builder = new ContextBuilder<MyContext>();
var customerConfig = _builder.Entity<Customer>();
customerConfig.Property(c => c.Id).IsIdentity();
var poConfig = _builder.Entity<PurchaseOrder>();
poConfig.Property(po => po.Id).IsIdentity();
poConfig.Relationship(po => po.Customer)
.FromProperty(c => c.PurchaseOrders)
.HasConstraint((po, c) => po.CustomerId == c.Id);
...
}
This works correctly when I'm adding new Customers, but not when I try to retrieve existing Customers. This code successfully saves a new Customer and all its child PurchaseOrders:
using (var context = builder.Create(connection))
{
context.Customers.AddObject(customer);
context.SaveChanges();
}
But this code only retrieves Customer objects; their PurchaseOrders lists are always empty.
using (var context = _builder.Create(_conn))
{
var customers = context.Customers.ToList();
}
What else do I need to do to the ContextBuilder to make MyContext always retrieve all the PurchaseOrders with each Customer?
You could also use:
var customers = context.Customers.Include("PurchaseOrders").ToList();
Or enable LazyLoading in the ContextOptions :
context.ContextOptions.LazyLoadingEnabled = true;
Just be careful with deferred loading if you are serializing the objects or you may end up querying the entire database.
Well the solution turned out to be simple, as I suspected it might. I called the context.LoadProperty() method for each individual customer:
using (var context = _builder.Create(_conn))
{
var customers = context.Customers.ToList();
foreach (var customer in customers)
{
context.LoadProperty<Customer>(customer, c => c.PurchaseOrders);
}
return customers;
}