Let’s say that I have the following:
SQL
Create database called Clm, then run this script:
CREATE TABLE dbo.ModelData(
modelId bigint IDENTITY(1,1) NOT NULL,
numberOfAminoAcids int NOT NULL,
maxPeptideLength int NOT NULL,
seed int NOT NULL,
fileStructureVersion nvarchar(50) NOT NULL,
modelData nvarchar(max) NOT NULL,
createdOn datetime NOT NULL,
CONSTRAINT PK_ModelData PRIMARY KEY CLUSTERED
(
modelId ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON PRIMARY
) ON PRIMARY TEXTIMAGE_ON PRIMARY
GO
ALTER TABLE dbo.ModelData ADD CONSTRAINT DF_ModelData_createdOn DEFAULT (getdate()) FOR createdOn
GO
F#
[<Literal>]
let ClmDbName = "Clm"
[<Literal>]
let AppConfigFile = __SOURCE_DIRECTORY__ + "\.\App.config"
[<Literal>]
let ClmConnectionString = "Server=localhost;Database=" + ClmDbName + ";Integrated Security=SSPI"
[<Literal>]
let ClmSqlProviderName = "name=" + ClmDbName
type ClmDB = SqlProgrammabilityProvider<ClmSqlProviderName, ConfigFile = AppConfigFile>
type ModelDataTable = ClmDB.dbo.Tables.ModelData
type ModelDataTableRow = ModelDataTable.Row
type ModelDataTableData =
SqlCommandProvider<"select * from dbo.ModelData where modelId = #modelId", ClmConnectionString, ResultType.DataReader>
App.config
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
<connectionStrings configSource="db.config" />
<runtime>
<gcAllowVeryLargeObjects enabled="true" />
</runtime>
</configuration>
db.config
<connectionStrings>
<add name="Clm" connectionString="Data Source=localhost;Initial Catalog=Clm;Integrated Security=SSPI" />
</connectionStrings>
I need to get SQL identity value from table ModelData. It is used somewhere in the code. So, I have the following function to add a new row with some default values and then get identity value back.
let getNewModelDataId (conn : SqlConnection) =
let t = new ModelDataTable()
let r =
t.NewRow(
numberOfAminoAcids = 0,
maxPeptideLength = 0,
seed = 0,
fileStructureVersion = "",
modelData = "",
createdOn = DateTime.Now
)
t.Rows.Add r
t.Update(conn) |> ignore
r.modelId
let openConnIfClosed (conn : SqlConnection) =
match conn.State with
| ConnectionState.Closed -> do conn.Open()
| _ -> ignore ()
And the I use it to get new identity value of modelId from the database.
let modelId = getNewModelDataId conn
The after about 0.5 – 1.5 hours of execution time I need to update some data, e.g.
use d = new ModelDataTableData(conn)
let t1 = new ModelDataTable()
d.Execute(modelId = modelId) |> t1.Load
let r1 = t1.Rows |> Seq.find (fun e -> e.modelId = modelId)
r1.modelData <- "Some new data..."
t1.Update(conn) |> ignore
where the string "Some new data..." represents some fairly large string. This only happens once per modelId.
The code above does work. But it looks soooo ugly, epsecially the part t1.Rows |> Seq.find ... ☹ I guess that I am missing something about FSharp.Data.SqlClient type providers. I’d appreciate any advice.
To begin with the most obvious blunder: FSharp.Data.SqlClient type provider via its SqlCommandProvider supports any DML data modification statements, including UPDATE. So,
instead of all that jazz with pulling the model to the client side, modifying, and pushing back to the server, the same can be achieved by
...
use cmd = SqlCommandProvider<
"UPDATE [dbo].[ModelData] SET [modelData]=#ModelData WHERE [modelId]=#ModelId",
conn>(conn)
cmd.Execute(ModelData="Some new data...", ModelId=modelId) |> ignore
...
The less obvious problem relates to the use of identity field. It sounds like it does not have any special role beyond uniquely identifying every new model. So, instead of the tinkering with creating a fake record, then pulling its id from the server, then updating the record having this id with real values, why not just:
introduce an additional column modelUid of type uniqueidentifier
add a nonclustered index on it, if this matters
begin creating of every new model by generating a fresh GUID with System.Guid.NewGuid()
use this GUID value whatever you want, then when you ready to persist your model do it with SQL DML INSERT using the same GUID for modelUid field
Notice, that with such approach you also use just SqlCommandProvider type of FSharp.Data.SqlClient type provider, so the things stay simple.
Related
I have created Telerik rest web API service.
//Report controller c# code
public ReportsController()
{
var appPath = HttpContext.Current.Server.MapPath("~/");
var reportsPath = Path.Combine(appPath, "Reports");
var resolver = new ReportFileResolver(reportsPath)
.AddFallbackResolver(new ReportTypeResolver());
//Setup the ReportServiceConfiguration
configurationInstance = new ReportServiceConfiguration
{
HostAppId = "Html5App",
Storage = new FileStorage(),
ReportResolver = resolver,
// ReportSharingTimeout = 0,
// ClientSessionTimeout = 15,
};
this.ReportServiceConfiguration = configurationInstance;
}
//Report viewer Code
$("#reportViewer1")
.telerik_ReportViewer({
serviceUrl: "http://localhost:12345/api/Reports",
reportSource: {
report: "Dashboard.trdx",
parameters: { ReportYear : 2009 }
},
viewMode: telerikReportViewer.ViewModes.INTERACTIVE,
scaleMode: telerikReportViewer.ScaleModes.SPECIFIC,
scale: 1.0,
enableAccessibility: false,
sendEmail: { enabled: true }
});
//trdx Code of parameter
<DataSources>
<SqlDataSource ConnectionString="Telerik.Reporting.Examples.CSharp.Properties.Settings.TelerikConnectionString" SelectCommand="SELECT DISTINCT YEAR(OrderDate) AS Year
FROM Sales.SalesOrderHeader
ORDER BY Year" Name="yearDataSource" />
<SqlDataSource ConnectionString="Telerik.Reporting.Examples.CSharp.Properties.Settings.TelerikConnectionString" SelectCommand="SELECT
P.Name AS ProductName
, SOD.LineTotal / 1000 AS LineTotal
, SOH.OrderDate
, SS.Name AS StoreName
, C.FirstName + ' ' + COALESCE (C.MiddleName, '') + ' ' + C.LastName AS SalesPersonFullName
FROM
Production. Product AS P
INNER JOIN Sales.SalesOrderDetail AS SOD ON P.ProductID = SOD.ProductID
INNER JOIN Sales.SalesOrderHeader AS SOH ON SOD.SalesOrderID = SOH.SalesOrderID
INNER JOIN Sales.Store AS SS ON SS.CustomerID = SOH.CustomerID
INNER JOIN Sales.SalesPerson AS SP ON SP.SalesPersonID = SOH.SalesPersonID
INNER JOIN HumanResources.Employee AS E ON E.EmployeeID = SP.SalesPersonID
INNER JOIN Person.Contact AS C ON C.ContactID = E.ContactID
WHERE (YEAR(SOH.OrderDate) = #Year)" Name="mainDataSource">
<Parameters>
<SqlDataSourceParameter DbType="Int32" Name="#Year">
<Value>
<String>=Parameters.ReportYear.Value</String>
</Value>
</SqlDataSourceParameter>
</Parameters>
<DefaultValues>
<SqlDataSourceParameter DbType="Int32" Name="#Year">
<Value>
<String>2001</String>
</Value>
</SqlDataSourceParameter>
</DefaultValues>
</SqlDataSource>
</DataSources>
Passing a parameter "ReportYear" and using .trdx file.
I'm getting an error "Missing or invalid parameter value. Please input valid data for all parameters."
If I'm not passing the parameter then the report will function properly.
What else is needed for getting the parameter value in .trdx file?
I'm really stuck into it. It would be great if someone can help in to it.
You have to add report parameters to report, Not sql parameters.
Right click on report and go to report parameter.
https://docs.telerik.com/reporting/designing-reports-parameters-adding-parameters
Pass the report parameter as "ReportYear" instead of "Year"
Majority of our tables in our models has a field called "intConcurrencyID" that we intended to use for concurrency checking. I believe the only way to enable this field for concurrency checking in a Database First environment is to set the Concurrency Mode to Fixed.
However, if we have huge numbers of tables per EDMX, we will have a hard time manually configuring each type per entity not to mention the possibility of some entities to be overlooked.
Do you have any ideas on how I can automate this process? It seems that T4 Template is not the way to go since the Concurrency Mode is in the CSDL not in the Code Behind..
I looked at the edmx file before and after changing a property's concurrency mode and came up with a solution as a console app which parses the edmx file starting with the storage model for 'timestamp' columns and modifies the conceptual model by following the mappings. This is a fairly robust solution, but it has some caveats.
I'm using MSSQL 2008, for which the timestamp/rowversion is the defacto concurrency type. In my models I only use this type as a concurrency token. If you use it in other places, but have a consistent name used for all concurrency tokens instead, I've included code for this scenario that can be uncommented. If you simply use a different concurrency type and that type is unique to concurrency columns, you can change the type from 'timestamp' in this line of code:
IEnumerable<XElement> storageEntities =
from el in ssdl.Descendants(XName.Get("EntityType",ssdlNS))
where (from prop in el.Elements(XName.Get("Property",ssdlNS)) where prop.Attribute("Type").Value == "timestamp" select prop).Count()>0
select el;
If your concurrency pattern is more complicated, this code won't suffice.
That being said, I whipped this up in about an hour and ran it and it worked great for me on a model with nearly 200 entities, saving me a lot of annoyance going through the sluggish edmx designer, so I'm sure others will benefit. This is a console application so you can put this in the prebuild step of your model project to integrate it into your build process.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
namespace ConfigureConcurrency
{
class Program
{
static void Main(string[] args)
{
string edmxPath = args[0]; //or replace with a fixed path
if (edmxPath == null || edmxPath.Length == 0)
return;
string edmxNS = #"http://schemas.microsoft.com/ado/2008/10/edmx";
string ssdlNS = #"http://schemas.microsoft.com/ado/2009/02/edm/ssdl";
string csdlNS = #"http://schemas.microsoft.com/ado/2008/09/edm";
string mapNS = #"http://schemas.microsoft.com/ado/2008/09/mapping/cs";
XElement root = XElement.Load(edmxPath);
//Storage Model
XElement ssdl = root.Descendants(XName.Get("StorageModels", edmxNS)).FirstOrDefault();
//Conceptual
XElement csdl = root.Descendants(XName.Get("ConceptualModels", edmxNS)).FirstOrDefault();
//Mapping
XElement map = root.Descendants(XName.Get("Mappings", edmxNS)).FirstOrDefault();
/*
Use this code instead of the line below it, if the type of your concurrency columns is used on other non-concurrency columns
and you use the same name for every concurrency column
string ConcurrencyColumnName = "RowVersion";
IEnumerable<XElement> storageEntities =
from el in ssdl.Descendants(XName.Get("EntityType", ssdlNS))
where (from prop in el.Elements(XName.Get("Property", ssdlNS)) where prop.Attribute("Name").Value == ConcurrencyColumnName select prop).Count() > 0
select el;
*/
IEnumerable<XElement> storageEntities =
from el in ssdl.Descendants(XName.Get("EntityType",ssdlNS))
where (from prop in el.Elements(XName.Get("Property",ssdlNS)) where prop.Attribute("Type").Value == "timestamp" select prop).Count()>0
select el;
//for each timestamp column, find the mapping then find the conceptual model property and establish the concurrency mode
foreach(XElement storageEntity in storageEntities)
{
//Get the mapping
XElement mapping = (from el in map.Descendants(XName.Get("EntityTypeMapping",mapNS)) where el.Element(XName.Get("MappingFragment",mapNS)).Attribute("StoreEntitySet").Value == storageEntity.Attribute("Name").Value select el).FirstOrDefault();
if (mapping != null)
{
//Get the column mapping
XElement column = (from el in storageEntity.Descendants(XName.Get("Property",ssdlNS)) where el.Attribute("Type").Value == "timestamp" select el).FirstOrDefault();
string columnName = column.Attribute("Name").Value;
XElement columnMapping = (from el in mapping.Descendants(XName.Get("ScalarProperty",mapNS)) where el.Attribute("ColumnName").Value == columnName select el).FirstOrDefault();
string propertyName = columnMapping.Attribute("Name").Value;
//Get the conceptual schema namespace and type name
string[] split = mapping.Attribute("TypeName").Value.Split('.');
string ns="", typeName =split[split.Length-1];
for (int i = 0; i < split.Length-1; i++)
{
if (i>0)
ns+=".";
ns += split[i];
}
//Find the entry in the conceptual model
XElement schema = (from el in csdl.Elements(XName.Get("Schema",csdlNS)) where el.Attribute("Namespace").Value == ns select el).FirstOrDefault();
if (schema != null)
{
//Find the entity type
XElement entity = (from el in schema.Descendants(XName.Get("EntityType",csdlNS)) where el.Attribute("Name").Value == typeName select el).FirstOrDefault();
//Find the property
XElement concurrencyProperty = (from el in entity.Elements(XName.Get("Property",csdlNS)) where el.Attribute("Name").Value == propertyName select el).FirstOrDefault();
//Set concurrency mode to fixed
concurrencyProperty.SetAttributeValue("ConcurrencyMode", "Fixed");
}
}
}
//Save the modifications
root.Save(edmxPath);
}
}
}
Solved my similar problem with the above console app.
But if you are running a later version of Entity Framework, be sure to update references to schemas. I am using EF 5 and for me I used the following lines:
string edmxNS = #"http://schemas.microsoft.com/ado/2009/11/edmx";
string ssdlNS = #"http://schemas.microsoft.com/ado/2009/11/edm/ssdl";
string csdlNS = #"http://schemas.microsoft.com/ado/2009/11/edm";
string mapNS = #"http://schemas.microsoft.com/ado/2009/11/mapping/cs";
I use OpenJPA 2.1 on WebSphere Application Server 8.
I have a bidirectional oneToMay relation like this:
#Entity(name = "table1")
public class Table1 {
#Id
#Column(columnDefinition = "DATE")
private Date date;
#OneToMany(mappedBy = "date", targetEntity = Table2.class, fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
private List<Table2> table2List;
}
#Entity(name = "table2")
public class Tabl2{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "fk_date", nullable = false)
private Table1 table1;
#Column(name = "value", nullable = false, columnDefinition = "INT")
private int value;
}
Now I want to select the whole table1 entity with a condition on table2 like this:
TypedQuery<WeekBean> q = em.createQuery(
"SELECT t1 from table1 t1 JOIN FETCH t1.table2List t2 WHERE t2.value = :value",
t1.class);
But this does not work, beacause I get an ArgumentException:
Exception in thread "main" <openjpa-2.1.1-SNAPSHOT-r422266:1141200 nonfatal user error> org.apache.openjpa.persistence.ArgumentException: " Encountered "f" at character 50, but expected [",", ".", "GROUP", "HAVING", "INNER", "JOIN", "LEFT", "ORDER", "WHERE", <EOF>]."
at org.apache.openjpa.kernel.jpql.JPQLParser.parse(JPQLParser.java:51)
at org.apache.openjpa.kernel.ExpressionStoreQuery.newCompilation(ExpressionStoreQuery.java:154)
at org.apache.openjpa.datacache.QueryCacheStoreQuery.newCompilation(QueryCacheStoreQuery.java:262)
at org.apache.openjpa.kernel.QueryImpl.newCompilation(QueryImpl.java:672)
at org.apache.openjpa.kernel.QueryImpl.compilationFromCache(QueryImpl.java:654)
at org.apache.openjpa.kernel.QueryImpl.compileForCompilation(QueryImpl.java:620)
at org.apache.openjpa.kernel.QueryImpl.compileForExecutor(QueryImpl.java:682)
at org.apache.openjpa.kernel.QueryImpl.compile(QueryImpl.java:589)
at org.apache.openjpa.persistence.EntityManagerImpl.createQuery(EntityManagerImpl.java:996)
at com.ibm.ws.persistence.EntityManagerImpl.createQuery(EntityManagerImpl.java:107)
at com.ibm.ws.persistence.EntityManagerImpl.createQuery(EntityManagerImpl.java:86)
at com.ibm.ws.persistence.EntityManagerImpl.createQuery(EntityManagerImpl.java:34)
at org.apache.openjpa.persistence.EntityManagerImpl.createQuery(EntityManagerImpl.java:974)
How can I use a JOIN FETCH with a condition on the collection?
Best Regards Veote
According JPA 2.0 specification such a construct is not supported, and looks like OpenJPA also does not provide functionality as vendor extension:
The association referenced by the right side of the FETCH JOIN clause
must be an association that belongs to an entity that is returned as a
result of the query. It is not permitted to specify an identification
variable for the entities referenced by the right side of the FETCH
JOIN clause, and hence references to the implicitly fetched entities
cannot appear elsewhere in the query.
In your case this means that you cannot define t2 and as a consequence you cannot use it in the WHERE clause.
I woud like to inquire if my Linq solution below is a good solution or if there is a better way. I am new to using Linq, and am most familiar with MySQL. So I've been converting one of my past projects from PHP to .NET MVC and am trying to learn Linq. I would like to find out if there is a better solution than the one I came up with.
I have the following table structures:
CREATE TABLE maplocations (
ID int NOT NULL AUTO_INCREMENT,
name varchar(35) NOT NULL,
Lat double NOT NULL,
Lng double NOT NULL,
PRIMARY KEY (ID),
UNIQUE KEY name (name)
);
CREATE TABLE reservations (
ID INT NOT NULL AUTO_INCREMENT,
loc_ID INT NOT NULL,
resDate DATE NOT NULL,
user_ID INT NOT NULL,
PRIMARY KEY (ID),
UNIQUE KEY one_per (loc_ID, resDate),
FOREIGN KEY (user_ID) REFERENCES Users (ID),
FOREIGN KEY (loc_ID) REFERENCES MapLocations (ID)
);
CREATE TABLE Users (
ID INT NOT NULL AUTO_INCREMENT,
name VARCHAR(20) NOT NULL,
email VARCHAR(50) NOT NULL,
pass VARCHAR(128) NOT NULL,
salt VARCHAR(5) NOT NULL,
PRIMARY KEY (ID),
UNIQUE KEY unique_names (name),
UNIQUE KEY unique_email (email)
);
In MySQL, I use the following query to get the ealiest reservation at each maplocation with a non null date for any locations that don't have a reservation.
SELECT locs.*, if(res.resDate,res.resDate,'0001-01-01') as resDate, res.Name as User
FROM MapLocations locs
LEFT JOIN (
SELECT loc_ID, resDate, Name
FROM Reservations, Users
WHERE resDate >= Date(Now())
AND user_ID = Users.ID
ORDER BY resDate
) res on locs.ID = res.loc_ID
group by locs.ID
ORDER BY locs.Name;
In Linq, with Visual studio automatically creating much of the structure after connecting to the database, I have come up with the following equivalent to that SQL Query
var resList = (from res in Reservations
where res.ResDate >= DateTime.Today
select res);
var locAndRes =
(from loc in Maplocations
join res in resList on loc.ID equals res.Loc_ID into join1
from res2 in join1.DefaultIfEmpty()
join usr in Users on res2.User_ID equals usr.ID into join2
from usr2 in join2.DefaultIfEmpty()
orderby loc.ID,res2.ResDate
select new {
ID = (int)loc.ID,
Name = (string)loc.Name,
Lat = (double)loc.Lat,
Lng = (double)loc.Lng,
resDate = res2 != null ?(DateTime)res2.ResDate : DateTime.MinValue,
user = usr2 != null ? usr2.Name : null
}).GroupBy(a => a.ID).Select(b => b.FirstOrDefault());
So, I'm wondering is there a better way to perform this query?
Are these equivalent?
Are there any good practices I should be following?
Also, one more question, I'm having trouble getting this from the var to a List. doing something like this doesn't work
List<locAndResModel> locList = locAndRes.AsQueryable().ToList<locAndResModel>();
In the above snippet locAndResModel is just a class which has variables to match the int, string, double double, DateTime, string results of the query. Is there an easy way to get a list without having to do a foreach and passing the results to a constructor override? Or should I just add it to ViewData and return the View?
You'll want to take advantage of the automatic joins performed by the Entity Framework. Give this a try and let me know if it does what you want:
var locAndRes = from maplocation in MapLocations
let earliestReservationDate = maplocation.Reservations.Min(res => res.resDate)
let earliestReservation = (from reservation in mapLocation.Reservations
where reservation.resDate == earliestReservationDate && reservation.resDate >= DateTime.Today
select reservation).FirstOrDefault()
select new locAndResModel( maplocation.ID, maplocation.name, maplocation.Lat, maplocation.Lng, earliestReservation != null ? earliestReservation.resDate : DateTime.MinValue, earliestReservation != null ?earliestReservation.User.name : null)
I'm rewriting a C# library in F# in which most of the classes map one-to-one with database tables (similar to ActiveRecord). I'm considering whether to use records or classes (maybe even DUs?). There's a fair amount of validation in the property setters to maintain invariants. What would be the best way to model this in F#? I don't want an object that violates business logic to be persisted to the database. Any ideas are welcome.
A few additional thoughts...
Is it better to move the invariants to an external 'controller' class? Coming from C# it feels wrong to allow an object that corresponds to a database record to contain anything that can't be saved to the database. I suppose because failing earlier seems better than failing later.
You can have your data in a record, and still keep the validation logic with the data type, by attaching methods to the record:
type Person =
{ First : string;
Last : string; } with
member x.IsValid () =
let hasValue = System.String.IsNullOrEmpty >> not
hasValue x.First && hasValue x.Last
let jeff = { First = "Jeff"; Last = "Goldblum" }
let jerry = { jeff with First = "Jerry" }
let broken = { jerry with Last = "" }
let valid = [jeff; jerry; broken]
|> List.filter (fun x -> x.IsValid())
The copy semantics for records are almost as convenient as setting a property. The validation doesn't happen on property set, but it's easy to filter a list of records down to only the valid ones.
This should actually be a good way for you to handle it. Having your validation logic in the constructor will give you piece of mind later on in your code because the object is immutable. This also opens up multi-threading possibilities.
Immutable Version
type Customer (id, name) =
do // Constructor
if id <= 0 then
raise(new ArgumentException("Invalid ID.", "id"))
elif String.IsNullOrEmpty(name) then
raise(new ArgumentException("Invalid Name.", "name"))
member this.ID
with get() = id
member this.Name
with get() = name
member this.ModifyName value =
new Customer(id, value)
Mutable Version
type Customer (id) =
let mutable name = ""
do // Constructor
if id <= 0 then
raise(new ArgumentException("Invalid ID.", "id"))
member this.ID
with get() = id
member this.Name
with get() = name
and set value =
if String.IsNullOrEmpty(name) then
raise(new ArgumentException("Invalid Name.", "value"))
name <- value
Have you taken a look at my FunctionalNHibernate project? It's designed as a layer on top of nhibernate to let you declaratively map records to a database. It's early days, but it's just about usable:
http://bitbucket.org/robertpi/functionalnhibernate/