SQL Server has a column type called "xml".
I was hoping that EF would store properties of type XElement or XDocument in SQL Server as 'xml', but when I try this model:
public class Model
{
/* ... */
XDocument XDocument { get; set; }
}
... I get this error:
The property 'Model.XDocument' is of type 'XDocument' which is
not supported by the current database provider
Googling was suprisingly unhelpful.
Does EF support storing properties of type XDocument or XElement on SQL Server as XML columns?
Related
When running a procedure on EF Core 3 using FromSqlRaw that updates values in the table, EF DOES NOT return the updated values when I query the database for those changed values.
I have been able to reproduce this behavior. To reproduce create a new console app c# with .net core 3.1.
Copy paste the code below into your main Program.cs file:
using System;
using System.Linq;
using Microsoft.Data.SqlClient;
using Microsoft.EntityFrameworkCore;
namespace EfCoreTest
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
// testing proc
var dbContext = new TestContext();
var tables = dbContext.TestTables.ToList();
var updated = dbContext.TestTables
.FromSqlRaw("execute testProc #Id=#Id, #Comments=#Comments", new object[]
{
new SqlParameter("Id", 1),
new SqlParameter("Comments", "testing comments 2"),
})
.ToList();
var again = dbContext.TestTables.ToList();
}
}
public class TestTable
{
public int TestTableId { get; set; }
public string Comment { get; set; }
}
public class TestContext : DbContext
{
public DbSet<TestTable> TestTables { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Server=localhost\SQLEXPRESS;Database=TestDb;Trusted_Connection=True");
}
}
}
Ensure that the following packages are installed:
Microsoft.EntityFrameworkCore
Microsoft.EntityFrameworkCore.Design
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.SqlServer.Design
Change your connection string if necessary.
Run dotnet ef migrations add initial
Run dotnet ef database update
Run the following code in your db:
drop procedure if exists testProc
go
create procedure testProc
#Id int,
#Comments nvarchar(max)
as
begin
update dbo.TestTables
set Comment = #Comments
where TestTableId = #Id;
select * from dbo.TestTables;
end
go
INSERT INTO [dbo].[TestTables]
(Comment) VALUES ('Test Comment');
So when you run the Main program on debug and put a breaker, you'll notice that NONE of the objects return values that were updated by the procedure when go to inspect it. While in debug if you run a select statement on the table you will see that the "Comment" field is indeed updated.
Why is this?
This is not specific to FromSql, but the way EF Core (all versions) tracking queries work.
Here is an excerpt from EF Core How Queries Work documentation topic:
The following is a high level overview of the process each query goes through.
The LINQ query is processed by Entity Framework Core to build a representation that is ready to be processed by the database provider
The result is cached so that this processing does not need to be done every time the query is executed
The result is passed to the database provider
The database provider identifies which parts of the query can be evaluated in the database
These parts of the query are translated to database specific query language (for example, SQL for a relational database)
One or more queries are sent to the database and the result set returned (results are values from the database, not entity instances)
For each item in the result set
If this is a tracking query, EF checks if the data represents an entity already in the change tracker for the context instance
If so, the existing entity is returned
If not, a new entity is created, change tracking is setup, and the new entity is returned
Note the last bullet. What they do is basically an implementation of the so called client wins strategy (as opposed to database wins which you are looking for), and currently there is no way of changing that other than using no-tracking query.
In your example, insert AsNotTracking() somewhere in the queries (before ToList, after dbContext.TestTables - it really doesn't matter because it applies to the whole query), or just
dbContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
and now you'll see the updated values (from your SP call or from other sessions to the same database).
With Code First database creation, how a can one specify a DateTime property to take the postgres timestampz datatype instead of the default timestamp?.
I am storing all dates as UTC, however have an issue with linq queries where the DateTime.Kind is Unspecified. I believe the timestampz should resolve this.
The below code examples attempt to use the 'Column' attribute, this results in an error (DateTime not compatible).
I am using npgsql v3.1.7 and EntityFramework6.Npgsql v 3.1.1
[Column(TypeName="timestampz")]
public DateTime CreatedByDate { get; set; }
[Column(TypeName="NpgsqlDateTime")]
public DateTime CreatedByDate { get; set; }
You can use the TimeStamp attribute in the namespace System.ComponentModel.DataAnnotations to have .NET create a non-nullable Timestamp column in the database table that your entity represents.
[Timestamp]
public Byte[] TimeStamp { get; set; }
Code first allows only one Timestamp property per entity.
Could you help me how can I mapping to any entity to db view?
Scenario is here, We create a view on db with native sql we have to do like this and we want to mapping this view to ours entity.
How can we do that? We try to create an entity with same columns on view but it doesn't work?
Hibernate-specific #Immutable annotation which I use in the following code snippet.
View can be mapped as like below....
#Entity
#Immutable
public class <NAME>View {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#Version
#Column(name = "version")
private int version;
The #Immutable annotation tells Hibernate to ignore all changes on this entity, but you can use it to retrieve data from the database.
List<View> views = em.createQuery("SELECT v FROM View v", View.class).getResultList();
I'm using NHibernate 3.3.3.4000 and SQL Server 2012.
I've searched for and not found an example of how to use mapping-by-code (loquacious) to configure optimistic concurrency using a SQL Server timestamp (i.e. rownumber) column.
I also use NHibernate's SchemaExport.Create() method to create the SQL Server database, so the mapping must result in a RowVersion column of type timestamp in the SQL Server table after NHibernate has created the database.
My DTO class is setup like this:
public class TestDto {
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual Byte[] RowVersion { get; set; }
}
Here Ayende explains how to setup for optimistic concurrency using XML mapping and a SQL Server timestamp column. My attempts to mimic this using mapping-by-code (shown below) have all failed in one way or another.
http://ayende.com/blog/3946/nhibernate-mapping-concurrency
Here is a mapping-by-code example for optimistic concurrency, but it doesn't use a SQL Server timestamp column - and my attempts to guess at the changes required to make it work with a timestamp column have failed:
http://notherdev.blogspot.com/2012/01/mapping-by-code-concurrency.html
Here is my non-working attempt at mapping-by-code that is based as best as I can determine on the previous two articles:
public TestMap() {
Table( "Test" );
DynamicUpdate( true );
Id( x => x.Id, map => {
map.Column( "ID" );
map.Generator( Generators.GuidComb );
} );
Version( x => x.RowVersion, map => {
map.Column( "RowVersion" );
map.Generated( VersionGeneration.Always );
map.UnsavedValue( null );
map.Insert( true );
//map.Type( new TimestampType() ); // Creates a datetime (not null) column.
//map.Type( new BinaryBlobType() ); // Creates a VARBINARY(MAX) (not null) column.
//map.Access( Accessor.Field ); // Causes error: Could not find property nor field 'RowVersion' in class 'SQC.Repository.Dtos.DataGroupDto'
} );
Property( x => x.Name, map => {
map.Column( "Name" );
map.NotNullable( true );
} );
}
When I uncomment this line the created table has RowVersion as "datetime (not null)" - and I need a SQL Server timestamp type.
map.Type( new TimestampType() ); // Creates a datetime (not null) column.
When I uncomment this line the created table has RowVersion as "VARBINARY(MAX) (not null)" - and I need a SQL Server timestamp type.
map.Type( new BinaryBlobType() ); // Creates a datetime (not null) column.
When I uncomment this line I get this error at runtime "Could not find property nor field 'RowVersion' in class 'TestDto'".
map.Access( Accessor.Field );
I hope someone can explain how to make this work using mapping-by-code (loquacious). The project is heavily committed to mapping-by-code, so Fluent NHibernate is not an option for us.
get rid of the "map.Access( Accessor.Field );"
This tells nhibernate to use a backing field to work with, however since you are using an auto property, there is no backing field.
Either dont have it, or just use Accessor.Proper
I know there have been a lot of questions about Entity Framework doing cross database queries on the same server posted to stackoverflow. Mostly the answer seems to be 'no', and this link from way back in 2008 is referenced. However, Entity Framework is changing all the time and with CTP5 out, I'm wondering if the answer is still the same - that you can't do it, or you can do it if you manually edit the edmx file, or you have to use views. This feature alone is the reason I'm still tied to Linq-to-SQL, as we have multiple SQL Server 2008 databases on the same server and need to query across them. Polluting our databases with hundreds of select * views is not an option, and with code-first development I don't have an edmx file to edit. I was playing with the pubs database to see if I could get somewhere, but I'm stuck. Any suggestions?
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.ComponentModel.DataAnnotations;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration;
namespace DbSchema {
public class Employee {
[Key]
public string ID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public short JobID { get; set; }
public Job Job { get; set; }
}
public class Job {
[Key]
public short ID { get; set; }
public string Description { get; set; }
}
public class PubsRepository : DbContext {
public DbSet<Employee> Employee { get; set; }
public DbSet<Job> Job { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder) {
// employee
var eeMap = modelBuilder.Entity<Employee>();
eeMap.ToTable("employee", "dbo"); // <-- how do I reference another database?
eeMap.Property(e => e.ID).HasColumnName("emp_id");
eeMap.Property(e => e.FirstName).HasColumnName("fname");
eeMap.Property(e => e.LastName).HasColumnName("lname");
eeMap.Property(e => e.JobID).HasColumnName("job_id");
// job
var jobMap = modelBuilder.Entity<Job>();
jobMap.Property(j => j.ID).HasColumnName("job_id");
jobMap.Property(j => j.Description).HasColumnName("job_desc");
}
public List<Employee> GetManagers() {
var qry = this.Employee.Where(x => x.Job.Description.Contains("manager"));
Debug.WriteLine(qry.ToString());
return qry.ToList(); // <-- error here when referencing another database!
}
}
}
I think that the answer is still no, but there are ways around it.
The reason why it is no, it that EF uses a DBContext, and a context has a connection string, and a connection string goes to a database.
Here are 2 ways around it:
use 2 different contexts one against each database, this will mean bringing data to the client and merging it on the client.
use linked tables on the database, pulling data through views, so that EF sees it as coming from a single database.
In your code it looks like you are using 2 dbcontexts
There are two ways to do it.
One is, of course, to create a view in one of the databases which does the cross database query, then access the veiw from your model as you would any other view.
The other was it to create the same cross database query view within the model itself by creating a DefiningQuery. This is most similar to how you would do it with SQLClient. In SQLClient, you'd create the view in T-SQL as the text of a SQLCommand, then execute the command to create a data reader or data table. Here you use the same T-SQL to create a DefiningQuery, then link it up with an Entity that you create manually. It's a bit of work, but it does exactly what you'd want it to.
Here's a link on using DefiningQuerys: http://msdn.microsoft.com/en-us/library/cc982038.aspx.
If you happen to have the book "Programming Entity Framework" by Lerman from O'Reilly, there a good example in chapter 16.
So you have to jump through a few hoops to do what you used to do directly with SQLClient, BUT you get the modeled Entity.
The answer is still the same. If you want to execute cross database query you have to fall back to SQL and use SqlQuery on context.Database.
Warning! using DefiningQuerys can be VERY SLOW!
Here's an example:
If this is the defining query that you create an Entity against:
Select
C.CustomerID,
C.FirstName,
C.LastName,
G.SalesCatetory
From
CustomerDatabase.dbo.Customers C
Inner Join MarketingDatabase.dbo.CustomerCategories G on G.CustomerID = C.CustomerID
Then when you do a select against the Entity by CustomerID, the SQL trace looks something like this:
Select
[Extent1].[CustomerID] as [CustomerID],
[Extent1].[FirstName] as [FirstName],
[Extent1].[LastName] as [LastName],
[Extent1].[SalesCatetory] as [SalesCatetory]
From (
Select
C.CustomerID,
C.FirstName,
C.LastName,
G.SalesCatetory
From
CustomerDatabase.dbo.Customers C
Inner Join MarketingDatabase.dbo.CustomerCategories G on G.CustomerID = C.CustomerID
) as [Extent1]
Where '123456' = [Extent1].[CustomerID]
SQL Server may run this query very slowly. I had one case, a little more complicated than the above example, where I tried the DefiningQuery text directly in a SQl Server Management Console query window by adding a where clause for the value I wanted to select for. It run in less than a second. Then I captured the SQL Trace from selecting for the same value from the Entity created for this DefiningQuery and ran the SQL Trace query in a SQL Server query window - it took 13 seconds!
So I guess that only real way to do cross database queries is to create a veiw on the server.