context.ObjectStateManager, assembly reference is missing in Entity Framework - entity-framework

I cant figure out what assembly refrence am i missing? To evade this error for my upsert logic in entity framework? Might be an old question but unable to find a solution for my project.
- Am following a Code First Approach in EF6.2.0
Please refer the picture i have attached for your reference.
/*Code Attached for reference as well */
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Net;
using System.Net.Http;
using System.Configuration;
using System.Threading.Tasks;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
namespace ServerlessCoding
{
public static class EF6AddModifyLogic
{
[FunctionName("EF6AddModifyLogic")]
public static async Task<HttpResponseMessage> Run([HttpTrigger(AuthorizationLevel.Admin, "get", "post", Route = null)]HttpRequestMessage req, TraceWriter log)
{
log.Info("C# HTTP trigger function processed a request.");
// Get request body
dynamic data = await req.Content.ReadAsAsync<object>();
string name = data?.name;
var connectionString = ConfigurationManager.AppSettings["SqlConnection"];
// var connectionString = ConfigurationManager.ConnectionStrings["SqlConnection"].ConnectionString; // Azure Serverless Code
using (var context = new PersonDbContext(connectionString))
{
// If you can't decide existance of the object by its Id you must exectue lookup query:
var person = new Person { Id = 1, Name = "Foo", Age = 32 };
var idVar = person.Id;
if (await context.Persons.AnyAsync(e => e.Id == idVar))
{
context.Persons.Attach(person); // you can now attach your person object to this context
context.ObjectStateManager.ChangeObjectState(person, System.Data.EntityState.Modified);
}
else
{
context.Persons.Add(person);
}
context.SaveChanges();
}
return name == null
? req.CreateResponse(HttpStatusCode.BadRequest, "Please pass a name on the query string or in the request body")
: req.CreateResponse(HttpStatusCode.OK, "Hello " + name);
}
// POCO Class - This should match the SQL table definition.
public class Person
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
}
// Context Class for creating the tables.
public partial class PersonDbContext : DbContext
{
public PersonDbContext(string cs) : base(cs) { }
public DbSet<Person> Persons { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
}
}

Related

Entity Framework Core Global Dynamic Query Filter

we are using ef core 3.1
And we want to use dynamic query filter,
I tried sample implementation but did not work correctly we expected, filtering always same tenant id,i tried to explain at below
public class TestDbContext : DbContext
{
public DbSet<TenantUser> TenantUsers { get; set; }
private readonly ITenantProvider _tenantProvider;
private Guid? TenantId => _tenantProvider.TenantId;
public TestDbContext (DbContextOptions<TestDbContext > options, ITenantProvider tenantProvider) : base(options)
{
_tenantProvider = tenantProvider;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<TenantUser>()
.HasQueryFilter(p => EF.Property<Guid>(p, "TenantId") == TenantId);
}
}
ITenantProvider returns TenantId from HttpContext headers
this code filtering always same tenant id from coming first request
Update:
public class TenantProvider : ITenantProvider
{
private readonly IHttpContextAccessor _httpContextAccessor;
public TenantProvider(IHttpContextAccessor httpContextAccessor)
{
_httpContextAccessor = httpContextAccessor;
}
public Guid? TenantId
{
get
{
if (_httpContextAccessor.HttpContext.Request.Headers.TryGetValue(HeaderNames.TenantId, out var tenantId) &&
Guid.TryParse(tenantId, out Guid parsedTenantId))
{
return parsedTenantId;
}
return null;
}
}
}
For example
First Request TenantId = 60000000-0000-0000-0000-000000000000
This filter => 60000000-0000-0000-0000-000000000000
Second Request TenantId = 10000000-0000-0000-0000-000000000000
This filter => 60000000-0000-0000-0000-000000000000
We tried something similar like that a few years ago. Main problem is here that OnModelCreating method only triggered once. So HasQueryFilter works once and gets the current tenant id from provider and it applies to all queries the same tenant id.
You should also implement a custom IModelCacheKeyFactory
public class MyModelCacheKeyFactory : IModelCacheKeyFactory
{
public object Create(DbContext context)
{
if (context is TestDbContext testDbContext)
{
return (context.GetType(), testDbContext.TenantId);
}
return context.GetType();
}
}
And then, you need to replace like this
var builder = new DbContextOptionsBuilder<TestDbContext>();
builder.ReplaceService<IModelCacheKeyFactory, MyModelCacheKeyFactory>();
var context = new TestDbContext(builder.Options);
Reference:
https://learn.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.infrastructure.imodelcachekeyfactory

EF Core 3.1: Navigation property doesn't lazy load entities when calling the backing field first

I am using EF Core 3.1.7. The DbContext has the UseLazyLoadingProxies set. Fluent API mappings are being used to map entities to the database. I have an entity with a navigation property that uses a backing field. Loads and saves to the database seem to work fine except for an issue when accessing the backing field before I access the navigation property.
It seems that referenced entities don't lazy load when accessing the backing field. Is this a deficiency of the Castle.Proxy class or an incorrect configuration?
Compare the Student class implementation of IsRegisteredForACourse to the IsRegisteredForACourse2 for the behavior in question.
Database tables and relationships.
Student Entity
using System.Collections.Generic;
namespace EFCoreMappingTests
{
public class Student
{
public int Id { get; }
public string Name { get; }
private readonly List<Course> _courses;
public virtual IReadOnlyList<Course> Courses => _courses.AsReadOnly();
protected Student()
{
_courses = new List<Course>();
}
public Student(string name) : this()
{
Name = name;
}
public bool IsRegisteredForACourse()
{
return _courses.Count > 0;
}
public bool IsRegisteredForACourse2()
{
//Note the use of the property compare to the previous method using the backing field.
return Courses.Count > 0;
}
public void AddCourse(Course course)
{
_courses.Add(course);
}
}
}
Course Entity
namespace EFCoreMappingTests
{
public class Course
{
public int Id { get; }
public string Name { get; }
public virtual Student Student { get; }
protected Course()
{
}
public Course(string name) : this()
{
Name = name;
}
}
}
DbContext
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace EFCoreMappingTests
{
public sealed class Context : DbContext
{
private readonly string _connectionString;
private readonly bool _useConsoleLogger;
public DbSet<Student> Students { get; set; }
public DbSet<Course> Courses { get; set; }
public Context(string connectionString, bool useConsoleLogger)
{
_connectionString = connectionString;
_useConsoleLogger = useConsoleLogger;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
ILoggerFactory loggerFactory = LoggerFactory.Create(builder =>
{
builder
.AddFilter((category, level) =>
category == DbLoggerCategory.Database.Command.Name && level == LogLevel.Information)
.AddConsole();
});
optionsBuilder
.UseSqlServer(_connectionString)
.UseLazyLoadingProxies();
if (_useConsoleLogger)
{
optionsBuilder
.UseLoggerFactory(loggerFactory)
.EnableSensitiveDataLogging();
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Student>(x =>
{
x.ToTable("Student").HasKey(k => k.Id);
x.Property(p => p.Id).HasColumnName("Id");
x.Property(p => p.Name).HasColumnName("Name");
x.HasMany(p => p.Courses)
.WithOne(p => p.Student)
.OnDelete(DeleteBehavior.Cascade)
.Metadata.PrincipalToDependent.SetPropertyAccessMode(PropertyAccessMode.Field);
});
modelBuilder.Entity<Course>(x =>
{
x.ToTable("Course").HasKey(k => k.Id);
x.Property(p => p.Id).HasColumnName("Id");
x.Property(p => p.Name).HasColumnName("Name");
x.HasOne(p => p.Student).WithMany(p => p.Courses);
});
}
}
}
Test program which demos the issue.
using Microsoft.Extensions.Configuration;
using System;
using System.IO;
using System.Linq;
namespace EFCoreMappingTests
{
class Program
{
static void Main(string[] args)
{
string connectionString = GetConnectionString();
using var context = new Context(connectionString, true);
var student2 = context.Students.FirstOrDefault(q => q.Id == 5);
Console.WriteLine(student2.IsRegisteredForACourse());
Console.WriteLine(student2.IsRegisteredForACourse2()); // The method uses the property which forces the lazy loading of the entities
Console.WriteLine(student2.IsRegisteredForACourse());
}
private static string GetConnectionString()
{
IConfigurationRoot configuration = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json")
.Build();
return configuration["ConnectionString"];
}
}
}
Console Output
False
True
True
When you declare a mapped property in an EF entity as virtual, EF generates a proxy which is capable of intercepting requests and assessing whether the data needs to be loaded. If you attempt to use a backing field before that virtual property is accessed, EF has no "signal" to lazy load the property.
As a general rule with entities you should always use the properties and avoid using/accessing backing fields. Auto-initialization can help:
public virtual IReadOnlyList<Course> Courses => new List<Course>().AsReadOnly();

EfCore 3 and Owned Type in same table, How do you set owned instance

How do you set owned type instance with efcore3?
In following example an exception is raised
'The entity of type 'Owned' is sharing the table 'Principals' with
entities of type 'Principal', but there is no entity of this type with
the same key value that has been marked as 'Added'.
If I set Child property inline savechanges doesn't update child properties
I can't find any example about this. I tried with several efcore3 builds and daily builds. What didn't I understand?
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
namespace TestEF
{
class Program
{
static void Main(string[] args)
{
var id = Guid.NewGuid();
using (var db = new Ctx())
{
db.Database.EnsureDeleted();
db.Database.EnsureCreated();
var p = new Principal {Id = id};
db.Principals.Add(p);
db.SaveChanges();
}
using (var db = new Ctx())
{
var p = db.Principals.Single(o => o.Id == id);
p.Child = new Owned();
p.Child.Prop1 = "Test2";
p.Child.Prop2 = "Test2";
db.SaveChanges();
}
}
public class Principal
{
public Guid Id { get; set; }
public Owned Child { get; set; }
}
public class Owned
{
public string Prop1 { get; set; }
public string Prop2 { get; set; }
}
public class Ctx : DbContext
{
public DbSet<Principal> Principals { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Data Source=.;Initial Catalog=TestEF;Trusted_Connection=True;Persist Security Info=true");
}
protected override void OnModelCreating(ModelBuilder mb)
{
var emb = mb.Entity<Principal>();
emb
.OwnsOne(o => o.Child, cfg =>
{
cfg.Property(o => o.Prop1).HasMaxLength(30);
//cfg.WithOwner();
});
}
}
}
}
This is a bug, filed at https://github.com/aspnet/EntityFrameworkCore/issues/17422
As a workaround you could make the child appear as modified:
db.ChangeTracker.DetectChanges();
var childEntry = db.Entry(p.Child);
childEntry.State = EntityState.Modified;
db.SaveChanges();
Try this instead:
_context.Update(entity);
This will update all the owned properties so SaveChanges() updates them, too.

entity framework core orderby guid failing to order properly

I'm trying to order by a Guid in EF Core with a relational database and its not ordering. Is there something I'm doing wrong or could this be an issue with EF Core?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using NUnit.Framework;
namespace TestName
{
public class BoxDbContext : DbContext
{
public BoxDbContext(
DbContextOptions<BoxDbContext> options) : base(options)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Box>().HasKey(x => x.Id);
modelBuilder.Entity<Box>().Property(t => t.Id).ValueGeneratedOnAdd();
base.OnModelCreating(modelBuilder);
}
}
public class Box
{
public Guid Id { get; set; }
public Guid SubId { get; set; }
}
[TestFixture]
public class TestClass
{
private SqliteConnection SqliteConnection { get; set; }
private DbContextOptions<BoxDbContext> Options => new DbContextOptionsBuilder<BoxDbContext>()
.UseSqlite(SqliteConnection)
.EnableSensitiveDataLogging()
.Options;
private DbContext GetDbContext()
{
BoxDbContext dbContext = new BoxDbContext(Options);
dbContext.Database.EnsureCreated();
return dbContext;
}
[SetUp]
public void DbSetup()
{
SqliteConnectionStringBuilder sqliteConnectionStringBuilder = new SqliteConnectionStringBuilder
{
Mode = SqliteOpenMode.Memory,
Cache = SqliteCacheMode.Private
};
SqliteConnection = new SqliteConnection(sqliteConnectionStringBuilder.ToString());
SqliteConnection.Open();
}
[TearDown]
public void DbTearDown()
{
SqliteConnection.Close();
}
[Test]
public async Task OrderByGuid()
{
List<Guid> subIds = new List<Guid>
{
Guid.Parse("901CAB07-315F-4594-A5C6-C37725643DB8"),
Guid.Parse("FA1760E7-27F4-4F8B-9205-44ACF2358044"),
Guid.Parse("0C434803-0004-4894-8E29-597AA8BCF8E2"),
Guid.Parse("C7E76CF2-35D1-4CF8-8A67-83F41842F052"),
Guid.Parse("1D6F9038-B5B3-4559-9480-3A2651E52623"),
};
using (DbContext dbContext = GetDbContext())
{
foreach (Guid subId in subIds)
{
dbContext.Set<Box>().Add(new Box {SubId = subId});
}
await dbContext.SaveChangesAsync();
}
IList<Box> boxs;
using (DbContext approvalsDbContext = GetDbContext())
{
boxs = await approvalsDbContext
.Set<Box>()
.OrderByDescending(x => x.SubId)
.ToListAsync();
}
Assert.That(boxs.Count, Is.EqualTo(subIds.Count));
Assert.That(boxs.ToArray()[0].SubId, Is.EqualTo(subIds[1]));
Assert.That(boxs.ToArray()[1].SubId, Is.EqualTo(subIds[3]));
Assert.That(boxs.ToArray()[2].SubId, Is.EqualTo(subIds[0]));
Assert.That(boxs.ToArray()[3].SubId, Is.EqualTo(subIds[4]));
Assert.That(boxs.ToArray()[4].SubId, Is.EqualTo(subIds[2]));
}
}
}
Thanks,
Chris
So I raised this with the EF Core team who said this was intended behaviour for SQLite as it doesn’t have a representation for Guid. https://github.com/aspnet/EntityFrameworkCore/issues/10198#issuecomment-340930189

Adding WebApi to an existing MVC4 app that uses Entity framework

I've been going around with this for a few days now. I have an existing MVC 4 project that uses entity framework for database creation. The app works as intended but I have a new requirement to add web api to this site. This is a database of quotes and the requirement is to return a simple quote that only contains limited information of the full database entry.
My original model:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
namespace Exercise4.Models
{
public class Quote
{
public int QuoteID { get; set; }
[Required (ErrorMessage = "A Category Field Must be selected or a New one must be Created before continuing")]
public int CategoryID { get; set; }
[Display (Name="Quotation")]
[Required (ErrorMessage = "A Quotation is Required")]
public string QName { get; set; }
[Display (Name= "Author")]
[Required (ErrorMessage = "An Authors Name is Required\n Use 'Unknown' if needed")]
public string QAuthor { get; set; }
[Display (Name = "Category")]
public virtual Category category { get; set; }
[DisplayFormat(DataFormatString = "{0:d}")]
public DateTime Date { get; set; }
public int UserId { get; set; }
}
}
The Simple Quote Model
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;
namespace Exercise4.Models
{
public class SimpleQuote
{
public int Id { get; set; }
public string Quote { get; set; }
public string Author { get; set; }
public string Category { get; set; }
}
}
The Context (*Note the SimpleQuote entry was added automagicly when I scaffold the new QuoteApiController)
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Web;
namespace Exercise4.Models
{
public class QuoteContext : DbContext
{
public DbSet<Quote> Quotes { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<UserProfile> UserIds { get; set; }
public QuoteContext()
{
Configuration.ProxyCreationEnabled = false;
}
public DbSet<SimpleQuote> SimpleQuotes { get; set; }
}
}
This returns an error when accessing the /api/quoteapi/ page with
The 'ObjectContent`1' type failed to serialize the response body for content type 'application/xml; charset=utf-8'.
Obviously this error occurs because it is trying to return a SimpleQuote Object that doesn't exist in the database.
The API Controller that was created.
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web;
using System.Web.Http;
using Exercise4.Models;
namespace Exercise4.Controllers
{
public class QuoteApiController : ApiController
{
private QuoteContext db = new QuoteContext();
// GET api/QuoteApi
public IEnumerable<SimpleQuote> GetSimpleQuotes()
{
return db.SimpleQuotes.AsEnumerable();
}
// GET api/QuoteApi/5
public SimpleQuote GetSimpleQuote(int id)
{
SimpleQuote simplequote = db.SimpleQuotes.Find(id);
if (simplequote == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
return simplequote;
}
protected override void Dispose(bool disposing)
{
db.Dispose();
base.Dispose(disposing);
}
}
}
Where am I going awry. I can't return a model that doesn't exist in the database. If I change the call in the api to return a Quote model that works but I only need to return the quote, author and category as strings. Would I just return the Quote object in the controller, pull out the information I need and then return the SimpleQuote object? Not sure how to do that. Any suggestions?
You mentioned scaffolding, are you using Code First to create your Database?
Also you only want the SimpleQuote for returning the information, it looks like its added to your DB context as a table. When what you really want is to pull the data from the Quote Table and build or extract the information you want and return just that. If you don’t have to return a SimpleQuote Object and just return a string you could write something very simplistic like this.
public HttpResponseMessage GetSimpleQuote(int id)
{
var quote = db.Quotes.Find(id);
if (quote == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
var output += "ID: " + quote.Id + " Quote: " + quote.Quote + " Author: " + quote.Author + " Category: " + quote.Category ;
var response = new HttpResponseMessage();
response.Content = new StringContent(output);
response.Content.Headers.ContentType = new MediaTypeHeaderValue("text/plain");
return response;
}
I 1+ and Accepted #CHammond's response for putting me on the right track. I do need to return a SimpleQuote object. Here is the code I used.
public SimpleQuote GetSimpleQuote(int id)
{
var quote = db.Quotes.Find(id);
if (quote == null)
{
throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}
SimpleQuote output = new SimpleQuote();
output.Quote = quote.QName;
output.Author = quote.QAuthor;
output.Category = db.Categories.Find(quote.CategoryID).CatName;
return output;
}